mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 15:17:31 +00:00
Compare commits
38 Commits
SP-1464-FE
...
bugfix/cle
Author | SHA1 | Date | |
---|---|---|---|
58755eafe1 | |||
ce225818fb | |||
8762a7aaa8 | |||
8dc833b2c3 | |||
13cef151aa | |||
ab23be9828 | |||
687b68ab22 | |||
25614c3dd0 | |||
7cbe20ae88 | |||
349fe6c555 | |||
9779f3783c | |||
fe3db663b6 | |||
888d444752 | |||
bab5e06968 | |||
d7b6174dee | |||
6ef0b2f9d1 | |||
3ceb03826e | |||
52608b1f35 | |||
ac2996629e | |||
51c52c66cb | |||
0f56273d99 | |||
34d4d892d9 | |||
3193fd26fe | |||
43802a9fad | |||
6e0b1775f0 | |||
233fb2ee2c | |||
b26928b3d5 | |||
6fc35a7b9a | |||
756457927c | |||
f30d7c0117 | |||
976d6e385a | |||
ff07e7509d | |||
17a582ab99 | |||
09fb604acc | |||
2068df173d | |||
bfc2a381d2 | |||
c03b8f290c | |||
2c684a9495 |
@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class SearchResetButtons extends StatelessWidget {
|
||||
const SearchResetButtons({
|
||||
@ -17,8 +17,10 @@ class SearchResetButtons extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 25),
|
||||
|
@ -108,7 +108,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
|
||||
return DeviceManagementBody(
|
||||
devices: deviceState.filteredDevices);
|
||||
} else {
|
||||
return const Center(child: Text('Error fetching Devices'));
|
||||
return const DeviceManagementBody(devices: []);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -72,6 +72,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
child: state is DeviceManagementLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: isLargeScreenSize(context)
|
||||
|
@ -14,29 +14,29 @@ class DeviceSearchFilters extends StatefulWidget {
|
||||
|
||||
class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
|
||||
with HelperResponsiveLayout {
|
||||
final _unitNameController = TextEditingController();
|
||||
final _productNameController = TextEditingController();
|
||||
late final TextEditingController _unitNameController;
|
||||
late final TextEditingController _productNameController;
|
||||
|
||||
List<Widget> get _widgets => [
|
||||
_buildSearchField("Space Name", _unitNameController, 200),
|
||||
_buildSearchField("Device Name / Product Name", _productNameController, 300),
|
||||
_buildSearchResetButtons(),
|
||||
];
|
||||
@override
|
||||
void initState() {
|
||||
_unitNameController = TextEditingController();
|
||||
_productNameController = TextEditingController();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (isExtraLargeScreenSize(context)) {
|
||||
return Row(
|
||||
children: _widgets
|
||||
.map((e) => Padding(padding: const EdgeInsets.all(10), child: e))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
return Wrap(
|
||||
alignment: WrapAlignment.start,
|
||||
runAlignment: WrapAlignment.start,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
spacing: 20,
|
||||
runSpacing: 10,
|
||||
children: _widgets,
|
||||
children: [
|
||||
_buildSearchField("Space Name", _unitNameController, 200),
|
||||
_buildSearchField("Device Name / Product Name", _productNameController, 300),
|
||||
_buildSearchResetButtons(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -24,11 +24,10 @@ class FlushMountedPresenceSensorChangeValueEvent
|
||||
extends FlushMountedPresenceSensorEvent {
|
||||
final int value;
|
||||
final String code;
|
||||
final bool isBatchControl;
|
||||
|
||||
const FlushMountedPresenceSensorChangeValueEvent({
|
||||
required this.value,
|
||||
required this.code,
|
||||
this.isBatchControl = false,
|
||||
});
|
||||
|
||||
@override
|
||||
|
@ -75,7 +75,7 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.nearDetection / 100).toDouble(),
|
||||
value: (model.nearDetection / 100).clamp(0.0, double.infinity),
|
||||
title: 'Nearest Detect Dist:',
|
||||
description: 'm',
|
||||
minValue: 0.0,
|
||||
@ -92,7 +92,7 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.farDetection / 100).toDouble(),
|
||||
value: (model.farDetection / 100).clamp(0.0, double.infinity),
|
||||
title: 'Max Detect Dist:',
|
||||
description: 'm',
|
||||
minValue: 0.0,
|
||||
@ -109,7 +109,7 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: model.presenceDelay.toDouble(),
|
||||
value: model.sensiReduce.toDouble(),
|
||||
title: 'Trigger Level:',
|
||||
minValue: 0,
|
||||
maxValue: 3,
|
||||
@ -117,7 +117,7 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codePresenceDelay,
|
||||
code: FlushMountedPresenceSensorModel.codeSensiReduce,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
@ -137,19 +137,21 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.sensiReduce.toDouble()),
|
||||
value: (model.presenceDelay / 10).toDouble(),
|
||||
title: 'Target Confirm Time:',
|
||||
description: 's',
|
||||
minValue: 0,
|
||||
maxValue: 3,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codeSensiReduce,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
minValue: 0.0,
|
||||
maxValue: 0.5,
|
||||
steps: 0.1,
|
||||
valuesPercision: 1,
|
||||
action: (double value) =>
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codePresenceDelay,
|
||||
value: (value * 10).toInt(),
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: ((model.noneDelay / 10).toDouble()),
|
||||
|
@ -15,7 +15,7 @@ import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_la
|
||||
|
||||
class FlushMountedPresenceSensorControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const FlushMountedPresenceSensorControlView({super.key, required this.device});
|
||||
const FlushMountedPresenceSensorControlView({required this.device, super.key});
|
||||
|
||||
final AllDevicesModel device;
|
||||
|
||||
@ -113,7 +113,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.nearDetection / 100).toDouble(),
|
||||
value: (model.nearDetection / 100).clamp(0.0, double.infinity),
|
||||
title: 'Nearest Detect Dist:',
|
||||
description: 'm',
|
||||
minValue: 0.0,
|
||||
@ -129,7 +129,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.farDetection / 100).toDouble(),
|
||||
value: (model.farDetection / 100).clamp(0.0, double.infinity),
|
||||
title: 'Max Detect Dist:',
|
||||
description: 'm',
|
||||
minValue: 0.0,
|
||||
@ -145,20 +145,20 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.presenceDelay.toDouble()),
|
||||
value: model.sensiReduce.toDouble(),
|
||||
title: 'Trigger Level:',
|
||||
minValue: 0,
|
||||
maxValue: 3,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
code: FlushMountedPresenceSensorModel.codePresenceDelay,
|
||||
code: FlushMountedPresenceSensorModel.codeSensiReduce,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.occurDistReduce.toDouble()),
|
||||
value: model.occurDistReduce.toDouble(),
|
||||
title: 'Indent Level:',
|
||||
minValue: 0,
|
||||
maxValue: 3,
|
||||
@ -171,21 +171,23 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.sensiReduce.toDouble()),
|
||||
value: (model.presenceDelay / 10).toDouble(),
|
||||
valuesPercision: 1,
|
||||
title: 'Target Confirm Time:',
|
||||
description: 's',
|
||||
minValue: 0,
|
||||
maxValue: 3,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
code: FlushMountedPresenceSensorModel.codeSensiReduce,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
minValue: 0.0,
|
||||
maxValue: 0.5,
|
||||
steps: 0.1,
|
||||
action: (double value) =>
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
code: FlushMountedPresenceSensorModel.codePresenceDelay,
|
||||
value: (value * 10).toInt(),
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: ((model.noneDelay / 10).toDouble()),
|
||||
value: (model.noneDelay / 10).toDouble(),
|
||||
description: 's',
|
||||
title: 'Disappe Delay:',
|
||||
minValue: 20,
|
||||
|
@ -217,29 +217,31 @@ class SmartPowerBloc extends Bloc<SmartPowerEvent, SmartPowerState> {
|
||||
try {
|
||||
var status =
|
||||
await DevicesManagementApi().getPowerClampInfo(event.deviceId);
|
||||
deviceStatus = PowerClampModel.fromJson(status);
|
||||
|
||||
deviceStatus = PowerClampModel.fromJson(status as Map<String, Object?>? ??{});
|
||||
final phaseADataPoints = deviceStatus.status.phaseA.dataPoints;
|
||||
final phaseBDataPoints = deviceStatus.status.phaseB.dataPoints;
|
||||
final phaseCDataPoints = deviceStatus.status.phaseC.dataPoints;
|
||||
phaseData = [
|
||||
{
|
||||
'name': 'Phase A',
|
||||
'voltage': '${deviceStatus.status.phaseA.dataPoints[0].value / 10} V',
|
||||
'current': '${deviceStatus.status.phaseA.dataPoints[1].value / 10} A',
|
||||
'activePower': '${deviceStatus.status.phaseA.dataPoints[2].value} W',
|
||||
'powerFactor': '${deviceStatus.status.phaseA.dataPoints[3].value}',
|
||||
'voltage': '${(phaseADataPoints.elementAtOrNull(0)?.value as num? ?? 0) / 10} V',
|
||||
'current': '${(phaseADataPoints.elementAtOrNull(1)?.value as num? ?? 0) / 10} A',
|
||||
'activePower': '${phaseADataPoints.elementAtOrNull(2)?.value??'N/A'} W',
|
||||
'powerFactor': '${phaseADataPoints.elementAtOrNull(3)?.value??'N/A'}',
|
||||
},
|
||||
{
|
||||
'name': 'Phase B',
|
||||
'voltage': '${deviceStatus.status.phaseB.dataPoints[0].value / 10} V',
|
||||
'current': '${deviceStatus.status.phaseB.dataPoints[1].value / 10} A',
|
||||
'activePower': '${deviceStatus.status.phaseB.dataPoints[2].value} W',
|
||||
'powerFactor': '${deviceStatus.status.phaseB.dataPoints[3].value}',
|
||||
'voltage': '${(phaseBDataPoints .elementAtOrNull(0)?.value as num? ?? 0) / 10} V',
|
||||
'current': '${(phaseBDataPoints .elementAtOrNull(1)?.value as num? ?? 0) / 10} A',
|
||||
'activePower': '${phaseBDataPoints.elementAtOrNull(2)?.value??'N/A'} W',
|
||||
'powerFactor': '${phaseBDataPoints.elementAtOrNull(3)?.value??'N/A'}',
|
||||
},
|
||||
{
|
||||
'name': 'Phase C',
|
||||
'voltage': '${deviceStatus.status.phaseC.dataPoints[0].value / 10} V',
|
||||
'current': '${deviceStatus.status.phaseC.dataPoints[1].value / 10} A',
|
||||
'activePower': '${deviceStatus.status.phaseC.dataPoints[2].value} W',
|
||||
'powerFactor': '${deviceStatus.status.phaseC.dataPoints[3].value}',
|
||||
'voltage': '${(phaseCDataPoints.elementAtOrNull(0)?.value as num? ?? 0) / 10} V',
|
||||
'current': '${(phaseCDataPoints.elementAtOrNull(1)?.value as num? ?? 0) / 10} A',
|
||||
'activePower': '${phaseCDataPoints.elementAtOrNull(2)?.value ?? 'N/A'} W',
|
||||
'powerFactor': '${phaseCDataPoints.elementAtOrNull(3)?.value ?? 'N/A'}',
|
||||
},
|
||||
];
|
||||
emit(GetDeviceStatus());
|
||||
@ -785,7 +787,7 @@ class SmartPowerBloc extends Bloc<SmartPowerEvent, SmartPowerState> {
|
||||
void selectDateRange() async {
|
||||
DateTime startDate = dateTime!;
|
||||
DateTime endDate = DateTime(startDate.year, startDate.month + 1, 1)
|
||||
.subtract(Duration(days: 1));
|
||||
.subtract(const Duration(days: 1));
|
||||
String formattedEndDate = DateFormat('dd/MM/yyyy').format(endDate);
|
||||
endChartDate = ' - $formattedEndDate';
|
||||
}
|
||||
|
@ -12,9 +12,9 @@ class PowerClampModel {
|
||||
|
||||
factory PowerClampModel.fromJson(Map<String, dynamic> json) {
|
||||
return PowerClampModel(
|
||||
productUuid: json['productUuid'],
|
||||
productType: json['productType'],
|
||||
status: PowerStatus.fromJson(json['status']),
|
||||
productUuid: json['productUuid'] as String? ?? '',
|
||||
productType: json['productType'] as String? ?? '',
|
||||
status: PowerStatus.fromJson(json['status'] as Map<String, dynamic>? ?? {}),
|
||||
);
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ class PowerClampModel {
|
||||
return PowerClampModel(
|
||||
productUuid: productUuid ?? this.productUuid,
|
||||
productType: productType ?? this.productType,
|
||||
status: statusPower ?? this.status,
|
||||
status: statusPower ?? status,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -46,12 +46,10 @@ class PowerStatus {
|
||||
|
||||
factory PowerStatus.fromJson(Map<String, dynamic> json) {
|
||||
return PowerStatus(
|
||||
phaseA: Phase.fromJson(json['phaseA']),
|
||||
phaseB: Phase.fromJson(json['phaseB']),
|
||||
phaseC: Phase.fromJson(json['phaseC']),
|
||||
general: Phase.fromJson(json['general']
|
||||
// List<DataPoint>.from(
|
||||
// json['general'].map((x) => DataPoint.fromJson(x))),
|
||||
phaseA: Phase.fromJson(json['phaseA']as List<dynamic>? ?? []),
|
||||
phaseB: Phase.fromJson(json['phaseB']as List<dynamic>? ?? []),
|
||||
phaseC: Phase.fromJson(json['phaseC']as List<dynamic>? ?? []),
|
||||
general: Phase.fromJson(json['general']as List<dynamic>? ?? []
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -69,30 +67,30 @@ class Phase {
|
||||
}
|
||||
|
||||
class DataPoint {
|
||||
dynamic code;
|
||||
dynamic customName;
|
||||
dynamic dpId;
|
||||
dynamic time;
|
||||
dynamic type;
|
||||
dynamic value;
|
||||
final String? code;
|
||||
final String? customName;
|
||||
final int? dpId;
|
||||
final int? time;
|
||||
final String? type;
|
||||
final dynamic value;
|
||||
|
||||
DataPoint({
|
||||
required this.code,
|
||||
required this.customName,
|
||||
required this.dpId,
|
||||
required this.time,
|
||||
required this.type,
|
||||
required this.value,
|
||||
this.code,
|
||||
this.customName,
|
||||
this.dpId,
|
||||
this.time,
|
||||
this.type,
|
||||
this.value,
|
||||
});
|
||||
|
||||
factory DataPoint.fromJson(Map<String, dynamic> json) {
|
||||
return DataPoint(
|
||||
code: json['code'],
|
||||
customName: json['customName'],
|
||||
dpId: json['dpId'],
|
||||
time: json['time'],
|
||||
type: json['type'],
|
||||
value: json['value'],
|
||||
code: json['code'] as String?,
|
||||
customName: json['customName'] as String?,
|
||||
dpId: json['dpId'] as int?,
|
||||
time: json['time'] as int?,
|
||||
type: json['type'] as String?,
|
||||
value: json['value'] as dynamic,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class EnergyConsumptionPage extends StatefulWidget {
|
||||
@ -10,7 +10,8 @@ class EnergyConsumptionPage extends StatefulWidget {
|
||||
final Widget widget;
|
||||
final Function()? onTap;
|
||||
|
||||
EnergyConsumptionPage({
|
||||
const EnergyConsumptionPage({
|
||||
super.key,
|
||||
required this.chartData,
|
||||
required this.totalConsumption,
|
||||
required this.date,
|
||||
@ -91,11 +92,12 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
|
||||
],
|
||||
),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.11,
|
||||
height: MediaQuery.sizeOf(context).height * 0.09,
|
||||
child: LineChart(
|
||||
LineChartData(
|
||||
lineTouchData: LineTouchData(
|
||||
@ -151,7 +153,7 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
|
||||
child: RotatedBox(
|
||||
quarterTurns: -1,
|
||||
child: Text(_chartData[index].time,
|
||||
style: TextStyle(fontSize: 10)),
|
||||
style: const TextStyle(fontSize: 10)),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -190,8 +192,8 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
|
||||
spots: _chartData
|
||||
.asMap()
|
||||
.entries
|
||||
.map((entry) => FlSpot(entry.key.toDouble(),
|
||||
entry.value.consumption))
|
||||
.map((entry) => FlSpot(
|
||||
entry.key.toDouble(), entry.value.consumption))
|
||||
.toList(),
|
||||
isCurved: true,
|
||||
color: ColorsManager.primaryColor.withOpacity(0.6),
|
||||
@ -218,7 +220,7 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
|
||||
borderData: FlBorderData(
|
||||
show: false,
|
||||
border: Border.all(
|
||||
color: Color(0xff023DFE).withOpacity(0.7),
|
||||
color: const Color(0xff023DFE).withOpacity(0.7),
|
||||
width: 10,
|
||||
),
|
||||
),
|
||||
@ -253,11 +255,9 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
|
||||
child: InkWell(
|
||||
onTap: widget.onTap,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: Text(widget.date),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: Text(widget.date),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -12,8 +12,7 @@ import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
//Smart Power Clamp
|
||||
class SmartPowerDeviceControl extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayout {
|
||||
final String deviceId;
|
||||
|
||||
const SmartPowerDeviceControl({super.key, required this.deviceId});
|
||||
@ -25,27 +24,27 @@ class SmartPowerDeviceControl extends StatelessWidget
|
||||
..add(SmartPowerFetchDeviceEvent(deviceId)),
|
||||
child: BlocBuilder<SmartPowerBloc, SmartPowerState>(
|
||||
builder: (context, state) {
|
||||
final _blocProvider = BlocProvider.of<SmartPowerBloc>(context);
|
||||
final blocProvider = BlocProvider.of<SmartPowerBloc>(context);
|
||||
|
||||
if (state is SmartPowerLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is FakeState) {
|
||||
return _buildStatusControls(
|
||||
currentPage: _blocProvider.currentPage,
|
||||
currentPage: blocProvider.currentPage,
|
||||
context: context,
|
||||
blocProvider: _blocProvider,
|
||||
blocProvider: blocProvider,
|
||||
);
|
||||
} else if (state is GetDeviceStatus) {
|
||||
return _buildStatusControls(
|
||||
currentPage: _blocProvider.currentPage,
|
||||
currentPage: blocProvider.currentPage,
|
||||
context: context,
|
||||
blocProvider: _blocProvider,
|
||||
blocProvider: blocProvider,
|
||||
);
|
||||
} else if (state is FilterRecordsState) {
|
||||
return _buildStatusControls(
|
||||
currentPage: _blocProvider.currentPage,
|
||||
currentPage: blocProvider.currentPage,
|
||||
context: context,
|
||||
blocProvider: _blocProvider,
|
||||
blocProvider: blocProvider,
|
||||
);
|
||||
}
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
@ -60,7 +59,7 @@ class SmartPowerDeviceControl extends StatelessWidget
|
||||
required SmartPowerBloc blocProvider,
|
||||
required int currentPage,
|
||||
}) {
|
||||
PageController _pageController = PageController(initialPage: currentPage);
|
||||
PageController pageController = PageController(initialPage: currentPage);
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
child: DeviceControlsContainer(
|
||||
@ -85,25 +84,31 @@ class SmartPowerDeviceControl extends StatelessWidget
|
||||
PowerClampInfoCard(
|
||||
iconPath: Assets.powerActiveIcon,
|
||||
title: 'Active',
|
||||
value: blocProvider
|
||||
.deviceStatus.status.general.dataPoints[2].value
|
||||
.toString(),
|
||||
value: blocProvider.deviceStatus.status.general.dataPoints
|
||||
.elementAtOrNull(2)
|
||||
?.value
|
||||
.toString() ??
|
||||
'',
|
||||
unit: '',
|
||||
),
|
||||
PowerClampInfoCard(
|
||||
iconPath: Assets.voltMeterIcon,
|
||||
title: 'Current',
|
||||
value: blocProvider
|
||||
.deviceStatus.status.general.dataPoints[1].value
|
||||
.toString(),
|
||||
value: blocProvider.deviceStatus.status.general.dataPoints
|
||||
.elementAtOrNull(1)
|
||||
?.value
|
||||
.toString() ??
|
||||
'',
|
||||
unit: ' A',
|
||||
),
|
||||
PowerClampInfoCard(
|
||||
iconPath: Assets.frequencyIcon,
|
||||
title: 'Frequency',
|
||||
value: blocProvider
|
||||
.deviceStatus.status.general.dataPoints[4].value
|
||||
.toString(),
|
||||
value: blocProvider.deviceStatus.status.general.dataPoints
|
||||
.elementAtOrNull(4)
|
||||
?.value
|
||||
.toString() ??
|
||||
'',
|
||||
unit: ' Hz',
|
||||
),
|
||||
],
|
||||
@ -142,7 +147,7 @@ class SmartPowerDeviceControl extends StatelessWidget
|
||||
icon: const Icon(Icons.arrow_left),
|
||||
onPressed: () {
|
||||
blocProvider.add(SmartPowerArrowPressedEvent(-1));
|
||||
_pageController.previousPage(
|
||||
pageController.previousPage(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
@ -162,7 +167,7 @@ class SmartPowerDeviceControl extends StatelessWidget
|
||||
icon: const Icon(Icons.arrow_right),
|
||||
onPressed: () {
|
||||
blocProvider.add(SmartPowerArrowPressedEvent(1));
|
||||
_pageController.nextPage(
|
||||
pageController.nextPage(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
@ -177,7 +182,7 @@ class SmartPowerDeviceControl extends StatelessWidget
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: PageView(
|
||||
controller: _pageController,
|
||||
controller: pageController,
|
||||
onPageChanged: (int page) {
|
||||
blocProvider.add(SmartPowerPageChangedEvent(page));
|
||||
},
|
||||
@ -190,8 +195,8 @@ class SmartPowerDeviceControl extends StatelessWidget
|
||||
blocProvider.add(SelectDateEvent(context: context));
|
||||
blocProvider.add(FilterRecordsByDateEvent(
|
||||
selectedDate: blocProvider.dateTime!,
|
||||
viewType: blocProvider
|
||||
.views[blocProvider.currentIndex]));
|
||||
viewType:
|
||||
blocProvider.views[blocProvider.currentIndex]));
|
||||
},
|
||||
widget: blocProvider.dateSwitcher(),
|
||||
chartData: blocProvider.energyDataList.isNotEmpty
|
||||
|
@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/routines/models/ac/ac_operational_value.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
@ -74,11 +75,26 @@ class ACHelper {
|
||||
child: _buildFunctionsList(
|
||||
context: context,
|
||||
acFunctions: acFunctions,
|
||||
onFunctionSelected: (functionCode, operationName) =>
|
||||
context.read<FunctionBloc>().add(SelectFunction(
|
||||
functionCode: functionCode,
|
||||
operationName: operationName,
|
||||
)),
|
||||
device: device,
|
||||
onFunctionSelected: (functionCode, operationName) {
|
||||
RoutineTapFunctionHelper.onTapFunction(
|
||||
context,
|
||||
functionCode: functionCode,
|
||||
functionOperationName: operationName,
|
||||
functionValueDescription:
|
||||
selectedFunctionData.valueDescription,
|
||||
deviceUuid: device?.uuid,
|
||||
codesToAddIntoFunctionsWithDefaultValue: [
|
||||
'temp_set',
|
||||
'temp_current',
|
||||
],
|
||||
defaultValue: functionCode == 'temp_set'
|
||||
? 200
|
||||
: functionCode == 'temp_current'
|
||||
? -100
|
||||
: 0,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
// Value selector
|
||||
@ -137,6 +153,7 @@ class ACHelper {
|
||||
required BuildContext context,
|
||||
required List<ACFunction> acFunctions,
|
||||
required Function(String, String) onFunctionSelected,
|
||||
required AllDevicesModel? device,
|
||||
}) {
|
||||
return ListView.separated(
|
||||
shrinkWrap: false,
|
||||
|
@ -131,7 +131,12 @@ class _CeilingSensorDialogState extends State<CeilingSensorDialog> {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
CpsFunctionsList(cpsFunctions: _cpsFunctions),
|
||||
CpsFunctionsList(
|
||||
cpsFunctions: _cpsFunctions,
|
||||
device: widget.device,
|
||||
selectedFunctionData: selectedFunctionData,
|
||||
dialogType: widget.dialogType,
|
||||
),
|
||||
if (state.selectedFunction != null)
|
||||
Expanded(
|
||||
child: isToggleFunction
|
||||
|
@ -1,14 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialog_function_list_tile.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class CpsFunctionsList extends StatelessWidget {
|
||||
const CpsFunctionsList({required this.cpsFunctions, super.key});
|
||||
const CpsFunctionsList({
|
||||
required this.cpsFunctions,
|
||||
required this.device,
|
||||
required this.selectedFunctionData,
|
||||
required this.dialogType,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final List<CpsFunctions> cpsFunctions;
|
||||
final AllDevicesModel? device;
|
||||
final DeviceFunctionData? selectedFunctionData;
|
||||
final String dialogType;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -26,12 +36,27 @@ class CpsFunctionsList extends StatelessWidget {
|
||||
return RoutineDialogFunctionListTile(
|
||||
iconPath: function.icon,
|
||||
operationName: function.operationName,
|
||||
onTap: () => context.read<FunctionBloc>().add(
|
||||
SelectFunction(
|
||||
functionCode: function.code,
|
||||
operationName: function.operationName,
|
||||
),
|
||||
),
|
||||
onTap: () => RoutineTapFunctionHelper.onTapFunction(
|
||||
context,
|
||||
functionCode: function.code,
|
||||
functionOperationName: function.operationName,
|
||||
functionValueDescription: selectedFunctionData?.valueDescription,
|
||||
deviceUuid: device?.uuid,
|
||||
codesToAddIntoFunctionsWithDefaultValue: [
|
||||
'static_max_dis',
|
||||
'presence_reference',
|
||||
'moving_reference',
|
||||
'perceptual_boundary',
|
||||
'moving_boundary',
|
||||
'moving_rigger_time',
|
||||
'moving_static_time',
|
||||
'none_body_time',
|
||||
'moving_max_dis',
|
||||
'moving_range',
|
||||
'presence_range',
|
||||
if (dialogType == "IF") 'sensitivity',
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
||||
|
||||
abstract final class RoutineTapFunctionHelper {
|
||||
const RoutineTapFunctionHelper._();
|
||||
|
||||
static void onTapFunction(
|
||||
BuildContext context, {
|
||||
required String functionCode,
|
||||
required String functionOperationName,
|
||||
required String? functionValueDescription,
|
||||
required String? deviceUuid,
|
||||
required List<String> codesToAddIntoFunctionsWithDefaultValue,
|
||||
int defaultValue = 0,
|
||||
}) {
|
||||
final functionsBloc = context.read<FunctionBloc>();
|
||||
functionsBloc.add(
|
||||
SelectFunction(
|
||||
functionCode: functionCode,
|
||||
operationName: functionOperationName,
|
||||
),
|
||||
);
|
||||
final addedFunctions = functionsBloc.state.addedFunctions;
|
||||
final isFunctionAlreadyAdded = addedFunctions.any(
|
||||
(e) => e.functionCode == functionCode,
|
||||
);
|
||||
final shouldAddFunction =
|
||||
codesToAddIntoFunctionsWithDefaultValue.contains(functionCode);
|
||||
if (!isFunctionAlreadyAdded && shouldAddFunction) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
functionData: DeviceFunctionData(
|
||||
entityId: deviceUuid ?? '',
|
||||
functionCode: functionCode,
|
||||
operationName: functionOperationName,
|
||||
value: defaultValue,
|
||||
condition: '==',
|
||||
valueDescription: functionValueDescription,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/
|
||||
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
@ -24,21 +25,23 @@ class OneGangSwitchHelper {
|
||||
required String uniqueCustomId,
|
||||
required bool removeComparetors,
|
||||
}) async {
|
||||
List<BaseSwitchFunction> oneGangFunctions = functions.whereType<BaseSwitchFunction>().toList();
|
||||
List<BaseSwitchFunction> oneGangFunctions =
|
||||
functions.whereType<BaseSwitchFunction>().toList();
|
||||
|
||||
return showDialog<Map<String, dynamic>?>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
|
||||
create: (_) => FunctionBloc()
|
||||
..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
|
||||
child: AlertDialog(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
|
||||
builder: (context, state) {
|
||||
final selectedFunction = state.selectedFunction;
|
||||
final selectedOperationName = state.selectedOperationName;
|
||||
final selectedFunctionData =
|
||||
state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction,
|
||||
final selectedFunctionData = state.addedFunctions
|
||||
.firstWhere((f) => f.functionCode == selectedFunction,
|
||||
orElse: () => DeviceFunctionData(
|
||||
entityId: '',
|
||||
functionCode: selectedFunction ?? '',
|
||||
@ -84,12 +87,19 @@ class OneGangSwitchHelper {
|
||||
size: 16,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
onTap: () {
|
||||
context.read<FunctionBloc>().add(SelectFunction(
|
||||
functionCode: function.code,
|
||||
operationName: function.operationName,
|
||||
));
|
||||
},
|
||||
onTap: () =>
|
||||
RoutineTapFunctionHelper.onTapFunction(
|
||||
context,
|
||||
functionCode: function.code,
|
||||
functionOperationName:
|
||||
function.operationName,
|
||||
functionValueDescription:
|
||||
selectedFunctionData.valueDescription,
|
||||
deviceUuid: device?.uuid,
|
||||
codesToAddIntoFunctionsWithDefaultValue: [
|
||||
'countdown_1',
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -220,11 +230,11 @@ class OneGangSwitchHelper {
|
||||
selectedFunctionData,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_buildCountDownDisplay(
|
||||
context, initialValue, device, operationName, selectedFunctionData, selectCode),
|
||||
_buildCountDownDisplay(context, initialValue, device, operationName,
|
||||
selectedFunctionData, selectCode),
|
||||
const SizedBox(height: 20),
|
||||
_buildCountDownSlider(
|
||||
context, initialValue, device, operationName, selectedFunctionData, selectCode),
|
||||
_buildCountDownSlider(context, initialValue, device, operationName,
|
||||
selectedFunctionData, selectCode),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -314,9 +324,10 @@ class OneGangSwitchHelper {
|
||||
value: (initialValue ?? 0).toDouble(),
|
||||
min: operationalValues.minValue?.toDouble() ?? 0.0,
|
||||
max: operationalValues.maxValue?.toDouble() ?? 0.0,
|
||||
divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
|
||||
(operationalValues.stepValue ?? 1))
|
||||
.round(),
|
||||
divisions:
|
||||
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
|
||||
(operationalValues.stepValue ?? 1))
|
||||
.round(),
|
||||
onChanged: (value) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
@ -368,7 +379,9 @@ class OneGangSwitchHelper {
|
||||
trailing: Icon(
|
||||
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
|
||||
size: 24,
|
||||
color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray,
|
||||
color: isSelected
|
||||
? ColorsManager.primaryColorWithOpacity
|
||||
: ColorsManager.textGray,
|
||||
),
|
||||
onTap: () {
|
||||
if (!isSelected) {
|
||||
|
@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_func
|
||||
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
@ -85,15 +86,21 @@ class ThreeGangSwitchHelper {
|
||||
size: 16,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
onTap: () {
|
||||
context
|
||||
.read<FunctionBloc>()
|
||||
.add(SelectFunction(
|
||||
functionCode: function.code,
|
||||
operationName:
|
||||
function.operationName,
|
||||
));
|
||||
},
|
||||
onTap: () =>
|
||||
RoutineTapFunctionHelper.onTapFunction(
|
||||
context,
|
||||
functionCode: function.code,
|
||||
functionOperationName:
|
||||
function.operationName,
|
||||
functionValueDescription:
|
||||
selectedFunctionData.valueDescription,
|
||||
deviceUuid: device?.uuid,
|
||||
codesToAddIntoFunctionsWithDefaultValue: [
|
||||
'countdown_1',
|
||||
'countdown_2',
|
||||
'countdown_3',
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -183,8 +190,7 @@ class ThreeGangSwitchHelper {
|
||||
);
|
||||
}
|
||||
|
||||
final selectedFn =
|
||||
switchFunctions.firstWhere((f) => f.code == selectedFunction);
|
||||
final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
|
||||
final values = selectedFn.getOperationalValues();
|
||||
|
||||
return _buildOperationalValuesList(
|
||||
@ -266,8 +272,7 @@ class ThreeGangSwitchHelper {
|
||||
minHeight: 40.0,
|
||||
minWidth: 40.0,
|
||||
),
|
||||
isSelected:
|
||||
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
|
||||
isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
|
||||
children: conditions.map((c) => Text(c)).toList(),
|
||||
);
|
||||
}
|
||||
@ -316,10 +321,10 @@ class ThreeGangSwitchHelper {
|
||||
value: (initialValue ?? 0).toDouble(),
|
||||
min: operationalValues.minValue?.toDouble() ?? 0.0,
|
||||
max: operationalValues.maxValue?.toDouble() ?? 0.0,
|
||||
divisions: (((operationalValues.maxValue ?? 0) -
|
||||
(operationalValues.minValue ?? 0)) /
|
||||
(operationalValues.stepValue ?? 1))
|
||||
.round(),
|
||||
divisions:
|
||||
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
|
||||
(operationalValues.stepValue ?? 1))
|
||||
.round(),
|
||||
onChanged: (value) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
@ -369,9 +374,7 @@ class ThreeGangSwitchHelper {
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
trailing: Icon(
|
||||
isSelected
|
||||
? Icons.radio_button_checked
|
||||
: Icons.radio_button_unchecked,
|
||||
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
|
||||
size: 24,
|
||||
color: isSelected
|
||||
? ColorsManager.primaryColorWithOpacity
|
||||
@ -387,8 +390,7 @@ class ThreeGangSwitchHelper {
|
||||
operationName: operationName,
|
||||
value: value.value,
|
||||
condition: selectedFunctionData?.condition,
|
||||
valueDescription:
|
||||
selectedFunctionData?.valueDescription,
|
||||
valueDescription: selectedFunctionData?.valueDescription,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_func
|
||||
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
@ -85,15 +86,20 @@ class TwoGangSwitchHelper {
|
||||
size: 16,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
onTap: () {
|
||||
context
|
||||
.read<FunctionBloc>()
|
||||
.add(SelectFunction(
|
||||
functionCode: function.code,
|
||||
operationName:
|
||||
function.operationName,
|
||||
));
|
||||
},
|
||||
onTap: () =>
|
||||
RoutineTapFunctionHelper.onTapFunction(
|
||||
context,
|
||||
functionCode: function.code,
|
||||
functionOperationName:
|
||||
function.operationName,
|
||||
functionValueDescription:
|
||||
selectedFunctionData.valueDescription,
|
||||
deviceUuid: device?.uuid,
|
||||
codesToAddIntoFunctionsWithDefaultValue: [
|
||||
'countdown_1',
|
||||
'countdown_2',
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -167,8 +173,7 @@ class TwoGangSwitchHelper {
|
||||
required String operationName,
|
||||
required bool removeComparetors,
|
||||
}) {
|
||||
if (selectedFunction == 'countdown_1' ||
|
||||
selectedFunction == 'countdown_2') {
|
||||
if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2') {
|
||||
final initialValue = selectedFunctionData?.value ?? 0;
|
||||
return _buildTemperatureSelector(
|
||||
context: context,
|
||||
@ -182,8 +187,7 @@ class TwoGangSwitchHelper {
|
||||
);
|
||||
}
|
||||
|
||||
final selectedFn =
|
||||
switchFunctions.firstWhere((f) => f.code == selectedFunction);
|
||||
final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
|
||||
final values = selectedFn.getOperationalValues();
|
||||
|
||||
return _buildOperationalValuesList(
|
||||
@ -265,8 +269,7 @@ class TwoGangSwitchHelper {
|
||||
minHeight: 40.0,
|
||||
minWidth: 40.0,
|
||||
),
|
||||
isSelected:
|
||||
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
|
||||
isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
|
||||
children: conditions.map((c) => Text(c)).toList(),
|
||||
);
|
||||
}
|
||||
@ -315,10 +318,10 @@ class TwoGangSwitchHelper {
|
||||
value: (initialValue ?? 0).toDouble(),
|
||||
min: operationalValues.minValue?.toDouble() ?? 0.0,
|
||||
max: operationalValues.maxValue?.toDouble() ?? 0.0,
|
||||
divisions: (((operationalValues.maxValue ?? 0) -
|
||||
(operationalValues.minValue ?? 0)) /
|
||||
(operationalValues.stepValue ?? 1))
|
||||
.round(),
|
||||
divisions:
|
||||
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
|
||||
(operationalValues.stepValue ?? 1))
|
||||
.round(),
|
||||
onChanged: (value) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
@ -368,9 +371,7 @@ class TwoGangSwitchHelper {
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
trailing: Icon(
|
||||
isSelected
|
||||
? Icons.radio_button_checked
|
||||
: Icons.radio_button_unchecked,
|
||||
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
|
||||
size: 24,
|
||||
color: isSelected
|
||||
? ColorsManager.primaryColorWithOpacity
|
||||
@ -386,8 +387,7 @@ class TwoGangSwitchHelper {
|
||||
operationName: operationName,
|
||||
value: value.value,
|
||||
condition: selectedFunctionData?.condition,
|
||||
valueDescription:
|
||||
selectedFunctionData?.valueDescription,
|
||||
valueDescription: selectedFunctionData?.valueDescription,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
@ -111,13 +112,23 @@ class _WallPresenceSensorState extends State<WallPresenceSensor> {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildFunctionList(context),
|
||||
_buildFunctionList(context, state),
|
||||
if (state.selectedFunction != null) _buildValueSelector(context, state),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFunctionList(BuildContext context) {
|
||||
Widget _buildFunctionList(BuildContext context, FunctionBlocState state) {
|
||||
final selectedFunction = state.selectedFunction;
|
||||
final selectedFunctionData = state.addedFunctions.firstWhere(
|
||||
(f) => f.functionCode == selectedFunction,
|
||||
orElse: () => DeviceFunctionData(
|
||||
entityId: '',
|
||||
functionCode: selectedFunction ?? '',
|
||||
operationName: '',
|
||||
value: null,
|
||||
),
|
||||
);
|
||||
return SizedBox(
|
||||
width: 360,
|
||||
child: ListView.separated(
|
||||
@ -149,12 +160,18 @@ class _WallPresenceSensorState extends State<WallPresenceSensor> {
|
||||
size: 16,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
onTap: () => context.read<FunctionBloc>().add(
|
||||
SelectFunction(
|
||||
functionCode: function.code,
|
||||
operationName: function.operationName,
|
||||
),
|
||||
),
|
||||
onTap: () => RoutineTapFunctionHelper.onTapFunction(
|
||||
context,
|
||||
functionCode: function.code,
|
||||
functionOperationName: function.operationName,
|
||||
functionValueDescription: selectedFunctionData.valueDescription,
|
||||
deviceUuid: widget.device?.uuid,
|
||||
codesToAddIntoFunctionsWithDefaultValue: [
|
||||
'dis_current',
|
||||
'presence_time',
|
||||
'illuminance_value',
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||
@ -246,7 +247,9 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
final previousState = state;
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
var spaceBloc = event.context.read<SpaceTreeBloc>();
|
||||
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc);
|
||||
var spaceTreeState = event.context.read<SpaceTreeBloc>().state;
|
||||
|
||||
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc, spaceTreeState);
|
||||
await fetchSpaceModels();
|
||||
await fetchTags();
|
||||
|
||||
@ -277,11 +280,13 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
LoadCommunityAndSpacesEvent event,
|
||||
Emitter<SpaceManagementState> emit,
|
||||
) async {
|
||||
var spaceTreeState = event.context.read<SpaceTreeBloc>().state;
|
||||
var spaceBloc = event.context.read<SpaceTreeBloc>();
|
||||
|
||||
_onloadProducts();
|
||||
await fetchTags();
|
||||
// Wait until `communityList` is loaded
|
||||
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc);
|
||||
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc, spaceTreeState);
|
||||
|
||||
// Fetch space models after communities are available
|
||||
final prevSpaceModels = await fetchSpaceModels();
|
||||
@ -292,23 +297,38 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
allTags: _cachedTags ?? []));
|
||||
}
|
||||
|
||||
Future<List<CommunityModel>> _waitForCommunityList(SpaceTreeBloc spaceBloc) async {
|
||||
Future<List<CommunityModel>> _waitForCommunityList(
|
||||
SpaceTreeBloc spaceBloc, SpaceTreeState spaceTreeState) async {
|
||||
// Check if communityList is already populated
|
||||
if (spaceBloc.state.communityList.isNotEmpty) {
|
||||
return spaceBloc.state.communityList;
|
||||
final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
|
||||
? spaceTreeState.filteredCommunity
|
||||
: spaceTreeState.communityList;
|
||||
if (filteredCommunities.isNotEmpty) {
|
||||
return filteredCommunities;
|
||||
}
|
||||
|
||||
final completer = Completer<List<CommunityModel>>();
|
||||
final subscription = spaceBloc.stream.listen((state) {
|
||||
if (state.communityList.isNotEmpty) {
|
||||
completer.complete(state.communityList);
|
||||
if (!completer.isCompleted && state.communityList.isNotEmpty) {
|
||||
completer
|
||||
.complete(state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList);
|
||||
}
|
||||
});
|
||||
try {
|
||||
final communities = await completer.future.timeout(
|
||||
const Duration(seconds: 10),
|
||||
onTimeout: () {
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete([]);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
);
|
||||
|
||||
// Return the list once available, then cancel the listener
|
||||
final communities = await completer.future;
|
||||
await subscription.cancel();
|
||||
return communities;
|
||||
return communities;
|
||||
} finally {
|
||||
await subscription.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void _onCommunityDelete(
|
||||
@ -446,6 +466,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
|
||||
if (previousState is SpaceManagementLoaded) {
|
||||
await _updateLoadedState(
|
||||
event.context,
|
||||
previousState,
|
||||
allSpaces,
|
||||
event.communityUuid,
|
||||
@ -462,6 +483,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
}
|
||||
|
||||
Future<void> _updateLoadedState(
|
||||
BuildContext context,
|
||||
SpaceManagementLoaded previousState,
|
||||
List<SpaceModel> allSpaces,
|
||||
String communityUuid,
|
||||
@ -469,7 +491,10 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
) async {
|
||||
var prevSpaceModels = await fetchSpaceModels();
|
||||
await fetchTags();
|
||||
final communities = List<CommunityModel>.from(previousState.communities);
|
||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||
final communities = spaceTreeState.searchQuery.isNotEmpty
|
||||
? spaceTreeState.filteredCommunity
|
||||
: spaceTreeState.communityList;
|
||||
|
||||
for (var community in communities) {
|
||||
if (community.uuid == communityUuid) {
|
||||
@ -484,6 +509,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
spaceModels: prevSpaceModels,
|
||||
allTags: _cachedTags ?? []));
|
||||
return;
|
||||
} else {
|
||||
print("Community not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -491,12 +518,21 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
Future<List<SpaceModel>> saveSpacesHierarchically(
|
||||
BuildContext context, List<SpaceModel> spaces, String communityUuid) async {
|
||||
final orderedSpaces = flattenHierarchy(spaces);
|
||||
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||
List<CommunityModel> communities = spaceBloc.state.communityList;
|
||||
CommunityModel? selectedCommunity = communities.firstWhere(
|
||||
(community) => community.uuid == communityUuid,
|
||||
);
|
||||
CommunityModel? selectedCommunity;
|
||||
try {
|
||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||
final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
|
||||
? spaceTreeState.filteredCommunity
|
||||
: spaceTreeState.communityList;
|
||||
|
||||
selectedCommunity = filteredCommunities.firstWhere(
|
||||
(community) => community.uuid == communityUuid,
|
||||
);
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final parentsToDelete = orderedSpaces.where((space) =>
|
||||
space.status == SpaceStatus.deleted &&
|
||||
@ -605,11 +641,11 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
projectId: projectUuid);
|
||||
} else {
|
||||
// Call create if the space does not have a UUID
|
||||
final List<CreateTagBodyModel> tagBodyModels = space.tags != null
|
||||
List<CreateTagBodyModel> tagBodyModels = space.tags != null
|
||||
? space.tags!.map((tag) => tag.toCreateTagBodyModel()).toList()
|
||||
: [];
|
||||
|
||||
final createSubspaceBodyModels = space.subspaces?.map((subspace) {
|
||||
var createSubspaceBodyModels = space.subspaces?.map((subspace) {
|
||||
final tagBodyModels =
|
||||
subspace.tags?.map((tag) => tag.toCreateTagBodyModel()).toList() ?? [];
|
||||
return CreateSubspaceModel()
|
||||
@ -618,6 +654,11 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
}).toList() ??
|
||||
[];
|
||||
|
||||
if (space.spaceModel?.uuid != null) {
|
||||
tagBodyModels = [];
|
||||
createSubspaceBodyModels = [];
|
||||
}
|
||||
|
||||
final response = await _api.createSpace(
|
||||
communityId: communityUuid,
|
||||
name: space.name,
|
||||
@ -669,9 +710,12 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
|
||||
try {
|
||||
await fetchTags();
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
var spaceBloc = event.context.read<SpaceTreeBloc>();
|
||||
List<CommunityModel> communities = spaceBloc.state.communityList;
|
||||
final spaceTreeState = event.context.read<SpaceTreeBloc>().state;
|
||||
final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
|
||||
? spaceTreeState.filteredCommunity
|
||||
: spaceTreeState.communityList;
|
||||
|
||||
List<CommunityModel> communities = filteredCommunities;
|
||||
|
||||
var prevSpaceModels = await fetchSpaceModels();
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
// Flutter imports
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@ -70,6 +72,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
_nameController = TextEditingController(
|
||||
text: widget.selectedCommunity?.name ?? '',
|
||||
);
|
||||
realignTree();
|
||||
|
||||
_transformationController = TransformationController();
|
||||
if (widget.selectedSpace != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
@ -94,6 +98,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
|
||||
connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
|
||||
_adjustCanvasSizeForSpaces();
|
||||
realignTree();
|
||||
});
|
||||
}
|
||||
|
||||
@ -336,6 +341,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
}
|
||||
spaces.add(newSpace);
|
||||
_updateNodePosition(newSpace, newSpace.position);
|
||||
realignTree();
|
||||
});
|
||||
},
|
||||
);
|
||||
@ -401,21 +407,31 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
|
||||
List<SpaceModel> flattenSpaces(List<SpaceModel> spaces) {
|
||||
List<SpaceModel> result = [];
|
||||
Map<String, SpaceModel> idToSpace = {};
|
||||
|
||||
void flatten(SpaceModel space) {
|
||||
if (space.status == SpaceStatus.deleted || space.status == SpaceStatus.parentDeleted) {
|
||||
return;
|
||||
}
|
||||
result.add(space);
|
||||
idToSpace[space.internalId] = space;
|
||||
|
||||
for (var child in space.children) {
|
||||
flatten(child);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (var space in spaces) {
|
||||
flatten(space);
|
||||
}
|
||||
|
||||
for (var space in result) {
|
||||
if (space.parent != null) {
|
||||
space.parent = idToSpace[space.parent!.internalId];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -450,7 +466,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
|
||||
void _saveSpaces() {
|
||||
if (widget.selectedCommunity == null) {
|
||||
debugPrint("No community selected for saving spaces.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -465,7 +480,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
}
|
||||
|
||||
String communityUuid = widget.selectedCommunity!.uuid;
|
||||
|
||||
context.read<SpaceManagementBloc>().add(SaveSpacesEvent(
|
||||
context,
|
||||
spaces: spacesToSave,
|
||||
@ -530,35 +544,83 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
}
|
||||
|
||||
Offset getBalancedChildPosition(SpaceModel parent) {
|
||||
int totalSiblings = parent.children.length + 1;
|
||||
double totalWidth = (totalSiblings - 1) * 250; // Horizontal spacing
|
||||
double startX = parent.position.dx - (totalWidth / 2);
|
||||
const double nodeWidth = 200;
|
||||
const double verticalGap = 180;
|
||||
|
||||
Offset position = Offset(startX + (parent.children.length * 250), parent.position.dy + 180);
|
||||
if (parent.children.isEmpty) {
|
||||
// First child → exactly center
|
||||
return Offset(parent.position.dx, parent.position.dy + verticalGap);
|
||||
} else {
|
||||
// More children → arrange them spaced horizontally
|
||||
double totalWidth = (parent.children.length) * (nodeWidth + 60);
|
||||
double startX = parent.position.dx - (totalWidth / 2);
|
||||
|
||||
// Check for overlaps & adjust
|
||||
while (spaces.any((s) => (s.position - position).distance < 250)) {
|
||||
position = Offset(position.dx + 250, position.dy);
|
||||
double childX = startX + (parent.children.length * (nodeWidth + 60));
|
||||
return Offset(childX, parent.position.dy + verticalGap);
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
void realignTree() {
|
||||
void updatePositions(SpaceModel node, double x, double y) {
|
||||
node.position = Offset(x, y);
|
||||
const double nodeWidth = 200;
|
||||
const double nodeHeight = 100;
|
||||
const double horizontalGap = 60;
|
||||
const double verticalGap = 180;
|
||||
const double rootGap = 400; // extra space between different roots
|
||||
|
||||
int numChildren = node.children.length;
|
||||
double childStartX = x - ((numChildren - 1) * 250) / 2;
|
||||
double canvasRightEdge = 1000;
|
||||
double canvasBottomEdge = 1000;
|
||||
|
||||
for (int i = 0; i < numChildren; i++) {
|
||||
updatePositions(node.children[i], childStartX + (i * 250), y + 180);
|
||||
double calculateSubtreeWidth(SpaceModel node) {
|
||||
if (node.children.isEmpty) return nodeWidth;
|
||||
double totalWidth = 0;
|
||||
for (var child in node.children) {
|
||||
totalWidth += calculateSubtreeWidth(child) + horizontalGap;
|
||||
}
|
||||
return totalWidth - horizontalGap;
|
||||
}
|
||||
|
||||
void layoutSubtree(SpaceModel node, double startX, double y) {
|
||||
double subtreeWidth = calculateSubtreeWidth(node);
|
||||
double centerX = startX + subtreeWidth / 2 - nodeWidth / 2;
|
||||
node.position = Offset(centerX, y);
|
||||
|
||||
canvasRightEdge = max(canvasRightEdge, centerX + nodeWidth);
|
||||
canvasBottomEdge = max(canvasBottomEdge, y + nodeHeight);
|
||||
|
||||
if (node.children.length == 1) {
|
||||
final child = node.children.first;
|
||||
layoutSubtree(child, centerX, y + verticalGap);
|
||||
} else {
|
||||
double childX = startX;
|
||||
for (var child in node.children) {
|
||||
double childWidth = calculateSubtreeWidth(child);
|
||||
layoutSubtree(child, childX, y + verticalGap);
|
||||
childX += childWidth + horizontalGap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (spaces.isNotEmpty) {
|
||||
updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy);
|
||||
// ⚡ New: layout each root separately
|
||||
final List<SpaceModel> roots = spaces
|
||||
.where((s) =>
|
||||
s.parent == null &&
|
||||
s.status != SpaceStatus.deleted &&
|
||||
s.status != SpaceStatus.parentDeleted)
|
||||
.toList();
|
||||
|
||||
double currentX = 100; // start some margin from left
|
||||
double currentY = 100; // top margin
|
||||
|
||||
for (var root in roots) {
|
||||
layoutSubtree(root, currentX, currentY);
|
||||
double rootWidth = calculateSubtreeWidth(root);
|
||||
currentX += rootWidth + rootGap;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
canvasWidth = canvasRightEdge + 400;
|
||||
canvasHeight = canvasBottomEdge + 400;
|
||||
});
|
||||
}
|
||||
|
||||
void _onDuplicate(BuildContext parentContext) {
|
||||
@ -642,63 +704,19 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
}
|
||||
|
||||
void _duplicateSpace(SpaceModel space) {
|
||||
final Map<SpaceModel, SpaceModel> originalToDuplicate = {};
|
||||
double horizontalGap = 250.0; // Increased spacing
|
||||
double verticalGap = 180.0; // Adjusted for better visualization
|
||||
final double horizontalGap = 250.0;
|
||||
final double verticalGap = 180.0;
|
||||
final double nodeWidth = 200;
|
||||
final double nodeHeight = 100;
|
||||
final double breathingSpace = 300.0;
|
||||
|
||||
print("🟢 Duplicating: ${space.name}");
|
||||
|
||||
/// **Find a new position ensuring no overlap**
|
||||
Offset getBalancedChildPosition(SpaceModel parent) {
|
||||
int totalSiblings = parent.children.length + 1;
|
||||
double totalWidth = (totalSiblings - 1) * horizontalGap;
|
||||
double startX = parent.position.dx - (totalWidth / 2);
|
||||
Offset position = Offset(
|
||||
startX + (parent.children.length * horizontalGap), parent.position.dy + verticalGap);
|
||||
|
||||
// **Check for overlaps & adjust**
|
||||
while (spaces.any((s) => (s.position - position).distance < horizontalGap)) {
|
||||
position = Offset(position.dx + horizontalGap, position.dy);
|
||||
}
|
||||
|
||||
print("🔹 New position for ${parent.name}: (${position.dx}, ${position.dy})");
|
||||
return position;
|
||||
}
|
||||
|
||||
/// **Realign the entire tree after duplication**
|
||||
void realignTree() {
|
||||
void updatePositions(SpaceModel node, double x, double y) {
|
||||
node.position = Offset(x, y);
|
||||
print("✅ Adjusted ${node.name} to (${x}, ${y})");
|
||||
|
||||
int numChildren = node.children.length;
|
||||
double childStartX = x - ((numChildren - 1) * horizontalGap) / 2;
|
||||
|
||||
for (int i = 0; i < numChildren; i++) {
|
||||
updatePositions(node.children[i], childStartX + (i * horizontalGap), y + verticalGap);
|
||||
}
|
||||
}
|
||||
|
||||
if (spaces.isNotEmpty) {
|
||||
print("🔄 Realigning tree...");
|
||||
updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy);
|
||||
}
|
||||
}
|
||||
|
||||
/// **Recursive duplication logic**
|
||||
/// Helper to recursively duplicate a node and its children
|
||||
SpaceModel duplicateRecursive(SpaceModel original, SpaceModel? duplicatedParent) {
|
||||
Offset newPosition = duplicatedParent == null
|
||||
? Offset(original.position.dx + horizontalGap, original.position.dy)
|
||||
: getBalancedChildPosition(duplicatedParent);
|
||||
|
||||
final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces);
|
||||
print(
|
||||
"🟡 Duplicating ${original.name} → ${duplicatedName} at (${newPosition.dx}, ${newPosition.dy})");
|
||||
|
||||
final duplicated = SpaceModel(
|
||||
name: duplicatedName,
|
||||
icon: original.icon,
|
||||
position: newPosition,
|
||||
position: original.position,
|
||||
isPrivate: original.isPrivate,
|
||||
children: [],
|
||||
status: SpaceStatus.newSpace,
|
||||
@ -708,28 +726,22 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
tags: original.tags,
|
||||
);
|
||||
|
||||
setState(() {
|
||||
spaces.add(duplicated);
|
||||
_updateNodePosition(duplicated, duplicated.position);
|
||||
spaces.add(duplicated);
|
||||
|
||||
if (duplicatedParent != null) {
|
||||
final newConnection = Connection(
|
||||
startSpace: duplicatedParent,
|
||||
endSpace: duplicated,
|
||||
direction: "down",
|
||||
);
|
||||
connections.add(newConnection);
|
||||
duplicated.incomingConnection = newConnection;
|
||||
duplicatedParent.addOutgoingConnection(newConnection);
|
||||
duplicatedParent.children.add(duplicated);
|
||||
print("🔗 Created connection: ${duplicatedParent.name} → ${duplicated.name}");
|
||||
}
|
||||
if (duplicatedParent != null) {
|
||||
final newConnection = Connection(
|
||||
startSpace: duplicatedParent,
|
||||
endSpace: duplicated,
|
||||
direction: "down",
|
||||
);
|
||||
connections.add(newConnection);
|
||||
|
||||
// **Recalculate the whole tree to avoid overlaps**
|
||||
realignTree();
|
||||
});
|
||||
duplicated.incomingConnection = newConnection;
|
||||
duplicatedParent.addOutgoingConnection(newConnection);
|
||||
duplicatedParent.children.add(duplicated);
|
||||
}
|
||||
|
||||
// Recursively duplicate children
|
||||
// Duplicate its children recursively
|
||||
for (var child in original.children) {
|
||||
duplicateRecursive(child, duplicated);
|
||||
}
|
||||
@ -737,21 +749,30 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
return duplicated;
|
||||
}
|
||||
|
||||
/// **Handle root duplication**
|
||||
if (space.parent == null) {
|
||||
print("🟠 Duplicating root node: ${space.name}");
|
||||
SpaceModel duplicatedRoot = duplicateRecursive(space, null);
|
||||
|
||||
setState(() {
|
||||
spaces.add(duplicatedRoot);
|
||||
/// Actual duplication process
|
||||
setState(() {
|
||||
if (space.parent == null) {
|
||||
// Duplicating a ROOT node
|
||||
duplicateRecursive(space, null);
|
||||
connections = createConnections(spaces);
|
||||
realignTree();
|
||||
});
|
||||
} else {
|
||||
final parent = space.parent!;
|
||||
|
||||
print("✅ Root duplication successful: ${duplicatedRoot.name}");
|
||||
} else {
|
||||
duplicateRecursive(space, space.parent);
|
||||
}
|
||||
|
||||
print("🟢 Finished duplication process for: ${space.name}");
|
||||
final duplicated = duplicateRecursive(space, parent);
|
||||
final newConnection = Connection(
|
||||
startSpace: parent,
|
||||
endSpace: duplicated,
|
||||
direction: "down",
|
||||
);
|
||||
connections.add(newConnection);
|
||||
duplicated.incomingConnection = newConnection;
|
||||
parent.addOutgoingConnection(newConnection);
|
||||
parent.children.add(duplicated);
|
||||
connections = createConnections(spaces);
|
||||
realignTree();
|
||||
//_realignSubtree(space.parent!);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
|
||||
@ -45,7 +46,6 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_spaceModels = List.from(widget.spaceModels ?? []);
|
||||
}
|
||||
|
||||
@ -106,9 +106,8 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
|
||||
children: [
|
||||
SidebarWidget(
|
||||
communities: widget.communities,
|
||||
selectedSpaceUuid: widget.selectedSpace?.uuid ??
|
||||
widget.selectedCommunity?.uuid ??
|
||||
'',
|
||||
selectedSpaceUuid:
|
||||
widget.selectedSpace?.uuid ?? widget.selectedCommunity?.uuid ?? '',
|
||||
onCreateCommunity: (name, description) {
|
||||
context.read<SpaceManagementBloc>().add(
|
||||
CreateCommunityEvent(name, description, context),
|
||||
|
@ -1,8 +1,11 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/common/widgets/empty_search_result_widget.dart';
|
||||
import 'package:syncrow_web/common/widgets/search_bar.dart';
|
||||
import 'package:syncrow_web/common/widgets/sidebar_communities_list.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
||||
@ -15,6 +18,8 @@ import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/cent
|
||||
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
import '../../../space_tree/bloc/space_tree_event.dart';
|
||||
|
||||
class SidebarWidget extends StatefulWidget {
|
||||
final List<CommunityModel> communities;
|
||||
final String? selectedSpaceUuid;
|
||||
@ -33,6 +38,7 @@ class SidebarWidget extends StatefulWidget {
|
||||
|
||||
class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
late final ScrollController _scrollController;
|
||||
Timer? _debounce;
|
||||
|
||||
String _searchQuery = '';
|
||||
String? _selectedSpaceUuid;
|
||||
@ -40,17 +46,48 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_selectedId = widget.selectedSpaceUuid;
|
||||
_scrollController = ScrollController();
|
||||
super.initState();
|
||||
_scrollController = ScrollController();
|
||||
_scrollController.addListener(_onScroll);
|
||||
_selectedId = widget.selectedSpaceUuid;
|
||||
_searchQuery = '';
|
||||
context.read<SpaceTreeBloc>().add(ClearCachedData());
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 100) {
|
||||
// Trigger pagination event
|
||||
final bloc = context.read<SpaceTreeBloc>();
|
||||
if (!bloc.state.paginationIsLoading && bloc.state.paginationModel?.hasNext == true) {
|
||||
bloc.add(
|
||||
PaginationEvent(
|
||||
bloc.state.paginationModel!,
|
||||
bloc.state.communityList,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.removeListener(_onScroll);
|
||||
_scrollController.dispose();
|
||||
_debounce?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onSearchChanged(String query) {
|
||||
if (_debounce?.isActive ?? false) _debounce?.cancel();
|
||||
|
||||
_debounce = Timer(const Duration(milliseconds: 500), () {
|
||||
setState(() {
|
||||
_searchQuery = query;
|
||||
});
|
||||
context.read<SpaceTreeBloc>().add(SearchQueryEvent(query));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant SidebarWidget oldWidget) {
|
||||
if (widget.selectedSpaceUuid != oldWidget.selectedSpaceUuid) {
|
||||
@ -59,38 +96,6 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
List<CommunityModel> _filteredCommunities() {
|
||||
if (_searchQuery.isEmpty) {
|
||||
_selectedSpaceUuid = null;
|
||||
return widget.communities;
|
||||
}
|
||||
|
||||
return widget.communities.where((community) {
|
||||
final containsQueryInCommunity =
|
||||
community.name.toLowerCase().contains(_searchQuery.toLowerCase());
|
||||
final containsQueryInSpaces = community.spaces.any((space) =>
|
||||
_containsQuery(space: space, query: _searchQuery.toLowerCase()));
|
||||
|
||||
return containsQueryInCommunity || containsQueryInSpaces;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
bool _containsQuery({
|
||||
required SpaceModel space,
|
||||
required String query,
|
||||
}) {
|
||||
final matchesSpace = space.name.toLowerCase().contains(query);
|
||||
final matchesChildren = space.children.any(
|
||||
(child) => _containsQuery(space: child, query: query),
|
||||
);
|
||||
|
||||
if (matchesSpace || matchesChildren) {
|
||||
_selectedSpaceUuid = space.uuid;
|
||||
}
|
||||
|
||||
return matchesSpace || matchesChildren;
|
||||
}
|
||||
|
||||
bool _isSpaceOrChildSelected(SpaceModel space) {
|
||||
final isSpaceSelected = _selectedSpaceUuid == space.uuid;
|
||||
final anySubSpaceIsSelected = space.children.any(_isSpaceOrChildSelected);
|
||||
@ -101,7 +106,10 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final filteredCommunities = _filteredCommunities();
|
||||
final spaceTreeState = context.watch<SpaceTreeBloc>().state;
|
||||
final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
|
||||
? spaceTreeState.filteredCommunity
|
||||
: spaceTreeState.communityList;
|
||||
|
||||
return Container(
|
||||
width: _width,
|
||||
@ -112,7 +120,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
children: [
|
||||
SidebarHeader(onAddCommunity: _onAddCommunity),
|
||||
CustomSearchBar(
|
||||
onSearchChanged: (query) => setState(() => _searchQuery = query),
|
||||
onSearchChanged: _onSearchChanged,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
@ -120,14 +128,23 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
visible: filteredCommunities.isNotEmpty,
|
||||
replacement: const EmptySearchResultWidget(),
|
||||
child: SidebarCommunitiesList(
|
||||
scrollController: _scrollController,
|
||||
onScrollToEnd: () {},
|
||||
communities: filteredCommunities,
|
||||
itemBuilder: (context, index) => _buildCommunityTile(
|
||||
context,
|
||||
filteredCommunities[index],
|
||||
),
|
||||
),
|
||||
scrollController: _scrollController,
|
||||
onScrollToEnd: () {},
|
||||
communities: filteredCommunities,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == filteredCommunities.length) {
|
||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||
if (spaceTreeState.paginationIsLoading) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
return _buildCommunityTile(context, filteredCommunities[index]);
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -205,9 +222,8 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
void _onAddCommunity() => _selectedId?.isNotEmpty ?? true
|
||||
? _clearSelection()
|
||||
: _showCreateCommunityDialog();
|
||||
void _onAddCommunity() =>
|
||||
_selectedId?.isNotEmpty ?? true ? _clearSelection() : _showCreateCommunityDialog();
|
||||
|
||||
void _clearSelection() {
|
||||
setState(() => _selectedId = '');
|
||||
|
@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart';
|
||||
@ -27,6 +29,8 @@ class SpaceModelPage extends StatelessWidget {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is SpaceModelLoaded) {
|
||||
final spaceModels = state.spaceModels;
|
||||
context.read<SpaceTreeBloc>().add(ClearCachedData());
|
||||
|
||||
final allTagValues = _getAllTagValues(spaceModels);
|
||||
final allSpaceModelNames = _getAllSpaceModelName(spaceModels);
|
||||
|
||||
|
@ -46,14 +46,14 @@ final class DebouncedBatchControlDevicesService
|
||||
final BatchControlDevicesService decoratee;
|
||||
final Duration debounceDuration;
|
||||
|
||||
final _pendingRequests = <(List<String> uuids, String code, Object value)>[];
|
||||
var _isProcessing = false;
|
||||
|
||||
DebouncedBatchControlDevicesService({
|
||||
required this.decoratee,
|
||||
this.debounceDuration = const Duration(milliseconds: 800),
|
||||
this.debounceDuration = const Duration(milliseconds: 1500),
|
||||
});
|
||||
|
||||
final _pendingRequests = <(List<String> uuids, String code, Object value)>[];
|
||||
var _isProcessing = false;
|
||||
|
||||
@override
|
||||
Future<bool> batchControlDevices({
|
||||
required List<String> uuids,
|
||||
@ -68,16 +68,26 @@ final class DebouncedBatchControlDevicesService
|
||||
|
||||
await Future.delayed(debounceDuration);
|
||||
|
||||
final lastRequest = _pendingRequests.last;
|
||||
final groupedRequests =
|
||||
<String, (List<String> uuids, String code, Object value)>{};
|
||||
for (final request in _pendingRequests) {
|
||||
final (_, requestCode, requestValue) = request;
|
||||
groupedRequests[requestCode] = request;
|
||||
}
|
||||
_pendingRequests.clear();
|
||||
|
||||
try {
|
||||
final (lastRequestUuids, lastRequestCode, lastRequestValue) = lastRequest;
|
||||
return decoratee.batchControlDevices(
|
||||
uuids: lastRequestUuids,
|
||||
code: lastRequestCode,
|
||||
value: lastRequestValue,
|
||||
);
|
||||
var allSuccessful = true;
|
||||
for (final request in groupedRequests.values) {
|
||||
final (lastRequestUuids, lastRequestCode, lastRequestValue) = request;
|
||||
final success = await decoratee.batchControlDevices(
|
||||
uuids: lastRequestUuids,
|
||||
code: lastRequestCode,
|
||||
value: lastRequestValue,
|
||||
);
|
||||
if (!success) allSuccessful = false;
|
||||
}
|
||||
return allSuccessful;
|
||||
} finally {
|
||||
_isProcessing = false;
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ final class DebouncedControlDeviceService implements ControlDeviceService {
|
||||
|
||||
DebouncedControlDeviceService({
|
||||
required this.decoratee,
|
||||
this.debounceDuration = const Duration(milliseconds: 800),
|
||||
this.debounceDuration = const Duration(milliseconds: 1500),
|
||||
});
|
||||
|
||||
final _pendingRequests = <(String deviceUuid, Status status)>[];
|
||||
@ -59,15 +59,24 @@ final class DebouncedControlDeviceService implements ControlDeviceService {
|
||||
|
||||
await Future.delayed(debounceDuration);
|
||||
|
||||
final lastRequest = _pendingRequests.last;
|
||||
final groupedRequests = <String, (String deviceUuid, Status status)>{};
|
||||
for (final request in _pendingRequests) {
|
||||
final (_, requestStatus) = request;
|
||||
groupedRequests[requestStatus.code] = request;
|
||||
}
|
||||
_pendingRequests.clear();
|
||||
|
||||
try {
|
||||
final (lastRequestDeviceUuid, lastRequestStatus) = lastRequest;
|
||||
return decoratee.controlDevice(
|
||||
deviceUuid: lastRequestDeviceUuid,
|
||||
status: lastRequestStatus,
|
||||
);
|
||||
var allSuccessful = true;
|
||||
for (final request in groupedRequests.values) {
|
||||
final (lastRequestDeviceUuid, lastRequestStatus) = request;
|
||||
final success = await decoratee.controlDevice(
|
||||
deviceUuid: lastRequestDeviceUuid,
|
||||
status: lastRequestStatus,
|
||||
);
|
||||
if (!success) allSuccessful = false;
|
||||
}
|
||||
return allSuccessful;
|
||||
} finally {
|
||||
_isProcessing = false;
|
||||
}
|
||||
|
@ -281,6 +281,7 @@ class CommunitySpaceManagementApi {
|
||||
return json['success'] ?? false;
|
||||
},
|
||||
);
|
||||
|
||||
return response;
|
||||
} catch (e) {
|
||||
debugPrint('Error updating space: $e');
|
||||
|
Reference in New Issue
Block a user