From 8dfe8d10d477e81e2dec45c0ab2de84c767b4d87 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 15 May 2025 10:01:43 +0300 Subject: [PATCH 01/25] removed requestType from query parameters of `RemoteOccupancyAnalyticsDevicesService._makeRequest`. --- .../remote_occupancy_analytics_devices_service.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart b/lib/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart index 95a4aacf..91bbe1f4 100644 --- a/lib/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart +++ b/lib/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart @@ -39,7 +39,6 @@ class RemoteOccupancyAnalyticsDevicesService implements AnalyticsDevicesService path: '/projects/$projectUuid/communities/${param.communityUuid}/spaces/${param.spaceUuid}/devices', queryParameters: { - 'requestType': param.requestType.name, 'communityUuid': param.communityUuid, 'spaceUuid': param.spaceUuid, 'productType': param.deviceTypes.first, From e6d272a60dadced8f3254f6fbfb97bc77e537ec6 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 15 May 2025 10:06:13 +0300 Subject: [PATCH 02/25] loads heatmap data on calendar change. --- .../helpers/fetch_occupancy_data_helper.dart | 22 +++++++++++-------- .../widgets/occupancy_heat_map_box.dart | 14 +++++++----- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/lib/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart b/lib/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart index be21027b..476ef9c6 100644 --- a/lib/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart +++ b/lib/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart @@ -38,15 +38,7 @@ abstract final class FetchOccupancyDataHelper { ), ), ); - - context.read().add( - LoadOccupancyHeatMapEvent( - GetOccupancyHeatMapParam( - spaceUuid: spaceId, - year: datePickerState.yearlyDate, - ), - ), - ); + loadHeatMapData(context, spaceUuid: spaceId, year: datePickerState.yearlyDate); if (selectedDevice case final AnalyticsDevice device) { context.read() @@ -57,6 +49,18 @@ abstract final class FetchOccupancyDataHelper { } } + static void loadHeatMapData( + BuildContext context, { + required String spaceUuid, + required DateTime year, + }) { + context.read().add( + LoadOccupancyHeatMapEvent( + GetOccupancyHeatMapParam(spaceUuid: spaceUuid, year: year), + ), + ); + } + static void loadAnalyticsDevices( BuildContext context, { required String communityUuid, diff --git a/lib/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_box.dart b/lib/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_box.dart index d52604bf..cab9eab4 100644 --- a/lib/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_box.dart +++ b/lib/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_box.dart @@ -47,12 +47,14 @@ class OccupancyHeatMapBox extends StatelessWidget { context.read().add( UpdateAnalyticsDatePickerEvent(yearlyDate: value), ); - FetchOccupancyDataHelper.loadOccupancyData( - context, - communityId: - spaceTreeState.selectedCommunities.firstOrNull ?? '', - spaceId: spaceTreeState.selectedSpaces.firstOrNull ?? '', - ); + if (spaceTreeState.selectedSpaces.isNotEmpty) { + FetchOccupancyDataHelper.loadHeatMapData( + context, + spaceUuid: + spaceTreeState.selectedSpaces.firstOrNull ?? '', + year: value, + ); + } }, datePickerType: DatePickerType.year, selectedDate: context From ad15d0e13804975e058fc6bbada9d900cb281d14 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 15 May 2025 10:08:41 +0300 Subject: [PATCH 03/25] loads occupancy chart on date changed. --- .../helpers/fetch_occupancy_data_helper.dart | 33 +++++++++++++------ .../widgets/occupancy_chart_box.dart | 16 ++++++--- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/lib/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart b/lib/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart index 476ef9c6..5882ada5 100644 --- a/lib/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart +++ b/lib/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart @@ -28,16 +28,12 @@ abstract final class FetchOccupancyDataHelper { loadAnalyticsDevices(context, communityUuid: communityId, spaceUuid: spaceId); final selectedDevice = context.read().state.selectedDevice; - context.read().add( - LoadOccupancyEvent( - GetOccupancyParam( - monthDate: - '${datePickerState.monthlyDate.year}-${datePickerState.monthlyDate.month}', - spaceUuid: spaceId, - communityUuid: communityId, - ), - ), - ); + loadOccupancyChartData( + context, + communityUuid: communityId, + spaceUuid: spaceId, + date: datePickerState.monthlyDate, + ); loadHeatMapData(context, spaceUuid: spaceId, year: datePickerState.yearlyDate); if (selectedDevice case final AnalyticsDevice device) { @@ -61,6 +57,23 @@ abstract final class FetchOccupancyDataHelper { ); } + static void loadOccupancyChartData( + BuildContext context, { + required String communityUuid, + required String spaceUuid, + required DateTime date, + }) { + context.read().add( + LoadOccupancyEvent( + GetOccupancyParam( + monthDate: '${date.year}-${date.month}', + spaceUuid: spaceUuid, + communityUuid: communityUuid, + ), + ), + ); + } + static void loadAnalyticsDevices( BuildContext context, { required String communityUuid, diff --git a/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart_box.dart b/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart_box.dart index 464a05dd..8c41da48 100644 --- a/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart_box.dart +++ b/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart_box.dart @@ -47,11 +47,17 @@ class OccupancyChartBox extends StatelessWidget { context.read().add( UpdateAnalyticsDatePickerEvent(montlyDate: value), ); - FetchOccupancyDataHelper.loadOccupancyData( - context, - communityId: spaceTreeState.selectedCommunities.firstOrNull ?? '', - spaceId: spaceTreeState.selectedSpaces.firstOrNull ?? '', - ); + if (spaceTreeState.selectedSpaces.isNotEmpty) { + FetchOccupancyDataHelper.loadOccupancyChartData( + context, + communityUuid: + spaceTreeState.selectedCommunities.firstOrNull ?? + '', + spaceUuid: + spaceTreeState.selectedSpaces.firstOrNull ?? '', + date: value, + ); + } }, selectedDate: context .watch() From 39d125ac7ee0d23c5843a8825e5a1468e90e2c90 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 15 May 2025 10:11:55 +0300 Subject: [PATCH 04/25] loads energy management data on date changed. --- .../analytics_page_tabs_and_children.dart | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart b/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart index 2a063599..77871134 100644 --- a/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart +++ b/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart @@ -14,7 +14,6 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget { @override Widget build(BuildContext context) { - final spaceTreeState = context.read().state; return BlocBuilder( buildWhen: (previous, current) => previous != current, builder: (context, selectedTab) => Column( @@ -68,15 +67,21 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget { context.read().add( UpdateAnalyticsDatePickerEvent(montlyDate: value), ); - FetchEnergyManagementDataHelper.loadEnergyManagementData( - context, - selectedDate: value, - communityId: - spaceTreeState.selectedCommunities.firstOrNull ?? - '', - spaceId: - spaceTreeState.selectedSpaces.firstOrNull ?? '', - ); + + final spaceTreeState = + context.read().state; + if (spaceTreeState.selectedSpaces.isNotEmpty) { + FetchEnergyManagementDataHelper + .loadEnergyManagementData( + context, + selectedDate: value, + communityId: + spaceTreeState.selectedCommunities.firstOrNull ?? + '', + spaceId: + spaceTreeState.selectedSpaces.firstOrNull ?? '', + ); + } }, selectedDate: context .watch() From c07b53107e474318446653e4efae455085d6e9d6 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 15 May 2025 10:51:09 +0300 Subject: [PATCH 05/25] SP-1506-FE-chart per phase api integration. --- .../models/phases_energy_consumption.dart | 65 +++++++++++++++---- .../analytics/views/analytics_page.dart | 4 +- ...gy_consumption_by_phases_chart_helper.dart | 20 ------ .../fetch_energy_management_data_helper.dart | 27 ++++++-- .../energy_consumption_by_phases_chart.dart | 39 +++++++---- .../power_clamp_energy_data_widget.dart | 8 +++ ...et_energy_consumption_by_phases_param.dart | 16 ++--- ..._energy_consumption_by_phases_service.dart | 29 --------- ..._energy_consumption_by_phases_service.dart | 5 +- 9 files changed, 120 insertions(+), 93 deletions(-) delete mode 100644 lib/pages/analytics/modules/energy_management/helpers/energy_consumption_by_phases_chart_helper.dart delete mode 100644 lib/pages/analytics/services/energy_consumption_by_phases/fake_energy_consumption_by_phases_service.dart diff --git a/lib/pages/analytics/models/phases_energy_consumption.dart b/lib/pages/analytics/models/phases_energy_consumption.dart index f986c3ad..a826bac0 100644 --- a/lib/pages/analytics/models/phases_energy_consumption.dart +++ b/lib/pages/analytics/models/phases_energy_consumption.dart @@ -1,27 +1,66 @@ import 'package:equatable/equatable.dart'; class PhasesEnergyConsumption extends Equatable { - final int month; - final double phaseA; - final double phaseB; - final double phaseC; + final String uuid; + final DateTime createdAt; + final DateTime updatedAt; + final String deviceUuid; + final DateTime date; + final double energyConsumedKw; + final double energyConsumedA; + final double energyConsumedB; + final double energyConsumedC; const PhasesEnergyConsumption({ - required this.month, - required this.phaseA, - required this.phaseB, - required this.phaseC, + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.deviceUuid, + required this.date, + required this.energyConsumedKw, + required this.energyConsumedA, + required this.energyConsumedB, + required this.energyConsumedC, }); @override - List get props => [month, phaseA, phaseB, phaseC]; + List get props => [ + uuid, + createdAt, + updatedAt, + deviceUuid, + date, + energyConsumedKw, + energyConsumedA, + energyConsumedB, + energyConsumedC, + ]; factory PhasesEnergyConsumption.fromJson(Map json) { return PhasesEnergyConsumption( - month: json['month'] as int, - phaseA: (json['phaseA'] as num).toDouble(), - phaseB: (json['phaseB'] as num).toDouble(), - phaseC: (json['phaseC'] as num).toDouble(), + uuid: json['uuid'] as String, + createdAt: DateTime.parse(json['createdAt'] as String), + updatedAt: DateTime.parse(json['updatedAt'] as String), + deviceUuid: json['deviceUuid'] as String, + date: DateTime.parse(json['date'] as String), + energyConsumedKw: double.parse(json['energyConsumedKw']), + energyConsumedA: double.parse(json['energyConsumedA']), + energyConsumedB: double.parse(json['energyConsumedB']), + energyConsumedC: double.parse(json['energyConsumedC']), ); } + + Map toJson() { + return { + 'uuid': uuid, + 'createdAt': createdAt.toIso8601String(), + 'updatedAt': updatedAt.toIso8601String(), + 'deviceUuid': deviceUuid, + 'date': date.toIso8601String().split('T')[0], + 'energyConsumedKw': energyConsumedKw.toString(), + 'energyConsumedA': energyConsumedA.toString(), + 'energyConsumedB': energyConsumedB.toString(), + 'energyConsumedC': energyConsumedC.toString(), + }; + } } diff --git a/lib/pages/analytics/modules/analytics/views/analytics_page.dart b/lib/pages/analytics/modules/analytics/views/analytics_page.dart index 883672a2..72cc7d51 100644 --- a/lib/pages/analytics/modules/analytics/views/analytics_page.dart +++ b/lib/pages/analytics/modules/analytics/views/analytics_page.dart @@ -15,7 +15,7 @@ import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_he import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart'; import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart'; import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart'; -import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/fake_energy_consumption_by_phases_service.dart'; +import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/remote_energy_consumption_by_phases_service.dart'; import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/remote_energy_consumption_per_device_service.dart'; import 'package:syncrow_web/pages/analytics/services/occupacy/fake_occupacy_service.dart'; import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/remote_occupancy_heat_map_service.dart'; @@ -57,7 +57,7 @@ class _AnalyticsPageState extends State { ), BlocProvider( create: (context) => EnergyConsumptionByPhasesBloc( - FakeEnergyConsumptionByPhasesService(), + RemoteEnergyConsumptionByPhasesService(_httpService), ), ), BlocProvider( diff --git a/lib/pages/analytics/modules/energy_management/helpers/energy_consumption_by_phases_chart_helper.dart b/lib/pages/analytics/modules/energy_management/helpers/energy_consumption_by_phases_chart_helper.dart deleted file mode 100644 index e79fe48d..00000000 --- a/lib/pages/analytics/modules/energy_management/helpers/energy_consumption_by_phases_chart_helper.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart'; - -abstract final class EnergyConsumptionByPhasesChartHelper { - const EnergyConsumptionByPhasesChartHelper._(); - - static const fakeData = [ - PhasesEnergyConsumption(month: 1, phaseA: 200, phaseB: 300, phaseC: 400), - PhasesEnergyConsumption(month: 2, phaseA: 300, phaseB: 400, phaseC: 500), - PhasesEnergyConsumption(month: 3, phaseA: 400, phaseB: 500, phaseC: 600), - PhasesEnergyConsumption(month: 4, phaseA: 100, phaseB: 100, phaseC: 100), - PhasesEnergyConsumption(month: 5, phaseA: 300, phaseB: 400, phaseC: 500), - PhasesEnergyConsumption(month: 6, phaseA: 300, phaseB: 100, phaseC: 400), - PhasesEnergyConsumption(month: 7, phaseA: 300, phaseB: 100, phaseC: 400), - PhasesEnergyConsumption(month: 8, phaseA: 500, phaseB: 100, phaseC: 100), - PhasesEnergyConsumption(month: 9, phaseA: 500, phaseB: 100, phaseC: 200), - PhasesEnergyConsumption(month: 10, phaseA: 100, phaseB: 50, phaseC: 50), - PhasesEnergyConsumption(month: 11, phaseA: 600, phaseB: 750, phaseC: 130), - PhasesEnergyConsumption(month: 12, phaseA: 100, phaseB: 80, phaseC: 100), - ]; -} diff --git a/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart b/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart index 96fbb224..29da8c61 100644 --- a/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart +++ b/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart @@ -34,14 +34,26 @@ abstract final class FetchEnergyManagementDataHelper { final datePickerState = context.read().state; final selectedDate0 = selectedDate ?? datePickerState.monthlyDate; - loadAnalyticsDevices(context, communityUuid: communityId, spaceUuid: spaceId); + loadAnalyticsDevices( + context, + communityUuid: communityId, + spaceUuid: spaceId, + selectedDate: selectedDate0, + ); loadTotalEnergyConsumption( context, selectedDate: selectedDate0, communityId: communityId, spaceId: spaceId, ); - loadEnergyConsumptionByPhases(context, selectedDate: selectedDate); + final selectedDevice = getSelectedDevice(context); + if (selectedDevice case final AnalyticsDevice device) { + loadEnergyConsumptionByPhases( + context, + powerClampUuid: device.uuid, + selectedDate: selectedDate0, + ); + } loadEnergyConsumptionPerDevice( context, communityId: communityId, @@ -54,11 +66,12 @@ abstract final class FetchEnergyManagementDataHelper { static void loadEnergyConsumptionByPhases( BuildContext context, { + required String powerClampUuid, DateTime? selectedDate, }) { final param = GetEnergyConsumptionByPhasesParam( - startDate: selectedDate, - spaceId: '', + date: selectedDate, + powerClampUuid: powerClampUuid, ); context.read().add( LoadEnergyConsumptionByPhasesEvent(param: param), @@ -121,6 +134,7 @@ abstract final class FetchEnergyManagementDataHelper { BuildContext context, { required String communityUuid, required String spaceUuid, + required DateTime selectedDate, }) { context.read().add( LoadAnalyticsDevicesEvent( @@ -128,6 +142,11 @@ abstract final class FetchEnergyManagementDataHelper { context.read().add( LoadPowerClampInfoEvent(device.uuid), ); + loadEnergyConsumptionByPhases( + context, + powerClampUuid: device.uuid, + selectedDate: selectedDate, + ); context.read().add( RealtimeDeviceChangesStarted(device.uuid), ); diff --git a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart.dart b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart.dart index c94755bb..883e8556 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart.dart @@ -1,6 +1,6 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/analytics/helpers/get_month_name_from_int.dart'; +import 'package:intl/intl.dart'; import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -31,21 +31,25 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget { barRods: [ BarChartRodData( color: ColorsManager.vividBlue.withValues(alpha: 0.1), - toY: data.phaseA + data.phaseB + data.phaseC, + toY: data.energyConsumedA + + data.energyConsumedB + + data.energyConsumedC, rodStackItems: [ BarChartRodStackItem( 0, - data.phaseA, + data.energyConsumedA, ColorsManager.vividBlue.withValues(alpha: 0.8), ), BarChartRodStackItem( - data.phaseA, - data.phaseA + data.phaseB, + data.energyConsumedA, + data.energyConsumedA + data.energyConsumedB, ColorsManager.vividBlue.withValues(alpha: 0.4), ), BarChartRodStackItem( - data.phaseA + data.phaseB, - data.phaseA + data.phaseB + data.phaseC, + data.energyConsumedA + data.energyConsumedB, + data.energyConsumedA + + data.energyConsumedB + + data.energyConsumedC, ColorsManager.vividBlue.withValues(alpha: 0.15), ), ], @@ -91,18 +95,27 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget { }) { final data = energyData; - final month = data[group.x.toInt()].month.getMonthName; - final phaseA = data[group.x.toInt()].phaseA; - final phaseB = data[group.x.toInt()].phaseB; - final phaseC = data[group.x.toInt()].phaseC; + final date = DateFormat('dd/MM/yyyy').format(data[group.x.toInt()].date); + final phaseA = data[group.x.toInt()].energyConsumedA; + final phaseB = data[group.x.toInt()].energyConsumedB; + final phaseC = data[group.x.toInt()].energyConsumedC; + final total = data[group.x.toInt()].energyConsumedKw; return BarTooltipItem( - '$month\n', + '$date\n', context.textTheme.bodyMedium!.copyWith( color: ColorsManager.blackColor, fontSize: 14, ), + textAlign: TextAlign.start, children: [ + TextSpan( + text: 'Total: $total\n', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontSize: 12, + ), + ), TextSpan( text: 'Phase A: $phaseA\n', style: context.textTheme.bodySmall?.copyWith( @@ -144,7 +157,7 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget { sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, _) { - final month = energyData[value.toInt()].month.getMonthName; + final month = DateFormat('dd/MM').format(energyData[value.toInt()].date); return FittedBox( alignment: AlignmentDirectional.bottomCenter, fit: BoxFit.scaleDown, diff --git a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart b/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart index 95f343c2..8a53d037 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart'; +import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart'; @@ -11,6 +12,7 @@ import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/po import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart'; import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart'; import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -132,6 +134,12 @@ class PowerClampEnergyDataWidget extends StatelessWidget { alignment: AlignmentDirectional.centerEnd, child: AnalyticsDeviceDropdown( onChanged: (value) { + FetchEnergyManagementDataHelper.loadEnergyConsumptionByPhases( + context, + powerClampUuid: value.uuid, + selectedDate: + context.read().state.monthlyDate, + ); FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges( context, deviceUuid: value.uuid, diff --git a/lib/pages/analytics/params/get_energy_consumption_by_phases_param.dart b/lib/pages/analytics/params/get_energy_consumption_by_phases_param.dart index 169e2753..243e156e 100644 --- a/lib/pages/analytics/params/get_energy_consumption_by_phases_param.dart +++ b/lib/pages/analytics/params/get_energy_consumption_by_phases_param.dart @@ -1,24 +1,20 @@ import 'package:equatable/equatable.dart'; class GetEnergyConsumptionByPhasesParam extends Equatable { - final DateTime? startDate; - final DateTime? endDate; - final String? spaceId; + final String powerClampUuid; + final DateTime? date; const GetEnergyConsumptionByPhasesParam({ - this.startDate, - this.endDate, - this.spaceId, + required this.powerClampUuid, + this.date, }); Map toJson() { return { - 'startDate': startDate?.toIso8601String(), - 'endDate': endDate?.toIso8601String(), - 'spaceId': spaceId, + 'monthDate': '${date?.year}-${date?.month.toString().padLeft(2, '0')}', }; } @override - List get props => [startDate, endDate, spaceId]; + List get props => [powerClampUuid, date]; } diff --git a/lib/pages/analytics/services/energy_consumption_by_phases/fake_energy_consumption_by_phases_service.dart b/lib/pages/analytics/services/energy_consumption_by_phases/fake_energy_consumption_by_phases_service.dart deleted file mode 100644 index f6ce67c9..00000000 --- a/lib/pages/analytics/services/energy_consumption_by_phases/fake_energy_consumption_by_phases_service.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart'; -import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart'; -import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/energy_consumption_by_phases_service.dart'; - -class FakeEnergyConsumptionByPhasesService - implements EnergyConsumptionByPhasesService { - @override - Future> load( - GetEnergyConsumptionByPhasesParam param, - ) { - return Future.delayed( - const Duration(milliseconds: 500), - () => const [ - PhasesEnergyConsumption(month: 1, phaseA: 200, phaseB: 300, phaseC: 400), - PhasesEnergyConsumption(month: 2, phaseA: 300, phaseB: 400, phaseC: 500), - PhasesEnergyConsumption(month: 3, phaseA: 400, phaseB: 500, phaseC: 600), - PhasesEnergyConsumption(month: 4, phaseA: 100, phaseB: 100, phaseC: 100), - PhasesEnergyConsumption(month: 5, phaseA: 300, phaseB: 400, phaseC: 500), - PhasesEnergyConsumption(month: 6, phaseA: 300, phaseB: 100, phaseC: 400), - PhasesEnergyConsumption(month: 7, phaseA: 300, phaseB: 100, phaseC: 400), - PhasesEnergyConsumption(month: 8, phaseA: 500, phaseB: 100, phaseC: 100), - PhasesEnergyConsumption(month: 9, phaseA: 500, phaseB: 100, phaseC: 200), - PhasesEnergyConsumption(month: 10, phaseA: 100, phaseB: 50, phaseC: 50), - PhasesEnergyConsumption(month: 11, phaseA: 600, phaseB: 750, phaseC: 130), - PhasesEnergyConsumption(month: 12, phaseA: 100, phaseB: 100, phaseC: 100), - ], - ); - } -} diff --git a/lib/pages/analytics/services/energy_consumption_by_phases/remote_energy_consumption_by_phases_service.dart b/lib/pages/analytics/services/energy_consumption_by_phases/remote_energy_consumption_by_phases_service.dart index f0ac31ed..28df5eed 100644 --- a/lib/pages/analytics/services/energy_consumption_by_phases/remote_energy_consumption_by_phases_service.dart +++ b/lib/pages/analytics/services/energy_consumption_by_phases/remote_energy_consumption_by_phases_service.dart @@ -15,8 +15,9 @@ final class RemoteEnergyConsumptionByPhasesService ) async { try { final response = await _httpService.get( - path: 'endpoint', + path: '/power-clamp/${param.powerClampUuid}/historical', showServerMessage: true, + queryParameters: param.toJson(), expectedResponseModel: (data) { final json = data as Map? ?? {}; final mappedData = json['data'] as List? ?? []; @@ -28,7 +29,7 @@ final class RemoteEnergyConsumptionByPhasesService ); return response; } catch (e) { - throw Exception('Failed to load energy consumption per device: $e'); + throw Exception('Failed to load energy consumption per phase: $e'); } } } From d80f5e1f3a27527383322d555b79e2aad6a8a634 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 15 May 2025 14:25:13 +0300 Subject: [PATCH 06/25] Refactor energy consumption charts to enhance grid data configuration Updated the grid data for EnergyConsumptionByPhasesChart, EnergyConsumptionPerDeviceChart, and TotalEnergyConsumptionChart to include horizontal line visibility and set a horizontal interval of 250. Removed unused phasesJson constant from TotalEnergyConsumptionChart for cleaner code. --- .../energy_consumption_by_phases_chart.dart | 5 ++++- .../energy_consumption_per_device_chart.dart | 5 ++++- .../total_energy_consumption_chart.dart | 19 ++++++++----------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart.dart b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart.dart index c94755bb..5633248b 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart.dart @@ -18,7 +18,10 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget { Widget build(BuildContext context) { return BarChart( BarChartData( - gridData: EnergyManagementChartsHelper.gridData(), + gridData: EnergyManagementChartsHelper.gridData().copyWith( + checkToShowHorizontalLine: (value) => true, + horizontalInterval: 250, + ), borderData: EnergyManagementChartsHelper.borderData(), barTouchData: _barTouchData(context), titlesData: _titlesData(context), diff --git a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart index 8a779d63..892e4676 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart @@ -16,7 +16,10 @@ class EnergyConsumptionPerDeviceChart extends StatelessWidget { context, leftTitlesInterval: 250, ), - gridData: EnergyManagementChartsHelper.gridData(), + gridData: EnergyManagementChartsHelper.gridData().copyWith( + checkToShowHorizontalLine: (value) => true, + horizontalInterval: 250, + ), borderData: EnergyManagementChartsHelper.borderData(), lineTouchData: EnergyManagementChartsHelper.lineTouchData(), lineBarsData: chartData.map((e) { diff --git a/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart b/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart index 70170180..b0f7961a 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart @@ -4,15 +4,6 @@ import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -// energy_consumption_chart will return id, name and consumption -const phasesJson = { - "1": { - "phaseOne": 1000, - "phaseTwo": 2000, - "phaseThree": 3000, - } -}; - class TotalEnergyConsumptionChart extends StatelessWidget { const TotalEnergyConsumptionChart({required this.chartData, super.key}); @@ -23,8 +14,14 @@ class TotalEnergyConsumptionChart extends StatelessWidget { return Expanded( child: LineChart( LineChartData( - titlesData: EnergyManagementChartsHelper.titlesData(context), - gridData: EnergyManagementChartsHelper.gridData(), + titlesData: EnergyManagementChartsHelper.titlesData( + context, + leftTitlesInterval: 250, + ), + gridData: EnergyManagementChartsHelper.gridData().copyWith( + checkToShowHorizontalLine: (value) => true, + horizontalInterval: 250, + ), borderData: EnergyManagementChartsHelper.borderData(), lineTouchData: EnergyManagementChartsHelper.lineTouchData(), lineBarsData: _lineBarsData, From 5191c1e4560162236838eb566ddff653975d8800 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 15 May 2025 15:28:36 +0300 Subject: [PATCH 07/25] Performed selection validation, and made future dates disabled. --- .../widgets/month_picker_widget.dart | 42 +++++++++++++++---- .../analytics/widgets/year_picker_widget.dart | 4 +- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/lib/pages/analytics/modules/analytics/widgets/month_picker_widget.dart b/lib/pages/analytics/modules/analytics/widgets/month_picker_widget.dart index 53ba31af..c1862ac8 100644 --- a/lib/pages/analytics/modules/analytics/widgets/month_picker_widget.dart +++ b/lib/pages/analytics/modules/analytics/widgets/month_picker_widget.dart @@ -121,6 +121,7 @@ class _MonthPickerWidgetState extends State { } Row _buildYearSelector() { + final currentYear = DateTime.now().year; return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -134,17 +135,35 @@ class _MonthPickerWidgetState extends State { ), const Spacer(), IconButton( - onPressed: () => setState(() => _currentYear = _currentYear - 1), + onPressed: () { + setState(() { + _currentYear = _currentYear - 1; + }); + }, icon: const Icon( Icons.chevron_left, color: ColorsManager.grey700, ), ), IconButton( - onPressed: () => setState(() => _currentYear = _currentYear + 1), - icon: const Icon( + onPressed: _currentYear < currentYear + ? () { + setState(() { + _currentYear = _currentYear + 1; + // Clear selected month if it becomes invalid in the new year + if (_currentYear == currentYear && + _selectedMonth != null && + _selectedMonth! > DateTime.now().month - 1) { + _selectedMonth = null; + } + }); + } + : null, + icon: Icon( Icons.chevron_right, - color: ColorsManager.grey700, + color: _currentYear < currentYear + ? ColorsManager.grey700 + : ColorsManager.grey700.withValues(alpha: 0.3), ), ), ], @@ -152,6 +171,9 @@ class _MonthPickerWidgetState extends State { } Widget _buildMonthsGrid() { + final currentDate = DateTime.now(); + final isCurrentYear = _currentYear == currentDate.year; + return GridView.builder( shrinkWrap: true, itemCount: 12, @@ -165,14 +187,18 @@ class _MonthPickerWidgetState extends State { ), itemBuilder: (context, index) { final isSelected = _selectedMonth == index; + final isFutureMonth = isCurrentYear && index > currentDate.month - 1; + return InkWell( - onTap: () => setState(() => _selectedMonth = index), + onTap: isFutureMonth ? null : () => setState(() => _selectedMonth = index), child: Container( alignment: Alignment.center, decoration: BoxDecoration( color: isSelected ? ColorsManager.vividBlue.withValues(alpha: 0.7) - : const Color(0xFFEDF2F7), + : isFutureMonth + ? ColorsManager.grey700.withValues(alpha: 0.1) + : const Color(0xFFEDF2F7), borderRadius: isSelected ? BorderRadius.circular(15) : BorderRadius.zero, ), @@ -182,7 +208,9 @@ class _MonthPickerWidgetState extends State { fontSize: 12, color: isSelected ? ColorsManager.whiteColors - : ColorsManager.blackColor.withValues(alpha: 0.8), + : isFutureMonth + ? ColorsManager.blackColor.withValues(alpha: 0.3) + : ColorsManager.blackColor.withValues(alpha: 0.8), fontWeight: FontWeight.w500, ), ), diff --git a/lib/pages/analytics/modules/analytics/widgets/year_picker_widget.dart b/lib/pages/analytics/modules/analytics/widgets/year_picker_widget.dart index 4c7bb748..c27a2d9a 100644 --- a/lib/pages/analytics/modules/analytics/widgets/year_picker_widget.dart +++ b/lib/pages/analytics/modules/analytics/widgets/year_picker_widget.dart @@ -20,9 +20,9 @@ class _YearPickerWidgetState extends State { late int _currentYear; static final years = List.generate( - DateTime.now().year - 2020 + 1, + DateTime.now().year - (DateTime.now().year - 5) + 1, (index) => (2020 + index), - ); + ).where((year) => year <= DateTime.now().year).toList(); @override void initState() { From 051bf657ed196b0a48c4128d9852ab1529c0eca0 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 15 May 2025 15:29:09 +0300 Subject: [PATCH 08/25] Changed background color of analytics date pickers to match the design language of the platform. --- .../modules/analytics/widgets/month_picker_widget.dart | 2 +- .../analytics/modules/analytics/widgets/year_picker_widget.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/analytics/modules/analytics/widgets/month_picker_widget.dart b/lib/pages/analytics/modules/analytics/widgets/month_picker_widget.dart index c1862ac8..ab7603e5 100644 --- a/lib/pages/analytics/modules/analytics/widgets/month_picker_widget.dart +++ b/lib/pages/analytics/modules/analytics/widgets/month_picker_widget.dart @@ -45,7 +45,7 @@ class _MonthPickerWidgetState extends State { @override Widget build(BuildContext context) { return Dialog( - backgroundColor: Theme.of(context).colorScheme.surface, + backgroundColor: ColorsManager.whiteColors, child: Container( padding: const EdgeInsetsDirectional.all(20), width: 320, diff --git a/lib/pages/analytics/modules/analytics/widgets/year_picker_widget.dart b/lib/pages/analytics/modules/analytics/widgets/year_picker_widget.dart index c27a2d9a..2bcb223b 100644 --- a/lib/pages/analytics/modules/analytics/widgets/year_picker_widget.dart +++ b/lib/pages/analytics/modules/analytics/widgets/year_picker_widget.dart @@ -33,7 +33,7 @@ class _YearPickerWidgetState extends State { @override Widget build(BuildContext context) { return Dialog( - backgroundColor: Theme.of(context).colorScheme.surface, + backgroundColor: ColorsManager.whiteColors, child: Container( padding: const EdgeInsetsDirectional.all(20), width: 320, From b1bae3cb15a46f8639c11d0d2fda4445c2a1557c Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 15 May 2025 15:59:02 +0300 Subject: [PATCH 09/25] fixed overflow bug on charts. --- .../helpers/energy_management_charts_helper.dart | 6 ++++-- .../widgets/total_energy_consumption_chart.dart | 1 + .../modules/occupancy/widgets/occupancy_chart.dart | 9 ++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart b/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart index 5c56d229..b8849335 100644 --- a/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart +++ b/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart @@ -20,7 +20,8 @@ abstract final class EnergyManagementChartsHelper { interval: 1, reservedSize: 32, showTitles: true, - maxIncluded: true, + maxIncluded: false, + minIncluded: false, getTitlesWidget: (value, meta) => Padding( padding: const EdgeInsetsDirectional.only(top: 20.0), child: Text( @@ -36,7 +37,8 @@ abstract final class EnergyManagementChartsHelper { leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, - maxIncluded: true, + maxIncluded: false, + minIncluded: false, interval: leftTitlesInterval, reservedSize: 110, getTitlesWidget: (value, meta) => Padding( diff --git a/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart b/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart index b0f7961a..81052d02 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart @@ -21,6 +21,7 @@ class TotalEnergyConsumptionChart extends StatelessWidget { gridData: EnergyManagementChartsHelper.gridData().copyWith( checkToShowHorizontalLine: (value) => true, horizontalInterval: 250, + ), borderData: EnergyManagementChartsHelper.borderData(), lineTouchData: EnergyManagementChartsHelper.lineTouchData(), diff --git a/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart b/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart index dc1357bc..8a93ccf1 100644 --- a/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart +++ b/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart @@ -23,7 +23,14 @@ class OccupancyChart extends StatelessWidget { ), borderData: EnergyManagementChartsHelper.borderData(), barTouchData: _barTouchData(context), - titlesData: _titlesData(context), + titlesData: _titlesData(context).copyWith( + leftTitles: _titlesData(context).leftTitles.copyWith( + sideTitles: _titlesData(context).leftTitles.sideTitles.copyWith( + maxIncluded: true, + minIncluded: true, + ), + ), + ), barGroups: List.generate(chartData.length, (index) { final actual = chartData[index]; return BarChartGroupData( From 3e3e17019a58f9d3b58c8c2cb0dc2f22ba9d8124 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 15 May 2025 16:22:54 +0300 Subject: [PATCH 10/25] format. --- .../widgets/total_energy_consumption_chart.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart b/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart index 81052d02..b0f7961a 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart @@ -21,7 +21,6 @@ class TotalEnergyConsumptionChart extends StatelessWidget { gridData: EnergyManagementChartsHelper.gridData().copyWith( checkToShowHorizontalLine: (value) => true, horizontalInterval: 250, - ), borderData: EnergyManagementChartsHelper.borderData(), lineTouchData: EnergyManagementChartsHelper.lineTouchData(), From a849c1dafbe07291f80a3eb5b3027e25b14163e1 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 15 May 2025 16:31:11 +0300 Subject: [PATCH 11/25] removed unused import. --- .../widgets/power_clamp_energy_data_widget.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart b/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart index 8a53d037..8ae6cd7f 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart @@ -12,7 +12,6 @@ import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/po import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart'; import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart'; import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart'; -import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; From f67d0e29127f171209caeec2fde21798c417a7ad Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 19 May 2025 10:17:48 +0300 Subject: [PATCH 12/25] SP-1494 reworks. 1. When the chart loads, we see it coming from the top right corner (check the attached video). 2. Day 1 is missing on the X axis. 3. Overlapping line not removed. --- .../energy_management_charts_helper.dart | 45 +++++++++++-------- .../energy_consumption_per_device_chart.dart | 3 +- .../total_energy_consumption_chart.dart | 4 +- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart b/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart index b8849335..8e7a9f58 100644 --- a/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart +++ b/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart @@ -20,12 +20,12 @@ abstract final class EnergyManagementChartsHelper { interval: 1, reservedSize: 32, showTitles: true, - maxIncluded: false, - minIncluded: false, + maxIncluded: true, + minIncluded: true, getTitlesWidget: (value, meta) => Padding( padding: const EdgeInsetsDirectional.only(top: 20.0), child: Text( - (value + 1).toString(), + value.toString(), style: context.textTheme.bodySmall?.copyWith( color: ColorsManager.greyColor, fontSize: 12, @@ -72,7 +72,7 @@ abstract final class EnergyManagementChartsHelper { static List getTooltipItems(List touchedSpots) { return touchedSpots.map((spot) { return LineTooltipItem( - getToolTipLabel(spot.x + 1, spot.y), + getToolTipLabel(spot.x, spot.y), const TextStyle( color: ColorsManager.textPrimaryColor, fontWeight: FontWeight.w600, @@ -93,31 +93,38 @@ abstract final class EnergyManagementChartsHelper { ); } - static FlBorderData borderData() { - return FlBorderData( - show: true, - border: const Border.symmetric( - horizontal: BorderSide( - color: ColorsManager.greyColor, - style: BorderStyle.solid, - width: 1, - ), - ), - ); - } - static FlGridData gridData() { - return const FlGridData( + return FlGridData( show: true, drawVerticalLine: false, drawHorizontalLine: true, + horizontalInterval: 250, + getDrawingHorizontalLine: (value) { + return FlLine( + color: ColorsManager.greyColor, + strokeWidth: 1, + dashArray: value == 0 ? null : [5, 5], + ); + }, + ); + } + + static FlBorderData borderData() { + return FlBorderData( + border: const Border( + bottom: BorderSide( + color: ColorsManager.greyColor, + style: BorderStyle.solid, + ), + ), + show: true, ); } static LineTouchData lineTouchData() { return LineTouchData( handleBuiltInTouches: true, - touchSpotThreshold: 2, + touchSpotThreshold: 16, touchTooltipData: EnergyManagementChartsHelper.lineTouchTooltipData(), ); } diff --git a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart index 892e4676..fcf7d384 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart @@ -16,6 +16,7 @@ class EnergyConsumptionPerDeviceChart extends StatelessWidget { context, leftTitlesInterval: 250, ), + gridData: EnergyManagementChartsHelper.gridData().copyWith( checkToShowHorizontalLine: (value) => true, horizontalInterval: 250, @@ -36,7 +37,7 @@ class EnergyConsumptionPerDeviceChart extends StatelessWidget { ); }).toList(), ), - duration: Durations.extralong1, + duration: Duration.zero, curve: Curves.easeIn, ); } diff --git a/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart b/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart index b0f7961a..204261df 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart @@ -26,7 +26,7 @@ class TotalEnergyConsumptionChart extends StatelessWidget { lineTouchData: EnergyManagementChartsHelper.lineTouchData(), lineBarsData: _lineBarsData, ), - duration: Durations.extralong1, + duration: Duration.zero, curve: Curves.easeIn, ), ); @@ -43,7 +43,7 @@ class TotalEnergyConsumptionChart extends StatelessWidget { .entries .map( (entry) => FlSpot( - entry.key.toDouble(), + entry.value.date.day.toDouble(), entry.value.value, ), ) From 494ae1c941d23ad420c8ac248f98e9f8aa8a5d40 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 19 May 2025 10:52:44 +0300 Subject: [PATCH 13/25] SP-1495 reworks. 1. Overlapping line not removed. 2. The colors of the data on X axis and Y axis are not identical to design. 3. Day 1 and 2 are missing on the X axis. 4. When the chart loads, we see it coming from the top right corner (check the attached video). 5. Display all available devices even if they have no data and make the chart empty state. --- .../analytics/models/analytics_device.dart | 54 ++++++++++- .../energy_management_charts_helper.dart | 2 +- ...ergy_consumption_per_device_chart_box.dart | 2 + ...y_consumption_per_device_devices_list.dart | 89 ++++++++++++------- ...energy_consumption_per_device_service.dart | 5 +- 5 files changed, 112 insertions(+), 40 deletions(-) diff --git a/lib/pages/analytics/models/analytics_device.dart b/lib/pages/analytics/models/analytics_device.dart index 6f066407..d2301e4a 100644 --- a/lib/pages/analytics/models/analytics_device.dart +++ b/lib/pages/analytics/models/analytics_device.dart @@ -1,13 +1,65 @@ class AnalyticsDevice { - const AnalyticsDevice({required this.name, required this.uuid}); + const AnalyticsDevice({ + required this.uuid, + required this.name, + required this.createdAt, + required this.updatedAt, + required this.deviceTuyaUuid, + required this.isActive, + required this.productDevice, + }); final String uuid; final String name; + final DateTime createdAt; + final DateTime updatedAt; + final String deviceTuyaUuid; + final bool isActive; + final ProductDevice productDevice; factory AnalyticsDevice.fromJson(Map json) { return AnalyticsDevice( uuid: json['uuid'] as String? ?? '', name: json['name'] as String? ?? '', + createdAt: DateTime.parse(json['createdAt'] as String? ?? ''), + updatedAt: DateTime.parse(json['updatedAt'] as String? ?? ''), + deviceTuyaUuid: json['deviceTuyaUuid'] as String? ?? '', + isActive: json['isActive'] as bool? ?? false, + productDevice: ProductDevice.fromJson( + json['productDevice'] as Map? ?? {}, + ), + ); + } +} + +class ProductDevice { + const ProductDevice({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.catName, + required this.prodId, + required this.name, + required this.prodType, + }); + + final String uuid; + final DateTime createdAt; + final DateTime updatedAt; + final String catName; + final String prodId; + final String name; + final String prodType; + + factory ProductDevice.fromJson(Map json) { + return ProductDevice( + uuid: json['uuid'] as String? ?? '', + createdAt: DateTime.parse(json['createdAt'] as String? ?? ''), + updatedAt: DateTime.parse(json['updatedAt'] as String? ?? ''), + catName: json['catName'] as String? ?? '', + prodId: json['prodId'] as String? ?? '', + name: json['name'] as String? ?? '', + prodType: json['prodType'] as String? ?? '', ); } } diff --git a/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart b/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart index 8e7a9f58..dadfc70f 100644 --- a/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart +++ b/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart @@ -38,7 +38,7 @@ abstract final class EnergyManagementChartsHelper { sideTitles: SideTitles( showTitles: true, maxIncluded: false, - minIncluded: false, + minIncluded: true, interval: leftTitlesInterval, reservedSize: 110, getTitlesWidget: (value, meta) => Padding( diff --git a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart index b62ebe54..f22517d5 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_per_device/energy_consumption_per_device_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart'; @@ -46,6 +47,7 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget { flex: 2, child: EnergyConsumptionPerDeviceDevicesList( chartData: state.chartData, + devices: context.watch().state.devices, ), ), ], diff --git a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_devices_list.dart b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_devices_list.dart index da7a59a8..5ef434e1 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_devices_list.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_devices_list.dart @@ -1,10 +1,16 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/analytics/models/analytics_device.dart'; import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget { - const EnergyConsumptionPerDeviceDevicesList({required this.chartData, super.key}); + const EnergyConsumptionPerDeviceDevicesList({ + required this.chartData, + required this.devices, + super.key, + }); + final List devices; final List chartData; @override @@ -16,45 +22,60 @@ class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end, - children: chartData.map((e) => _buildDeviceCell(context, e)).toList(), + children: devices.map((e) => _buildDeviceCell(context, e)).toList(), ), ); } - Widget _buildDeviceCell(BuildContext context, DeviceEnergyDataModel device) { - return Container( - height: MediaQuery.sizeOf(context).height * 0.0365, - padding: const EdgeInsetsDirectional.symmetric( - vertical: 8, - horizontal: 12, - ), - decoration: BoxDecoration( - borderRadius: BorderRadiusDirectional.circular(8), - border: Border.all( - color: ColorsManager.greyColor, - width: 1, + Widget _buildDeviceCell(BuildContext context, AnalyticsDevice device) { + final deviceColor = chartData + .firstWhere( + (element) => element.deviceId == device.uuid, + orElse: () => const DeviceEnergyDataModel( + energy: [], + deviceName: '', + deviceId: '', + color: Colors.red, + ), + ) + .color; + + return Tooltip( + message: '${device.name}\n${device.productDevice.uuid}', + child: Container( + height: MediaQuery.sizeOf(context).height * 0.0365, + padding: const EdgeInsetsDirectional.symmetric( + vertical: 8, + horizontal: 12, ), - ), - child: FittedBox( - fit: BoxFit.scaleDown, - alignment: Alignment.center, - child: Row( - spacing: 6, - children: [ - CircleAvatar( - radius: 4, - backgroundColor: device.color, - ), - Text( - device.deviceName, - textAlign: TextAlign.center, - style: const TextStyle( - color: ColorsManager.blackColor, - fontWeight: FontWeight.w400, - fontSize: 14, + decoration: BoxDecoration( + borderRadius: BorderRadiusDirectional.circular(8), + border: Border.all( + color: ColorsManager.greyColor, + width: 1, + ), + ), + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: Alignment.center, + child: Row( + spacing: 6, + children: [ + CircleAvatar( + radius: 4, + backgroundColor: deviceColor, ), - ), - ], + Text( + device.name, + textAlign: TextAlign.center, + style: const TextStyle( + color: ColorsManager.blackColor, + fontWeight: FontWeight.w400, + fontSize: 14, + ), + ), + ], + ), ), ), ); diff --git a/lib/pages/analytics/services/energy_consumption_per_device/remote_energy_consumption_per_device_service.dart b/lib/pages/analytics/services/energy_consumption_per_device/remote_energy_consumption_per_device_service.dart index 8ceb6d80..165ab5ab 100644 --- a/lib/pages/analytics/services/energy_consumption_per_device/remote_energy_consumption_per_device_service.dart +++ b/lib/pages/analytics/services/energy_consumption_per_device/remote_energy_consumption_per_device_service.dart @@ -34,10 +34,7 @@ abstract final class _EnergyConsumptionPerDeviceMapper { static List map(dynamic data) { final json = data as Map? ?? {}; final mappedData = json['data'] as List? ?? []; - return mappedData.where((e) { - final deviceData = (e as Map)['data'] as List? ?? []; - return deviceData.isNotEmpty; - }).map((e) { + return mappedData.map((e) { final deviceData = e as Map; final energyData = deviceData['data'] as List; From 625f7377912bcaf5b2a3645c937894c8321000f5 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 19 May 2025 11:05:08 +0300 Subject: [PATCH 14/25] SP-1506 rework Remove extra line. The colors of the data on X axis and Y axis are not identical to design. Display days only on the X axis. When the bar chart loads, we see it coming from the top (check the attached video). --- .../analytics_page_tabs_and_children.dart | 1 + .../fetch_energy_management_data_helper.dart | 19 +++++++++++-------- .../energy_consumption_by_phases_chart.dart | 9 +++++---- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart b/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart index 77871134..5e9e347a 100644 --- a/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart +++ b/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart @@ -74,6 +74,7 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget { FetchEnergyManagementDataHelper .loadEnergyManagementData( context, + shouldFetchAnalyticsDevices: false, selectedDate: value, communityId: spaceTreeState.selectedCommunities.firstOrNull ?? diff --git a/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart b/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart index 29da8c61..a6fe4703 100644 --- a/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart +++ b/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart @@ -26,6 +26,7 @@ abstract final class FetchEnergyManagementDataHelper { required String communityId, required String spaceId, DateTime? selectedDate, + bool shouldFetchAnalyticsDevices = true, }) { if (communityId.isEmpty && spaceId.isEmpty) { clearAllData(context); @@ -34,12 +35,16 @@ abstract final class FetchEnergyManagementDataHelper { final datePickerState = context.read().state; final selectedDate0 = selectedDate ?? datePickerState.monthlyDate; - loadAnalyticsDevices( - context, - communityUuid: communityId, - spaceUuid: spaceId, - selectedDate: selectedDate0, - ); + if (shouldFetchAnalyticsDevices) { + loadAnalyticsDevices( + context, + communityUuid: communityId, + spaceUuid: spaceId, + selectedDate: selectedDate0, + ); + loadRealtimeDeviceChanges(context); + loadPowerClampInfo(context); + } loadTotalEnergyConsumption( context, selectedDate: selectedDate0, @@ -60,8 +65,6 @@ abstract final class FetchEnergyManagementDataHelper { spaceId: spaceId, selectedDate: selectedDate0, ); - loadRealtimeDeviceChanges(context); - loadPowerClampInfo(context); } static void loadEnergyConsumptionByPhases( diff --git a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart.dart b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart.dart index 805ed551..1497d0fd 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart.dart @@ -56,7 +56,7 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget { ColorsManager.vividBlue.withValues(alpha: 0.15), ), ], - width: 16, + width: 8, borderRadius: const BorderRadius.only( topLeft: Radius.circular(8), topRight: Radius.circular(8), @@ -66,6 +66,7 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget { ); }).toList(), ), + duration: Duration.zero, ); } @@ -160,9 +161,9 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget { sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, _) { - final month = DateFormat('dd/MM').format(energyData[value.toInt()].date); + final month = DateFormat('d').format(energyData[value.toInt()].date); return FittedBox( - alignment: AlignmentDirectional.bottomCenter, + alignment: AlignmentDirectional.center, fit: BoxFit.scaleDown, child: RotatedBox( quarterTurns: 3, @@ -176,7 +177,7 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget { ), ); }, - reservedSize: 36, + reservedSize: 18, ), ); From 0d45a155e320322318b071084d34ad4adc375b37 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 19 May 2025 11:22:15 +0300 Subject: [PATCH 15/25] add step parameter in onTapFunction. Add dialogType parameter in WaterHeaterPresenceSensor and CeilingSensorDialog. Update step parameter in FlushValueSelectorWidget. Update step parameter in FunctionBloc and WaterHeaterFunctions. Update step, unit, min, and max parameters in ACFunction subclasses. --- analysis_options.yaml | 1 + .../functions_bloc/functions_bloc_bloc.dart | 10 +- lib/pages/routines/models/ac/ac_function.dart | 36 +- .../ceiling_presence_sensor_functions.dart | 42 +- .../routines/models/device_functions.dart | 37 +- .../models/flush/flush_functions.dart | 79 ++-- .../water_heater/water_heater_functions.dart | 12 +- .../routines/models/wps/wps_functions.dart | 57 +-- .../widgets/custom_routines_textbox.dart | 297 ++++++++++++++ .../widgets/routine_dialogs/ac_dialog.dart | 379 ++++++++++-------- .../ceiling_sensor/ceiling_sensor_dialog.dart | 4 +- .../cps_dialog_slider_selector.dart | 12 +- .../ceiling_sensor/cps_functions_list.dart | 51 +-- .../flush_operational_values_list.dart | 20 +- .../flush_value_selector_widget.dart | 7 +- .../helpers/routine_tap_function_helper.dart | 1 + .../one_gang_switch_dialog.dart | 162 +++----- .../three_gang_switch_dialog.dart | 200 ++++----- .../two_gang_switch_dialog.dart | 108 ++--- .../wps_value_selector_widget.dart | 17 +- .../water_heater_presence_sensor.dart | 1 + .../water_heater_value_selector_widget.dart | 133 ++---- 22 files changed, 938 insertions(+), 728 deletions(-) create mode 100644 lib/pages/routines/widgets/custom_routines_textbox.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index 81bdd00d..80a63bcb 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -10,6 +10,7 @@ analyzer: errors: constant_identifier_names: ignore + overridden_fields: ignore include: package:flutter_lints/flutter.yaml linter: diff --git a/lib/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart b/lib/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart index bb4a7a1e..659d3261 100644 --- a/lib/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart +++ b/lib/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart @@ -26,8 +26,10 @@ class FunctionBloc extends Bloc { functionCode: event.functionData.functionCode, operationName: event.functionData.operationName, value: event.functionData.value ?? existingData.value, - valueDescription: event.functionData.valueDescription ?? existingData.valueDescription, + valueDescription: event.functionData.valueDescription ?? + existingData.valueDescription, condition: event.functionData.condition ?? existingData.condition, + step: event.functionData.step ?? existingData.step, ); } else { functions.clear(); @@ -59,8 +61,10 @@ class FunctionBloc extends Bloc { ); } - FutureOr _onSelectFunction(SelectFunction event, Emitter emit) { + FutureOr _onSelectFunction( + SelectFunction event, Emitter emit) { emit(state.copyWith( - selectedFunction: event.functionCode, selectedOperationName: event.operationName)); + selectedFunction: event.functionCode, + selectedOperationName: event.operationName)); } } diff --git a/lib/pages/routines/models/ac/ac_function.dart b/lib/pages/routines/models/ac/ac_function.dart index 0b534e88..edc377dd 100644 --- a/lib/pages/routines/models/ac/ac_function.dart +++ b/lib/pages/routines/models/ac/ac_function.dart @@ -14,6 +14,10 @@ abstract class ACFunction extends DeviceFunction { required super.operationName, required super.icon, required this.type, + super.step, + super.unit, + super.max, + super.min, }); List getOperationalValues(); @@ -75,26 +79,24 @@ class ModeFunction extends ACFunction { } class TempSetFunction extends ACFunction { - final int min; - final int max; - final int step; - - TempSetFunction( - {required super.deviceId, required super.deviceName, required type}) - : min = 160, - max = 300, - step = 1, - super( + TempSetFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( code: 'temp_set', operationName: 'Set Temperature', icon: Assets.assetsTempreture, - type: type, + min: 200, + max: 300, + step: 1, + unit: "°C", ); @override List getOperationalValues() { List values = []; - for (int temp = min; temp <= max; temp += step) { + for (int temp = min!.toInt(); temp <= max!; temp += step!.toInt()) { values.add(ACOperationalValue( icon: Assets.assetsTempreture, description: "${temp / 10}°C", @@ -104,7 +106,6 @@ class TempSetFunction extends ACFunction { return values; } } - class LevelFunction extends ACFunction { LevelFunction( {required super.deviceId, required super.deviceName, required type}) @@ -166,9 +167,10 @@ class ChildLockFunction extends ACFunction { } class CurrentTempFunction extends ACFunction { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; + final String unit = "°C"; CurrentTempFunction( {required super.deviceId, required super.deviceName, required type}) @@ -185,7 +187,7 @@ class CurrentTempFunction extends ACFunction { @override List getOperationalValues() { List values = []; - for (int temp = min; temp <= max; temp += step) { + for (int temp = min.toInt(); temp <= max; temp += step.toInt()) { values.add(ACOperationalValue( icon: Assets.currentTemp, description: "${temp / 10}°C", diff --git a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart index 6dbe5cf6..122d8ea1 100644 --- a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart +++ b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart @@ -6,10 +6,12 @@ class CpsOperationalValue { final String description; final dynamic value; + CpsOperationalValue({ required this.icon, required this.description, required this.value, + }); } @@ -94,9 +96,9 @@ final class CpsSensitivityFunction extends CpsFunctions { icon: Assets.sensitivity, ); - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; static const _images = [ Assets.sensitivityFeature1, @@ -115,10 +117,10 @@ final class CpsSensitivityFunction extends CpsFunctions { @override List getOperationalValues() { final values = []; - for (var value = min; value <= max; value += step) { + for (var value = min; value <= max; value += step.toInt()) { values.add( CpsOperationalValue( - icon: _images[value], + icon: _images[value.toInt()], description: '$value', value: value, ), @@ -142,9 +144,9 @@ final class CpsMovingSpeedFunction extends CpsFunctions { icon: Assets.speedoMeter, ); - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; @override List getOperationalValues() { @@ -173,9 +175,9 @@ final class CpsSpatialStaticValueFunction extends CpsFunctions { icon: Assets.spatialStaticValue, ); - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; @override List getOperationalValues() { @@ -204,9 +206,9 @@ final class CpsSpatialMotionValueFunction extends CpsFunctions { icon: Assets.spatialMotionValue, ); - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; @override List getOperationalValues() { @@ -375,9 +377,9 @@ final class CpsPresenceJudgementThrsholdFunction extends CpsFunctions { icon: Assets.presenceJudgementThrshold, ); - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; @override List getOperationalValues() { @@ -406,9 +408,9 @@ final class CpsMotionAmplitudeTriggerThresholdFunction extends CpsFunctions { icon: Assets.presenceJudgementThrshold, ); - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; @override List getOperationalValues() { diff --git a/lib/pages/routines/models/device_functions.dart b/lib/pages/routines/models/device_functions.dart index 59b63a4f..b895dccc 100644 --- a/lib/pages/routines/models/device_functions.dart +++ b/lib/pages/routines/models/device_functions.dart @@ -4,6 +4,11 @@ abstract class DeviceFunction { final String code; final String operationName; final String icon; + final double? step; + final String? unit; + final double? max; + final double? min; + DeviceFunction({ required this.deviceId, @@ -11,6 +16,10 @@ abstract class DeviceFunction { required this.code, required this.operationName, required this.icon, + this.step, + this.unit, + this.max, + this.min, }); } @@ -22,6 +31,10 @@ class DeviceFunctionData { final dynamic value; final String? condition; final String? valueDescription; + final double? step; + final String? unit; + final double? max; + final double? min; DeviceFunctionData({ required this.entityId, @@ -31,6 +44,10 @@ class DeviceFunctionData { required this.value, this.condition, this.valueDescription, + this.step, + this.unit, + this.max, + this.min, }); Map toJson() { @@ -42,6 +59,10 @@ class DeviceFunctionData { 'value': value, if (condition != null) 'condition': condition, if (valueDescription != null) 'valueDescription': valueDescription, + if (step != null) 'step': step, + if (unit != null) 'unit': unit, + if (max != null) 'max': max, + if (min != null) 'min': min, }; } @@ -54,6 +75,10 @@ class DeviceFunctionData { value: json['value'], condition: json['condition'], valueDescription: json['valueDescription'], + step: json['step']?.toDouble(), + unit: json['unit'], + max: json['max']?.toDouble(), + min: json['min']?.toDouble(), ); } @@ -68,7 +93,11 @@ class DeviceFunctionData { other.operationName == operationName && other.value == value && other.condition == condition && - other.valueDescription == valueDescription; + other.valueDescription == valueDescription && + other.step == step && + other.unit == unit && + other.max == max && + other.min == min; } @override @@ -79,6 +108,10 @@ class DeviceFunctionData { operationName.hashCode ^ value.hashCode ^ condition.hashCode ^ - valueDescription.hashCode; + valueDescription.hashCode ^ + step.hashCode ^ + unit.hashCode ^ + max.hashCode ^ + min.hashCode; } } diff --git a/lib/pages/routines/models/flush/flush_functions.dart b/lib/pages/routines/models/flush/flush_functions.dart index 5013c0b8..a8f6ccd4 100644 --- a/lib/pages/routines/models/flush/flush_functions.dart +++ b/lib/pages/routines/models/flush/flush_functions.dart @@ -20,12 +20,11 @@ abstract class FlushFunctions } class FlushPresenceDelayFunction extends FlushFunctions { - final int min; FlushPresenceDelayFunction({ required super.deviceId, required super.deviceName, required super.type, - }) : min = 0, + }) : super( code: FlushMountedPresenceSensorModel.codePresenceState, operationName: 'Presence State', @@ -50,9 +49,9 @@ class FlushPresenceDelayFunction extends FlushFunctions { } class FlushSensiReduceFunction extends FlushFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; FlushSensiReduceFunction({ required super.deviceId, @@ -80,8 +79,8 @@ class FlushSensiReduceFunction extends FlushFunctions { } class FlushNoneDelayFunction extends FlushFunctions { - final int min; - final int max; + final double min; + final double max; final String unit; FlushNoneDelayFunction({ @@ -110,9 +109,9 @@ class FlushNoneDelayFunction extends FlushFunctions { } class FlushIlluminanceFunction extends FlushFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; FlushIlluminanceFunction({ required super.deviceId, @@ -130,7 +129,7 @@ class FlushIlluminanceFunction extends FlushFunctions { @override List getOperationalValues() { List values = []; - for (int lux = min; lux <= max; lux += step) { + for (int lux = min.toInt(); lux <= max; lux += step.toInt()) { values.add(FlushOperationalValue( icon: Assets.IlluminanceIcon, description: "$lux Lux", @@ -142,9 +141,9 @@ class FlushIlluminanceFunction extends FlushFunctions { } class FlushOccurDistReduceFunction extends FlushFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; FlushOccurDistReduceFunction({ required super.deviceId, @@ -173,9 +172,9 @@ class FlushOccurDistReduceFunction extends FlushFunctions { // ==== then functions ==== class FlushSensitivityFunction extends FlushFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; FlushSensitivityFunction({ required super.deviceId, @@ -203,9 +202,9 @@ class FlushSensitivityFunction extends FlushFunctions { } class FlushNearDetectionFunction extends FlushFunctions { - final int min; + final double min; final double max; - final int step; + final double step; final String unit; FlushNearDetectionFunction({ @@ -225,7 +224,7 @@ class FlushNearDetectionFunction extends FlushFunctions { @override List getOperationalValues() { final values = []; - for (var value = min; value <= max; value += step) { + for (var value = min.toDouble(); value <= max; value += step) { values.add(FlushOperationalValue( icon: Assets.nobodyTime, description: '$value $unit', @@ -237,9 +236,9 @@ class FlushNearDetectionFunction extends FlushFunctions { } class FlushMaxDetectDistFunction extends FlushFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; final String unit; FlushMaxDetectDistFunction({ @@ -259,7 +258,7 @@ class FlushMaxDetectDistFunction extends FlushFunctions { @override List getOperationalValues() { final values = []; - for (var value = min; value <= max; value += step) { + for (var value = min; value <= max; value += step.toInt()) { values.add(FlushOperationalValue( icon: Assets.nobodyTime, description: '$value $unit', @@ -271,9 +270,9 @@ class FlushMaxDetectDistFunction extends FlushFunctions { } class FlushTargetConfirmTimeFunction extends FlushFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; final String unit; FlushTargetConfirmTimeFunction({ @@ -293,7 +292,7 @@ class FlushTargetConfirmTimeFunction extends FlushFunctions { @override List getOperationalValues() { final values = []; - for (var value = min; value <= max; value += step) { + for (var value = min.toDouble(); value <= max; value += step) { values.add(FlushOperationalValue( icon: Assets.nobodyTime, description: '$value $unit', @@ -305,9 +304,9 @@ class FlushTargetConfirmTimeFunction extends FlushFunctions { } class FlushDisappeDelayFunction extends FlushFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; final String unit; FlushDisappeDelayFunction({ @@ -327,7 +326,7 @@ class FlushDisappeDelayFunction extends FlushFunctions { @override List getOperationalValues() { final values = []; - for (var value = min; value <= max; value += step) { + for (var value = min.toDouble(); value <= max; value += step) { values.add(FlushOperationalValue( icon: Assets.nobodyTime, description: '$value $unit', @@ -339,9 +338,9 @@ class FlushDisappeDelayFunction extends FlushFunctions { } class FlushIndentLevelFunction extends FlushFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; final String unit; FlushIndentLevelFunction({ @@ -361,7 +360,7 @@ class FlushIndentLevelFunction extends FlushFunctions { @override List getOperationalValues() { final values = []; - for (var value = min; value <= max; value += step) { + for (var value = min.toDouble(); value <= max; value += step) { values.add(FlushOperationalValue( icon: Assets.nobodyTime, description: '$value $unit', @@ -373,9 +372,9 @@ class FlushIndentLevelFunction extends FlushFunctions { } class FlushTriggerLevelFunction extends FlushFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; final String unit; FlushTriggerLevelFunction({ @@ -395,7 +394,7 @@ class FlushTriggerLevelFunction extends FlushFunctions { @override List getOperationalValues() { final values = []; - for (var value = min; value <= max; value += step) { + for (var value = min.toDouble(); value <= max; value += step) { values.add(FlushOperationalValue( icon: Assets.nobodyTime, description: '$value $unit', diff --git a/lib/pages/routines/models/water_heater/water_heater_functions.dart b/lib/pages/routines/models/water_heater/water_heater_functions.dart index 571e15f7..7ebea019 100644 --- a/lib/pages/routines/models/water_heater/water_heater_functions.dart +++ b/lib/pages/routines/models/water_heater/water_heater_functions.dart @@ -20,17 +20,16 @@ abstract class WaterHeaterFunctions } class WHRestartStatusFunction extends WaterHeaterFunctions { - final int min; WHRestartStatusFunction({ required super.deviceId, required super.deviceName, required super.type, - }) : min = 0, - super( + }) : super( code: 'relay_status', operationName: 'Restart Status', icon: Assets.refreshStatusIcon, ); + @override List getOperationalValues() { @@ -55,13 +54,11 @@ class WHRestartStatusFunction extends WaterHeaterFunctions { } class WHSwitchFunction extends WaterHeaterFunctions { - final int min; WHSwitchFunction({ required super.deviceId, required super.deviceName, required super.type, - }) : min = 0, - super( + }) : super( code: 'switch_1', operationName: 'Switch', icon: Assets.assetsAcPower, @@ -104,12 +101,11 @@ class TimerConfirmTimeFunction extends WaterHeaterFunctions { } class BacklightFunction extends WaterHeaterFunctions { - final int min; BacklightFunction({ required super.deviceId, required super.deviceName, required super.type, - }) : min = 0, + }) : super( code: 'switch_backlight', operationName: 'Backlight', diff --git a/lib/pages/routines/models/wps/wps_functions.dart b/lib/pages/routines/models/wps/wps_functions.dart index 8907927c..101c5cf0 100644 --- a/lib/pages/routines/models/wps/wps_functions.dart +++ b/lib/pages/routines/models/wps/wps_functions.dart @@ -4,7 +4,7 @@ import 'package:syncrow_web/pages/routines/models/wps/wps_operational_value.dart import 'package:syncrow_web/utils/constants/assets.dart'; abstract class WpsFunctions extends DeviceFunction { - final String type; + final String type; WpsFunctions({ required super.deviceId, @@ -13,6 +13,10 @@ abstract class WpsFunctions extends DeviceFunction { required super.operationName, required super.icon, required this.type, + super.step, + super.unit, + super.max, + super.min, }); List getOperationalValues(); @@ -20,9 +24,13 @@ abstract class WpsFunctions extends DeviceFunction { // For far_detection (75-600cm in 75cm steps) class FarDetectionFunction extends WpsFunctions { - final int min; - final int max; - final int step; + + final double min; + @override + final double max; + @override + final double step; + @override final String unit; FarDetectionFunction( @@ -41,7 +49,7 @@ class FarDetectionFunction extends WpsFunctions { @override List getOperationalValues() { final values = []; - for (var value = min; value <= max; value += step) { + for (var value = min; value <= max; value += step.toInt()) { values.add(WpsOperationalValue( icon: Assets.currentDistanceIcon, description: '$value $unit', @@ -54,9 +62,9 @@ class FarDetectionFunction extends WpsFunctions { // For presence_time (0-65535 minutes) class PresenceTimeFunction extends WpsFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; final String unit; PresenceTimeFunction( @@ -86,9 +94,9 @@ class PresenceTimeFunction extends WpsFunctions { // For motion_sensitivity_value (1-5 levels) class MotionSensitivityFunction extends WpsFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; MotionSensitivityFunction( {required super.deviceId, required super.deviceName, required type}) @@ -116,9 +124,9 @@ class MotionSensitivityFunction extends WpsFunctions { } class MotionLessSensitivityFunction extends WpsFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; MotionLessSensitivityFunction( {required super.deviceId, required super.deviceName, required type}) @@ -171,8 +179,8 @@ class IndicatorFunction extends WpsFunctions { } class NoOneTimeFunction extends WpsFunctions { - final int min; - final int max; + final double min; + final double max; final String unit; NoOneTimeFunction( @@ -225,9 +233,9 @@ class PresenceStateFunction extends WpsFunctions { } class CurrentDistanceFunction extends WpsFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; CurrentDistanceFunction( {required super.deviceId, required super.deviceName, required type}) @@ -244,11 +252,10 @@ class CurrentDistanceFunction extends WpsFunctions { @override List getOperationalValues() { List values = []; - for (int cm = min; cm <= max; cm += step) { + for (int cm = min.toInt(); cm <= max; cm += step.toInt()) { values.add(WpsOperationalValue( icon: Assets.assetsTempreture, description: "${cm}CM", - value: cm, )); } @@ -257,9 +264,9 @@ class CurrentDistanceFunction extends WpsFunctions { } class IlluminanceValueFunction extends WpsFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; IlluminanceValueFunction({ required super.deviceId, @@ -277,7 +284,7 @@ class IlluminanceValueFunction extends WpsFunctions { @override List getOperationalValues() { List values = []; - for (int lux = min; lux <= max; lux += step) { + for (int lux = min.toInt(); lux <= max; lux += step.toInt()) { values.add(WpsOperationalValue( icon: Assets.IlluminanceIcon, description: "$lux Lux", diff --git a/lib/pages/routines/widgets/custom_routines_textbox.dart b/lib/pages/routines/widgets/custom_routines_textbox.dart new file mode 100644 index 00000000..e9ada1c2 --- /dev/null +++ b/lib/pages/routines/widgets/custom_routines_textbox.dart @@ -0,0 +1,297 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:syncrow_web/pages/routines/widgets/condition_toggle.dart'; +import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class CustomRoutinesTextbox extends StatefulWidget { + final String? currentCondition; + final String dialogType; + final (double, double) sliderRange; + final dynamic displayedValue; + final dynamic initialValue; + final void Function(String condition) onConditionChanged; + final void Function(double value) onTextChanged; + final String unit; + final double dividendOfRange; + final double stepIncreaseAmount; + final bool withSpecialChar; + + const CustomRoutinesTextbox({ + required this.dialogType, + required this.sliderRange, + required this.displayedValue, + required this.initialValue, + required this.onConditionChanged, + required this.onTextChanged, + required this.currentCondition, + required this.unit, + required this.dividendOfRange, + required this.stepIncreaseAmount, + required this.withSpecialChar, + super.key, + }); + + @override + State createState() => _CustomRoutinesTextboxState(); +} + +class _CustomRoutinesTextboxState extends State { + late final TextEditingController _controller; + bool hasError = false; + String? errorMessage; + + int getDecimalPlaces(double step) { + String stepStr = step.toString(); + if (stepStr.contains('.')) { + List parts = stepStr.split('.'); + String decimalPart = parts[1]; + decimalPart = decimalPart.replaceAll(RegExp(r'0+$'), ''); + return decimalPart.isEmpty ? 0 : decimalPart.length; + } else { + return 0; + } + } + + @override + void initState() { + super.initState(); + int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount); + double initialValue; + if (widget.initialValue != null && + widget.initialValue is num && + (widget.initialValue as num) == 0) { + initialValue = 0.0; + } else { + initialValue = double.tryParse(widget.displayedValue) ?? 0.0; + } + _controller = TextEditingController( + text: initialValue.toStringAsFixed(decimalPlaces), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + void _validateInput(String value) { + final doubleValue = double.tryParse(value); + if (doubleValue == null) { + setState(() { + errorMessage = "Invalid number"; + hasError = true; + }); + return; + } + + final min = widget.sliderRange.$1; + final max = widget.sliderRange.$2; + + if (doubleValue < min) { + setState(() { + errorMessage = "Value must be at least $min"; + hasError = true; + }); + } else if (doubleValue > max) { + setState(() { + errorMessage = "Value must be at most $max"; + hasError = true; + }); + } else { + int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount); + int factor = pow(10, decimalPlaces).toInt(); + int scaledStep = (widget.stepIncreaseAmount * factor).round(); + int scaledValue = (doubleValue * factor).round(); + + if (scaledValue % scaledStep != 0) { + setState(() { + errorMessage = "must be a multiple of ${widget.stepIncreaseAmount}"; + hasError = true; + }); + } else { + setState(() { + errorMessage = null; + hasError = false; + }); + } + } + } + + @override + void didUpdateWidget(CustomRoutinesTextbox oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.initialValue != oldWidget.initialValue) { + if (widget.initialValue != null && + widget.initialValue is num && + (widget.initialValue as num) == 0) { + int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount); + _controller.text = 0.0.toStringAsFixed(decimalPlaces); + } + } + } + + void _correctAndUpdateValue(String value) { + final doubleValue = double.tryParse(value) ?? 0.0; + int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount); + double rounded = (doubleValue / widget.stepIncreaseAmount).round() * + widget.stepIncreaseAmount; + rounded = rounded.clamp(widget.sliderRange.$1, widget.sliderRange.$2); + rounded = double.parse(rounded.toStringAsFixed(decimalPlaces)); + + setState(() { + hasError = false; + errorMessage = null; + }); + + _controller.text = rounded.toStringAsFixed(decimalPlaces); + _controller.selection = TextSelection.fromPosition( + TextPosition(offset: _controller.text.length), + ); + widget.onTextChanged(rounded); + } + + @override + Widget build(BuildContext context) { + int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount); + + List formatters = []; + if (decimalPlaces == 0) { + formatters.add(FilteringTextInputFormatter.digitsOnly); + } else { + formatters.add(FilteringTextInputFormatter.allow( + RegExp(r'^\d*\.?\d{0,' + decimalPlaces.toString() + r'}$'), + )); + } + formatters.add(RangeInputFormatter( + min: widget.sliderRange.$1, + max: widget.sliderRange.$2, + )); + + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (widget.dialogType == 'IF') + ConditionToggle( + currentCondition: widget.currentCondition, + onChanged: widget.onConditionChanged, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 35, vertical: 2), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + 'Step: ${widget.stepIncreaseAmount}', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.grayColor, + fontSize: 10, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + Center( + child: Container( + width: 170, + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + decoration: BoxDecoration( + color: const Color(0xFFF8F8F8), + borderRadius: BorderRadius.circular(20), + border: hasError + ? Border.all(color: Colors.red, width: 1) + : Border.all( + color: ColorsManager.lightGrayBorderColor, width: 1), + boxShadow: [ + BoxShadow( + color: ColorsManager.blackColor.withOpacity(0.05), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Row( + children: [ + Expanded( + child: TextFormField( + controller: _controller, + style: context.textTheme.bodyLarge?.copyWith( + fontSize: 20, + fontWeight: FontWeight.bold, + color: ColorsManager.blackColor, + ), + keyboardType: TextInputType.number, + inputFormatters: widget.withSpecialChar == true + ? [FilteringTextInputFormatter.digitsOnly] + : null, + decoration: const InputDecoration( + border: InputBorder.none, + isDense: true, + contentPadding: EdgeInsets.zero, + ), + onChanged: _validateInput, + onFieldSubmitted: _correctAndUpdateValue, + onTapOutside: (_) => + _correctAndUpdateValue(_controller.text), + ), + ), + const SizedBox(width: 12), + Text( + widget.unit, + style: context.textTheme.bodyMedium?.copyWith( + fontSize: 20, + fontWeight: FontWeight.bold, + color: ColorsManager.vividBlue, + ), + ), + ], + ), + ), + ), + if (errorMessage != null) + Padding( + padding: const EdgeInsets.only(top: 2.0), + child: Text( + errorMessage!, + style: context.textTheme.bodySmall?.copyWith( + color: Colors.red, + fontSize: 10, + ), + ), + ), + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Min. ${widget.sliderRange.$1.toInt()}${widget.unit}', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.grayColor, + fontSize: 10, + fontWeight: FontWeight.w400, + ), + ), + Text( + 'Max. ${widget.sliderRange.$2.toInt()}${widget.unit}', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.grayColor, + fontSize: 10, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + const SizedBox(height: 16), + ], + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart index 1de9b0d4..fc58500e 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart @@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart'; 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/custom_routines_textbox.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'; @@ -76,24 +77,20 @@ class ACHelper { context: context, acFunctions: acFunctions, device: device, - onFunctionSelected: (functionCode, operationName) { + 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, - ); + context, + functionCode: functionCode, + functionOperationName: operationName, + functionValueDescription: + selectedFunctionData.valueDescription, + deviceUuid: device?.uuid, + codesToAddIntoFunctionsWithDefaultValue: [ + 'temp_set', + 'temp_current', + ], + defaultValue: 0); }, ), ), @@ -206,27 +203,61 @@ class ACHelper { required String operationName, bool? removeComparators, }) { - final initialVal = selectedFunction == 'temp_set' ? 200 : -100; + final selectedFn = + acFunctions.firstWhere((f) => f.code == selectedFunction); + if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') { - final initialValue = selectedFunctionData?.value ?? initialVal; - return _buildTemperatureSelector( - context: context, - initialValue: initialValue, - selectCode: selectedFunction, + // Convert stored integer value to display value + final displayValue = + (selectedFunctionData?.value ?? selectedFn.min ?? 0) / 10; + final minValue = selectedFn.min! / 10; + final maxValue = selectedFn.max! / 10; + return CustomRoutinesTextbox( + withSpecialChar: true, + dividendOfRange: maxValue, currentCondition: selectedFunctionData?.condition, - device: device, - operationName: operationName, - selectedFunctionData: selectedFunctionData, - removeComparators: removeComparators, + dialogType: selectedFn.type, + sliderRange: (minValue, maxValue), + displayedValue: displayValue.toStringAsFixed(1), + initialValue: displayValue.toDouble(), + unit: selectedFn.unit!, + onConditionChanged: (condition) => context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectedFunction, + operationName: selectedFn.operationName, + condition: condition, + value: 0, + step: selectedFn.step, + unit: selectedFn.unit, + max: selectedFn.max, + min: selectedFn.min, + ), + ), + ), + onTextChanged: (value) => context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectedFunction, + operationName: selectedFn.operationName, + value: (value * 10).round(), // Store as integer + condition: selectedFunctionData?.condition, + step: selectedFn.step, + unit: selectedFn.unit, + max: selectedFn.max, + min: selectedFn.min, + ), + ), + ), + stepIncreaseAmount: selectedFn.step! / 10, // Convert step for display ); } - final selectedFn = acFunctions.firstWhere((f) => f.code == selectedFunction); - final values = selectedFn.getOperationalValues(); - return _buildOperationalValuesList( context: context, - values: values, + values: selectedFn.getOperationalValues(), selectedValue: selectedFunctionData?.value, device: device, operationName: operationName, @@ -235,150 +266,151 @@ class ACHelper { ); } - /// Build temperature selector for AC functions dialog - static Widget _buildTemperatureSelector({ - required BuildContext context, - required dynamic initialValue, - required String? currentCondition, - required String selectCode, - AllDevicesModel? device, - required String operationName, - DeviceFunctionData? selectedFunctionData, - bool? removeComparators, - }) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (removeComparators != true) - _buildConditionToggle( - context, - currentCondition, - selectCode, - device, - operationName, - selectedFunctionData, - ), - const SizedBox(height: 20), - _buildTemperatureDisplay( - context, - initialValue, - device, - operationName, - selectedFunctionData, - selectCode, - ), - const SizedBox(height: 20), - _buildTemperatureSlider( - context, - initialValue, - device, - operationName, - selectedFunctionData, - selectCode, - ), - ], - ); - } + // /// Build temperature selector for AC functions dialog + // static Widget _buildTemperatureSelector({ + // required BuildContext context, + // required dynamic initialValue, + // required String? currentCondition, + // required String selectCode, + // AllDevicesModel? device, + // required String operationName, + // DeviceFunctionData? selectedFunctionData, + // bool? removeComparators, + // }) { + // return Column( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // if (removeComparators != true) + // _buildConditionToggle( + // context, + // currentCondition, + // selectCode, + // device, + // operationName, + // selectedFunctionData, + // ), + // const SizedBox(height: 20), + // _buildTemperatureDisplay( + // context, + // initialValue, + // device, + // operationName, + // selectedFunctionData, + // selectCode, + // ), + // const SizedBox(height: 20), + // _buildTemperatureSlider( + // context, + // initialValue, + // device, + // operationName, + // selectedFunctionData, + // selectCode, + // ), + // ], + // ); + // } - /// Build condition toggle for AC functions dialog - static Widget _buildConditionToggle( - BuildContext context, - String? currentCondition, - String selectCode, - AllDevicesModel? device, - String operationName, - DeviceFunctionData? selectedFunctionData, + // /// Build condition toggle for AC functions dialog + // static Widget _buildConditionToggle( + // BuildContext context, + // String? currentCondition, + // String selectCode, + // AllDevicesModel? device, + // String operationName, + // DeviceFunctionData? selectedFunctionData, - // Function(String) onConditionChanged, - ) { - final conditions = ["<", "==", ">"]; + // // Function(String) onConditionChanged, + // ) { + // final conditions = ["<", "==", ">"]; - return ToggleButtons( - onPressed: (int index) { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: selectCode, - operationName: operationName, - condition: conditions[index], - value: selectedFunctionData?.value ?? selectCode == 'temp_set' - ? 200 - : -100, - valueDescription: selectedFunctionData?.valueDescription, - ), - ), - ); - }, - borderRadius: const BorderRadius.all(Radius.circular(8)), - selectedBorderColor: ColorsManager.primaryColorWithOpacity, - selectedColor: Colors.white, - fillColor: ColorsManager.primaryColorWithOpacity, - color: ColorsManager.primaryColorWithOpacity, - constraints: const BoxConstraints( - minHeight: 40.0, - minWidth: 40.0, - ), - isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), - children: conditions.map((c) => Text(c)).toList(), - ); - } + // return ToggleButtons( + // onPressed: (int index) { + // context.read().add( + // AddFunction( + // functionData: DeviceFunctionData( + // entityId: device?.uuid ?? '', + // functionCode: selectCode, + // operationName: operationName, + // condition: conditions[index], + // value: selectedFunctionData?.value ?? selectCode == 'temp_set' + // ? 200 + // : -100, + // valueDescription: selectedFunctionData?.valueDescription, + // ), + // ), + // ); + // }, + // borderRadius: const BorderRadius.all(Radius.circular(8)), + // selectedBorderColor: ColorsManager.primaryColorWithOpacity, + // selectedColor: Colors.white, + // fillColor: ColorsManager.primaryColorWithOpacity, + // color: ColorsManager.primaryColorWithOpacity, + // constraints: const BoxConstraints( + // minHeight: 40.0, + // minWidth: 40.0, + // ), + // isSelected: + // conditions.map((c) => c == (currentCondition ?? "==")).toList(), + // children: conditions.map((c) => Text(c)).toList(), + // ); + // } - /// Build temperature display for AC functions dialog - static Widget _buildTemperatureDisplay( - BuildContext context, - dynamic initialValue, - AllDevicesModel? device, - String operationName, - DeviceFunctionData? selectedFunctionData, - String selectCode, - ) { - final initialVal = selectCode == 'temp_set' ? 200 : -100; - return Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - decoration: BoxDecoration( - color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Text( - '${(initialValue ?? initialVal) / 10}°C', - style: context.textTheme.headlineMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), - ), - ); - } + // /// Build temperature display for AC functions dialog + // static Widget _buildTemperatureDisplay( + // BuildContext context, + // dynamic initialValue, + // AllDevicesModel? device, + // String operationName, + // DeviceFunctionData? selectedFunctionData, + // String selectCode, + // ) { + // final initialVal = selectCode == 'temp_set' ? 200 : -100; + // return Container( + // padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + // decoration: BoxDecoration( + // color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), + // borderRadius: BorderRadius.circular(10), + // ), + // child: Text( + // '${(initialValue ?? initialVal) / 10}°C', + // style: context.textTheme.headlineMedium!.copyWith( + // color: ColorsManager.primaryColorWithOpacity, + // ), + // ), + // ); + // } - static Widget _buildTemperatureSlider( - BuildContext context, - dynamic initialValue, - AllDevicesModel? device, - String operationName, - DeviceFunctionData? selectedFunctionData, - String selectCode, - ) { - return Slider( - value: initialValue is int ? initialValue.toDouble() : 200.0, - min: selectCode == 'temp_current' ? -100 : 200, - max: selectCode == 'temp_current' ? 900 : 300, - divisions: 10, - label: '${((initialValue ?? 160) / 10).toInt()}°C', - onChanged: (value) { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: selectCode, - operationName: operationName, - value: value, - condition: selectedFunctionData?.condition, - valueDescription: selectedFunctionData?.valueDescription, - ), - ), - ); - }, - ); - } + // static Widget _buildTemperatureSlider( + // BuildContext context, + // dynamic initialValue, + // AllDevicesModel? device, + // String operationName, + // DeviceFunctionData? selectedFunctionData, + // String selectCode, + // ) { + // return Slider( + // value: initialValue is int ? initialValue.toDouble() : 200.0, + // min: selectCode == 'temp_current' ? -100 : 200, + // max: selectCode == 'temp_current' ? 900 : 300, + // divisions: 10, + // label: '${((initialValue ?? 160) / 10).toInt()}°C', + // onChanged: (value) { + // context.read().add( + // AddFunction( + // functionData: DeviceFunctionData( + // entityId: device?.uuid ?? '', + // functionCode: selectCode, + // operationName: operationName, + // value: value, + // condition: selectedFunctionData?.condition, + // valueDescription: selectedFunctionData?.valueDescription, + // ), + // ), + // ); + // }, + // ); + // } static Widget _buildOperationalValuesList({ required BuildContext context, @@ -414,7 +446,9 @@ class ACHelper { 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 @@ -430,7 +464,8 @@ class ACHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: selectedFunctionData?.valueDescription, + valueDescription: + selectedFunctionData?.valueDescription, ), ), ); diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart index f3d07a66..8fab09e8 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart @@ -41,7 +41,8 @@ class _CeilingSensorDialogState extends State { void initState() { super.initState(); - _cpsFunctions = widget.functions.whereType().where((function) { + _cpsFunctions = + widget.functions.whereType().where((function) { if (widget.dialogType == 'THEN') { return function.type == 'THEN' || function.type == 'BOTH'; } @@ -149,6 +150,7 @@ class _CeilingSensorDialogState extends State { device: widget.device, ) : CpsDialogSliderSelector( + step: selectedCpsFunctions.step!, operations: operations, selectedFunction: selectedFunction ?? '', selectedFunctionData: selectedFunctionData, diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart index cd8e4c46..3d2473c9 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart @@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.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/custom_routines_textbox.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart'; import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart'; @@ -16,6 +17,7 @@ class CpsDialogSliderSelector extends StatelessWidget { required this.device, required this.operationName, required this.dialogType, + required this.step, super.key, }); @@ -26,13 +28,16 @@ class CpsDialogSliderSelector extends StatelessWidget { final AllDevicesModel? device; final String operationName; final String dialogType; + final double step; @override Widget build(BuildContext context) { - return SliderValueSelector( + return CustomRoutinesTextbox( + withSpecialChar: false, currentCondition: selectedFunctionData.condition, dialogType: dialogType, - sliderRange: CpsSliderHelpers.sliderRange(selectedFunctionData.functionCode), + sliderRange: + CpsSliderHelpers.sliderRange(selectedFunctionData.functionCode), displayedValue: CpsSliderHelpers.displayText( value: selectedFunctionData.value, functionCode: selectedFunctionData.functionCode, @@ -50,7 +55,7 @@ class CpsDialogSliderSelector extends StatelessWidget { ), ), ), - onSliderChanged: (value) => context.read().add( + onTextChanged: (value) => context.read().add( AddFunction( functionData: DeviceFunctionData( entityId: device?.uuid ?? '', @@ -64,6 +69,7 @@ class CpsDialogSliderSelector extends StatelessWidget { dividendOfRange: CpsSliderHelpers.dividendOfRange( selectedFunctionData.functionCode, ), + stepIncreaseAmount: step, ); } } diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart index efc57653..d11871a7 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart @@ -34,30 +34,33 @@ class CpsFunctionsList extends StatelessWidget { itemBuilder: (context, index) { final function = cpsFunctions[index]; return RoutineDialogFunctionListTile( - iconPath: function.icon, - 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', - ], - ), - ); + iconPath: function.icon, + operationName: function.operationName, + onTap: () { + RoutineTapFunctionHelper.onTapFunction( + context, + step: function.step, + 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', + ], + ); + }); }, ), ); diff --git a/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart b/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart index 3e618c35..4c780058 100644 --- a/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart +++ b/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/routines/models/flush/flush_operational_value.dart'; @@ -21,22 +22,20 @@ class FlushOperationalValuesList extends StatelessWidget { @override Widget build(BuildContext context) { - return ListView.builder( - padding: const EdgeInsets.all(20), - itemCount: values.length, - itemBuilder: (context, index) => - _buildValueItem(context, values[index]), - ); + return ListView.builder( + padding: const EdgeInsets.all(20), + itemCount: values.length, + itemBuilder: (context, index) => _buildValueItem(context, values[index]), + ); } - - Widget _buildValueItem(BuildContext context, FlushOperationalValue value) { return Padding( padding: const EdgeInsets.all(8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + SvgPicture.asset(value.icon, width: 25, height: 25), Expanded(child: _buildValueDescription(value)), _buildValueRadio(context, value), ], @@ -44,9 +43,6 @@ class FlushOperationalValuesList extends StatelessWidget { ); } - - - Widget _buildValueDescription(FlushOperationalValue value) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), @@ -60,6 +56,4 @@ class FlushOperationalValuesList extends StatelessWidget { groupValue: selectedValue, onChanged: (_) => onSelect(value)); } - - } diff --git a/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_value_selector_widget.dart b/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_value_selector_widget.dart index 64f060e5..7ca89edb 100644 --- a/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_value_selector_widget.dart +++ b/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_value_selector_widget.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/flush/flush_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart'; import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart'; @@ -66,7 +67,8 @@ class FlushValueSelectorWidget extends StatelessWidget { if (isDistanceDetection) { initialValue = initialValue / 100; } - return SliderValueSelector( + return CustomRoutinesTextbox( + withSpecialChar: true, currentCondition: functionData.condition, dialogType: dialogType, sliderRange: sliderRange, @@ -83,7 +85,7 @@ class FlushValueSelectorWidget extends StatelessWidget { ), ), ), - onSliderChanged: (value) { + onTextChanged: (value) { final roundedValue = _roundToStep(value, stepSize); final finalValue = isDistanceDetection ? (roundedValue * 100).toInt() : roundedValue; @@ -102,6 +104,7 @@ class FlushValueSelectorWidget extends StatelessWidget { }, unit: _unit, dividendOfRange: stepSize, + stepIncreaseAmount: stepSize, ); } diff --git a/lib/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart b/lib/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart index 2b09f579..a3223abd 100644 --- a/lib/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart +++ b/lib/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart @@ -8,6 +8,7 @@ abstract final class RoutineTapFunctionHelper { static void onTapFunction( BuildContext context, { + double? step, required String functionCode, required String functionOperationName, required String? functionValueDescription, diff --git a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart index 3c786045..641fd234 100644 --- a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart @@ -4,11 +4,11 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.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'; @@ -87,14 +87,15 @@ class OneGangSwitchHelper { size: 16, color: ColorsManager.textGray, ), - onTap: () => - RoutineTapFunctionHelper.onTapFunction( + onTap: () => RoutineTapFunctionHelper + .onTapFunction( context, functionCode: function.code, functionOperationName: function.operationName, functionValueDescription: - selectedFunctionData.valueDescription, + selectedFunctionData + .valueDescription, deviceUuid: device?.uuid, codesToAddIntoFunctionsWithDefaultValue: [ 'countdown_1', @@ -108,14 +109,16 @@ class OneGangSwitchHelper { if (selectedFunction != null) Expanded( child: _buildValueSelector( - context: context, - selectedFunction: selectedFunction, - selectedFunctionData: selectedFunctionData, - acFunctions: oneGangFunctions, - device: device, - operationName: selectedOperationName ?? '', - removeComparetors: removeComparetors, - ), + context: context, + selectedFunction: selectedFunction, + selectedFunctionData: + selectedFunctionData, + acFunctions: oneGangFunctions, + device: device, + operationName: + selectedOperationName ?? '', + removeComparetors: removeComparetors, + dialogType: dialogType), ), ], ), @@ -172,6 +175,7 @@ class OneGangSwitchHelper { AllDevicesModel? device, required String operationName, required bool removeComparetors, + required String dialogType, }) { if (selectedFunction == 'countdown_1') { final initialValue = selectedFunctionData?.value ?? 0; @@ -184,6 +188,7 @@ class OneGangSwitchHelper { operationName: operationName, selectedFunctionData: selectedFunctionData, removeComparetors: removeComparetors, + dialogType: dialogType, ); } final selectedFn = acFunctions.firstWhere( @@ -216,93 +221,18 @@ class OneGangSwitchHelper { required String operationName, DeviceFunctionData? selectedFunctionData, required bool removeComparetors, + String? dialogType, }) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - if (removeComparetors != true) - _buildConditionToggle( - context, - currentCondition, - selectCode, - device, - operationName, - selectedFunctionData, - ), - const SizedBox(height: 20), - _buildCountDownDisplay(context, initialValue, device, operationName, - selectedFunctionData, selectCode), const SizedBox(height: 20), _buildCountDownSlider(context, initialValue, device, operationName, - selectedFunctionData, selectCode), + selectedFunctionData, selectCode, dialogType!), ], ); } - /// Build condition toggle for AC functions dialog - static Widget _buildConditionToggle( - BuildContext context, - String? currentCondition, - String selectCode, - AllDevicesModel? device, - String operationName, - DeviceFunctionData? selectedFunctionData, - // Function(String) onConditionChanged, - ) { - final conditions = ["<", "==", ">"]; - - return ToggleButtons( - onPressed: (int index) { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: selectCode, - operationName: operationName, - condition: conditions[index], - value: selectedFunctionData?.value ?? 0, - valueDescription: selectedFunctionData?.valueDescription, - ), - ), - ); - }, - borderRadius: const BorderRadius.all(Radius.circular(8)), - selectedBorderColor: ColorsManager.primaryColorWithOpacity, - selectedColor: Colors.white, - fillColor: ColorsManager.primaryColorWithOpacity, - color: ColorsManager.primaryColorWithOpacity, - constraints: const BoxConstraints( - minHeight: 40.0, - minWidth: 40.0, - ), - isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), - children: conditions.map((c) => Text(c)).toList(), - ); - } - - /// Build temperature display for AC functions dialog - static Widget _buildCountDownDisplay( - BuildContext context, - dynamic initialValue, - AllDevicesModel? device, - String operationName, - DeviceFunctionData? selectedFunctionData, - String selectCode) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - decoration: BoxDecoration( - color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Text( - DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0), - style: context.textTheme.headlineMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), - ), - ); - } - static Widget _buildCountDownSlider( BuildContext context, dynamic initialValue, @@ -310,38 +240,47 @@ class OneGangSwitchHelper { String operationName, DeviceFunctionData? selectedFunctionData, String selectCode, + String dialogType, ) { - const twelveHoursInSeconds = 43200.0; - final operationalValues = SwitchOperationalValue( - icon: '', - description: "sec", - value: 0.0, - minValue: 0, - maxValue: twelveHoursInSeconds, - stepValue: 1, - ); - return Slider( - 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(), - onChanged: (value) { + return CustomRoutinesTextbox( + withSpecialChar: false, + currentCondition: selectedFunctionData?.condition, + dialogType: dialogType, + sliderRange: (0, 43200), + displayedValue: (initialValue ?? 0).toString(), + initialValue: (initialValue ?? 0).toString(), + onConditionChanged: (condition) { context.read().add( AddFunction( functionData: DeviceFunctionData( entityId: device?.uuid ?? '', functionCode: selectCode, operationName: operationName, - value: value, + condition: condition, + value: selectedFunctionData?.value ?? 0, + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); + }, + onTextChanged: (value) { + final roundedValue = value.round(); + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: roundedValue, condition: selectedFunctionData?.condition, valueDescription: selectedFunctionData?.valueDescription, ), ), ); }, + unit: 'sec', + dividendOfRange: 1, + stepIncreaseAmount: 1, ); } @@ -377,7 +316,9 @@ class OneGangSwitchHelper { 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 @@ -393,7 +334,8 @@ class OneGangSwitchHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: selectedFunctionData?.valueDescription, + valueDescription: + selectedFunctionData?.valueDescription, ), ), ); diff --git a/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart index 44a367ef..5e50c11d 100644 --- a/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart @@ -4,10 +4,10 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.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'; @@ -86,20 +86,21 @@ class ThreeGangSwitchHelper { size: 16, color: ColorsManager.textGray, ), - onTap: () => - RoutineTapFunctionHelper.onTapFunction( + onTap: () => RoutineTapFunctionHelper + .onTapFunction( context, functionCode: function.code, functionOperationName: function.operationName, functionValueDescription: - selectedFunctionData.valueDescription, + selectedFunctionData + .valueDescription, deviceUuid: device?.uuid, - codesToAddIntoFunctionsWithDefaultValue: [ - 'countdown_1', - 'countdown_2', - 'countdown_3', - ], + codesToAddIntoFunctionsWithDefaultValue: + function.code + .startsWith('countdown') + ? [function.code] + : [], ), ); }, @@ -109,14 +110,16 @@ class ThreeGangSwitchHelper { if (selectedFunction != null) Expanded( child: _buildValueSelector( - context: context, - selectedFunction: selectedFunction, - selectedFunctionData: selectedFunctionData, - switchFunctions: switchFunctions, - device: device, - operationName: selectedOperationName ?? '', - removeComparetors: removeComparetors, - ), + context: context, + selectedFunction: selectedFunction, + selectedFunctionData: + selectedFunctionData, + switchFunctions: switchFunctions, + device: device, + operationName: + selectedOperationName ?? '', + removeComparetors: removeComparetors, + dialogType: dialogType), ), ], ), @@ -133,14 +136,6 @@ class ThreeGangSwitchHelper { onConfirm: state.addedFunctions.isNotEmpty ? () { /// add the functions to the routine bloc - // for (var function in state.addedFunctions) { - // context.read().add( - // AddFunctionToRoutine( - // function, - // uniqueCustomId, - // ), - // ); - // } context.read().add( AddFunctionToRoutine( state.addedFunctions, @@ -173,24 +168,26 @@ class ThreeGangSwitchHelper { AllDevicesModel? device, required String operationName, required bool removeComparetors, + required String dialogType, }) { if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2' || selectedFunction == 'countdown_3') { final initialValue = selectedFunctionData?.value ?? 0; return _buildTemperatureSelector( - context: context, - initialValue: initialValue, - selectCode: selectedFunction, - currentCondition: selectedFunctionData?.condition, - device: device, - operationName: operationName, - selectedFunctionData: selectedFunctionData, - removeComparetors: removeComparetors, - ); + context: context, + initialValue: initialValue, + selectCode: selectedFunction, + currentCondition: selectedFunctionData?.condition, + device: device, + operationName: operationName, + selectedFunctionData: selectedFunctionData, + removeComparetors: removeComparetors, + dialogType: dialogType); } - final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction); + final selectedFn = + switchFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); return _buildOperationalValuesList( @@ -213,93 +210,18 @@ class ThreeGangSwitchHelper { required String operationName, DeviceFunctionData? selectedFunctionData, bool? removeComparetors, + required String dialogType, }) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - if (removeComparetors != true) - _buildConditionToggle( - context, - currentCondition, - selectCode, - device, - operationName, - selectedFunctionData, - ), - const SizedBox(height: 20), - _buildCountDownDisplay(context, initialValue, device, operationName, - selectedFunctionData, selectCode), const SizedBox(height: 20), _buildCountDownSlider(context, initialValue, device, operationName, - selectedFunctionData, selectCode), + selectedFunctionData, selectCode, dialogType), ], ); } - /// Build condition toggle for AC functions dialog - static Widget _buildConditionToggle( - BuildContext context, - String? currentCondition, - String selectCode, - AllDevicesModel? device, - String operationName, - DeviceFunctionData? selectedFunctionData, - // Function(String) onConditionChanged, - ) { - final conditions = ["<", "==", ">"]; - - return ToggleButtons( - onPressed: (int index) { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: selectCode, - operationName: operationName, - condition: conditions[index], - value: selectedFunctionData?.value ?? 0, - valueDescription: selectedFunctionData?.valueDescription, - ), - ), - ); - }, - borderRadius: const BorderRadius.all(Radius.circular(8)), - selectedBorderColor: ColorsManager.primaryColorWithOpacity, - selectedColor: Colors.white, - fillColor: ColorsManager.primaryColorWithOpacity, - color: ColorsManager.primaryColorWithOpacity, - constraints: const BoxConstraints( - minHeight: 40.0, - minWidth: 40.0, - ), - isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), - children: conditions.map((c) => Text(c)).toList(), - ); - } - - /// Build temperature display for AC functions dialog - static Widget _buildCountDownDisplay( - BuildContext context, - dynamic initialValue, - AllDevicesModel? device, - String operationName, - DeviceFunctionData? selectedFunctionData, - String selectCode) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - decoration: BoxDecoration( - color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Text( - DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0), - style: context.textTheme.headlineMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), - ), - ); - } - static Widget _buildCountDownSlider( BuildContext context, dynamic initialValue, @@ -307,38 +229,47 @@ class ThreeGangSwitchHelper { String operationName, DeviceFunctionData? selectedFunctionData, String selectCode, + String dialogType, ) { - const twelveHoursInSeconds = 43200.0; - final operationalValues = SwitchOperationalValue( - icon: '', - description: "sec", - value: 0.0, - minValue: 0, - maxValue: twelveHoursInSeconds, - stepValue: 1, - ); - return Slider( - 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(), - onChanged: (value) { + return CustomRoutinesTextbox( + withSpecialChar: true, + currentCondition: selectedFunctionData?.condition, + dialogType: dialogType, + sliderRange: (0, 43200), + displayedValue: (initialValue ?? 0).toString(), + initialValue: (initialValue ?? 0).toString(), + onConditionChanged: (condition) { context.read().add( AddFunction( functionData: DeviceFunctionData( entityId: device?.uuid ?? '', functionCode: selectCode, operationName: operationName, - value: value, + condition: condition, + value: selectedFunctionData?.value ?? 0, + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); + }, + onTextChanged: (value) { + final roundedValue = value.round(); + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: roundedValue, condition: selectedFunctionData?.condition, valueDescription: selectedFunctionData?.valueDescription, ), ), ); }, + unit: 'sec', + dividendOfRange: 1, + stepIncreaseAmount: 1, ); } @@ -374,7 +305,9 @@ 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 @@ -390,7 +323,8 @@ class ThreeGangSwitchHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: selectedFunctionData?.valueDescription, + valueDescription: + selectedFunctionData?.valueDescription, ), ), ); diff --git a/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart index f551d21b..6b3dc813 100644 --- a/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart @@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.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'; @@ -86,14 +87,15 @@ class TwoGangSwitchHelper { size: 16, color: ColorsManager.textGray, ), - onTap: () => - RoutineTapFunctionHelper.onTapFunction( + onTap: () => RoutineTapFunctionHelper + .onTapFunction( context, functionCode: function.code, functionOperationName: function.operationName, functionValueDescription: - selectedFunctionData.valueDescription, + selectedFunctionData + .valueDescription, deviceUuid: device?.uuid, codesToAddIntoFunctionsWithDefaultValue: [ 'countdown_1', @@ -115,6 +117,7 @@ class TwoGangSwitchHelper { device: device, operationName: selectedOperationName ?? '', removeComparetors: removeComparetors, + dialogType: dialogType, ), ), ], @@ -172,22 +175,25 @@ class TwoGangSwitchHelper { AllDevicesModel? device, required String operationName, required bool removeComparetors, + required String dialogType, }) { - if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2') { + if (selectedFunction == 'countdown_1' || + selectedFunction == 'countdown_2') { final initialValue = selectedFunctionData?.value ?? 0; return _buildTemperatureSelector( - context: context, - initialValue: initialValue, - selectCode: selectedFunction, - currentCondition: selectedFunctionData?.condition, - device: device, - operationName: operationName, - selectedFunctionData: selectedFunctionData, - removeComparetors: removeComparetors, - ); + context: context, + initialValue: initialValue, + selectCode: selectedFunction, + currentCondition: selectedFunctionData?.condition, + device: device, + operationName: operationName, + selectedFunctionData: selectedFunctionData, + removeComparetors: removeComparetors, + dialogType: dialogType); } - final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction); + final selectedFn = + switchFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); return _buildOperationalValuesList( @@ -210,25 +216,13 @@ class TwoGangSwitchHelper { required String operationName, DeviceFunctionData? selectedFunctionData, bool? removeComparetors, + String? dialogType, }) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - if (removeComparetors != true) - _buildConditionToggle( - context, - currentCondition, - selectCode, - device, - operationName, - selectedFunctionData, - ), - const SizedBox(height: 20), - _buildCountDownDisplay(context, initialValue, device, operationName, - selectedFunctionData, selectCode), - const SizedBox(height: 20), _buildCountDownSlider(context, initialValue, device, operationName, - selectedFunctionData, selectCode), + selectedFunctionData, selectCode, dialogType!), ], ); } @@ -269,7 +263,8 @@ 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(), ); } @@ -304,38 +299,48 @@ class TwoGangSwitchHelper { String operationName, DeviceFunctionData? selectedFunctionData, String selectCode, + String dialogType, ) { - const twelveHoursInSeconds = 43200.0; - final operationalValues = SwitchOperationalValue( - icon: '', - description: "sec", - value: 0.0, - minValue: 0, - maxValue: twelveHoursInSeconds, - stepValue: 1, - ); - return Slider( - 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(), - onChanged: (value) { + return CustomRoutinesTextbox( + withSpecialChar: true, + currentCondition: selectedFunctionData?.condition, + dialogType: dialogType, + sliderRange: (0, 43200), + displayedValue: (initialValue ?? 0).toString(), + initialValue: (initialValue ?? 0).toString(), + onConditionChanged: (condition) { context.read().add( AddFunction( functionData: DeviceFunctionData( entityId: device?.uuid ?? '', functionCode: selectCode, operationName: operationName, - value: value, + condition: condition, + value: selectedFunctionData?.value ?? 0, + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); + }, + onTextChanged: (value) { + final roundedValue = + value.round(); // Round to nearest integer (stepSize 1) + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: roundedValue, condition: selectedFunctionData?.condition, valueDescription: selectedFunctionData?.valueDescription, ), ), ); }, + unit: 'sec', + dividendOfRange: 1, + stepIncreaseAmount: 1, ); } @@ -371,7 +376,9 @@ 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 @@ -387,7 +394,8 @@ class TwoGangSwitchHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: selectedFunctionData?.valueDescription, + valueDescription: + selectedFunctionData?.valueDescription, ), ), ); diff --git a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart index 30232846..677c26ee 100644 --- a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart +++ b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart @@ -4,8 +4,8 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; 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/custom_routines_textbox.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wps_operational_values_list.dart'; -import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart'; class WpsValueSelectorWidget extends StatelessWidget { final String selectedFunction; @@ -27,11 +27,13 @@ class WpsValueSelectorWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final selectedFn = wpsFunctions.firstWhere((f) => f.code == selectedFunction); + final selectedFn = + wpsFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); if (_isSliderFunction(selectedFunction)) { - return SliderValueSelector( + return CustomRoutinesTextbox( + withSpecialChar: false, currentCondition: functionData.condition, dialogType: dialogType, sliderRange: sliderRange, @@ -48,7 +50,7 @@ class WpsValueSelectorWidget extends StatelessWidget { ), ), ), - onSliderChanged: (value) => context.read().add( + onTextChanged: (value) => context.read().add( AddFunction( functionData: DeviceFunctionData( entityId: device?.uuid ?? '', @@ -61,6 +63,7 @@ class WpsValueSelectorWidget extends StatelessWidget { ), unit: _unit, dividendOfRange: 1, + stepIncreaseAmount: _steps, ); } @@ -99,4 +102,10 @@ class WpsValueSelectorWidget extends StatelessWidget { 'illuminance_value' => 'Lux', _ => '', }; + double get _steps => switch (functionData.functionCode) { + 'presence_time' => 1, + 'dis_current' => 1, + 'illuminance_value' => 1, + _ => 1, + }; } diff --git a/lib/pages/routines/widgets/routine_dialogs/water_heater/water_heater_presence_sensor.dart b/lib/pages/routines/widgets/routine_dialogs/water_heater/water_heater_presence_sensor.dart index 8721613e..d87e8484 100644 --- a/lib/pages/routines/widgets/routine_dialogs/water_heater/water_heater_presence_sensor.dart +++ b/lib/pages/routines/widgets/routine_dialogs/water_heater/water_heater_presence_sensor.dart @@ -176,6 +176,7 @@ class _WaterHeaterDialogRoutinesState extends State { functionData: functionData, whFunctions: _waterHeaterFunctions, device: widget.device, + dialogType: widget.dialogType, ), ); } diff --git a/lib/pages/routines/widgets/routine_dialogs/water_heater/water_heater_value_selector_widget.dart b/lib/pages/routines/widgets/routine_dialogs/water_heater/water_heater_value_selector_widget.dart index d305e5f6..a09bbba7 100644 --- a/lib/pages/routines/widgets/routine_dialogs/water_heater/water_heater_value_selector_widget.dart +++ b/lib/pages/routines/widgets/routine_dialogs/water_heater/water_heater_value_selector_widget.dart @@ -2,25 +2,24 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; -import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; -import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/water_heater/water_heater_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/water_heater/water_heater_operational_values_list.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/extension/build_context_x.dart'; class WaterHeaterValueSelectorWidget extends StatelessWidget { final String selectedFunction; final DeviceFunctionData functionData; final List whFunctions; final AllDevicesModel? device; + final String dialogType; const WaterHeaterValueSelectorWidget({ required this.selectedFunction, required this.functionData, required this.whFunctions, required this.device, + required this.dialogType, super.key, }); @@ -39,22 +38,6 @@ class WaterHeaterValueSelectorWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ - _buildConditionToggle( - context, - functionData.condition, - selectedFunction, - device, - selectedFn.operationName, - functionData, - ), - _buildCountDownDisplay( - context, - functionData.value, - device, - selectedFn.operationName, - functionData, - selectedFunction, - ), _buildCountDownSlider( context, functionData.value, @@ -62,6 +45,7 @@ class WaterHeaterValueSelectorWidget extends StatelessWidget { selectedFn.operationName, functionData, selectedFunction, + dialogType ), const SizedBox(height: 10), ], @@ -90,28 +74,6 @@ class WaterHeaterValueSelectorWidget extends StatelessWidget { ); } - static Widget _buildCountDownDisplay( - BuildContext context, - dynamic initialValue, - AllDevicesModel? device, - String operationName, - DeviceFunctionData? selectedFunctionData, - String selectCode) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - decoration: BoxDecoration( - color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Text( - DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0), - style: context.textTheme.headlineMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), - ), - ); - } - static Widget _buildCountDownSlider( BuildContext context, dynamic initialValue, @@ -119,78 +81,47 @@ class WaterHeaterValueSelectorWidget extends StatelessWidget { String operationName, DeviceFunctionData? selectedFunctionData, String selectCode, + String dialogType, ) { - const twelveHoursInSeconds = 43200.0; - final operationalValues = SwitchOperationalValue( - icon: '', - description: "sec", - value: 0.0, - minValue: 0, - maxValue: twelveHoursInSeconds, - stepValue: 1, - ); - return Slider( - 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(), - onChanged: (value) { + return CustomRoutinesTextbox( + withSpecialChar: false, + currentCondition: selectedFunctionData?.condition, + dialogType: dialogType, + sliderRange: (0, 43200), + displayedValue: (initialValue ?? 0).toString(), + initialValue: (initialValue ?? 0).toString(), + onConditionChanged: (condition) { context.read().add( AddFunction( functionData: DeviceFunctionData( entityId: device?.uuid ?? '', functionCode: selectCode, operationName: operationName, - value: value, + value: condition, + condition: condition, + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); + }, + onTextChanged: (value) { + final roundedValue = value.round(); + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: roundedValue, condition: selectedFunctionData?.condition, valueDescription: selectedFunctionData?.valueDescription, ), ), ); }, - ); - } - - static Widget _buildConditionToggle( - BuildContext context, - String? currentCondition, - String selectCode, - AllDevicesModel? device, - String operationName, - DeviceFunctionData? selectedFunctionData, - ) { - final conditions = ["<", "==", ">"]; - - return ToggleButtons( - onPressed: (int index) { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: selectCode, - operationName: operationName, - condition: conditions[index], - value: selectedFunctionData?.value ?? 0, - valueDescription: selectedFunctionData?.valueDescription, - ), - ), - ); - }, - borderRadius: const BorderRadius.all(Radius.circular(8)), - selectedBorderColor: ColorsManager.primaryColorWithOpacity, - selectedColor: Colors.white, - fillColor: ColorsManager.primaryColorWithOpacity, - color: ColorsManager.primaryColorWithOpacity, - constraints: const BoxConstraints( - minHeight: 40.0, - minWidth: 40.0, - ), - isSelected: - conditions.map((c) => c == (currentCondition ?? "==")).toList(), - children: conditions.map((c) => Text(c)).toList(), + unit: 'sec', + dividendOfRange: 1, + stepIncreaseAmount: 1, ); } } From 4326559e14c2b74f467f5f4f90f1a49c44d6864d Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 20 May 2025 13:51:04 +0300 Subject: [PATCH 16/25] shows `OccupancyHeatMapBox` instead of a `Placeholder` in vertical srcollable `AnalyticsOccupancyView`. --- .../modules/occupancy/views/analytics_occupancy_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart b/lib/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart index df4ba4a8..2cf18742 100644 --- a/lib/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart +++ b/lib/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart @@ -22,7 +22,7 @@ class AnalyticsOccupancyView extends StatelessWidget { children: [ SizedBox(height: height * 0.45, child: const OccupancyEndSideBar()), SizedBox(height: height * 0.5, child: const OccupancyChartBox()), - SizedBox(height: height * 0.5, child: const Placeholder()), + SizedBox(height: height * 0.5, child: const OccupancyHeatMapBox()), ], ), ); From c21842cc6dcabce67eb6a7298a4e456081e718da Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 20 May 2025 13:56:00 +0300 Subject: [PATCH 17/25] removed overflow and fixed sizing and text drawing of `PowerClampEnergyStatusWidget`. --- .../widgets/power_clamp_energy_status_widget.dart | 3 +++ .../modules/occupancy/views/analytics_occupancy_view.dart | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart b/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart index 27d74ae0..fc957035 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart @@ -48,6 +48,9 @@ class PowerClampEnergyStatusWidget extends StatelessWidget { fontWeight: FontWeight.w400, fontSize: 16, ), + softWrap: true, + maxLines: 2, + overflow: TextOverflow.ellipsis, ), trailing: Text.rich( TextSpan( diff --git a/lib/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart b/lib/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart index 2cf18742..679c9927 100644 --- a/lib/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart +++ b/lib/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart @@ -20,7 +20,7 @@ class AnalyticsOccupancyView extends StatelessWidget { child: Column( spacing: 32, children: [ - SizedBox(height: height * 0.45, child: const OccupancyEndSideBar()), + SizedBox(height: height * 0.46, child: const OccupancyEndSideBar()), SizedBox(height: height * 0.5, child: const OccupancyChartBox()), SizedBox(height: height * 0.5, child: const OccupancyHeatMapBox()), ], From 15343be258f4f31896a841f19a3efa83d07760cb Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 20 May 2025 14:11:25 +0300 Subject: [PATCH 18/25] show space uuid in analytics devices dropdown. --- .../widgets/analytics_device_dropdown.dart | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart b/lib/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart index 0d96fb70..044cf1fb 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/analytics/models/analytics_device.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -48,6 +49,7 @@ class AnalyticsDeviceDropdown extends StatelessWidget { } Widget _buildDevicesDropdown(BuildContext context, AnalyticsDevicesState state) { + final spaceUuid = context.read().state.selectedSpaces.firstOrNull; return DropdownButton( value: state.selectedDevice, isDense: true, @@ -60,10 +62,32 @@ class AnalyticsDeviceDropdown extends StatelessWidget { ), style: _getTextStyle(context), padding: _defaultPadding, + selectedItemBuilder: (context) { + return state.devices.map((e) { + return Text(e.name); + }).toList(); + }, items: state.devices.map((e) { return DropdownMenuItem( value: e, - child: Text(e.name), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text(e.name), + if (spaceUuid != null) + FittedBox( + fit: BoxFit.scaleDown, + alignment: AlignmentDirectional.centerStart, + child: Text( + spaceUuid, + style: _getTextStyle(context)?.copyWith( + fontSize: 10, + ), + ), + ), + ], + ), ); }).toList(), onChanged: (value) { From 4c2802acfc53b2f70f71b6e995db7ed046b9d093 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 20 May 2025 14:20:16 +0300 Subject: [PATCH 19/25] date picker decorations matched with design. --- .../widgets/month_picker_widget.dart | 47 ++++++++++++------- .../analytics/widgets/year_picker_widget.dart | 41 ++++++++++------ 2 files changed, 55 insertions(+), 33 deletions(-) diff --git a/lib/pages/analytics/modules/analytics/widgets/month_picker_widget.dart b/lib/pages/analytics/modules/analytics/widgets/month_picker_widget.dart index ab7603e5..57133b02 100644 --- a/lib/pages/analytics/modules/analytics/widgets/month_picker_widget.dart +++ b/lib/pages/analytics/modules/analytics/widgets/month_picker_widget.dart @@ -178,7 +178,6 @@ class _MonthPickerWidgetState extends State { shrinkWrap: true, itemCount: 12, physics: const NeverScrollableScrollPhysics(), - padding: const EdgeInsets.all(8), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, childAspectRatio: 2.5, @@ -191,27 +190,39 @@ class _MonthPickerWidgetState extends State { return InkWell( onTap: isFutureMonth ? null : () => setState(() => _selectedMonth = index), - child: Container( - alignment: Alignment.center, + child: DecoratedBox( decoration: BoxDecoration( - color: isSelected - ? ColorsManager.vividBlue.withValues(alpha: 0.7) - : isFutureMonth - ? ColorsManager.grey700.withValues(alpha: 0.1) - : const Color(0xFFEDF2F7), - borderRadius: - isSelected ? BorderRadius.circular(15) : BorderRadius.zero, + color: const Color(0xFFEDF2F7), + borderRadius: BorderRadius.only( + topLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero, + bottomLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero, + topRight: index % 3 == 2 ? const Radius.circular(16) : Radius.zero, + bottomRight: + index % 3 == 2 ? const Radius.circular(16) : Radius.zero, + ), ), - child: Text( - _monthNames[index], - style: context.textTheme.titleSmall?.copyWith( - fontSize: 12, + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( color: isSelected - ? ColorsManager.whiteColors + ? ColorsManager.vividBlue.withValues(alpha: 0.7) : isFutureMonth - ? ColorsManager.blackColor.withValues(alpha: 0.3) - : ColorsManager.blackColor.withValues(alpha: 0.8), - fontWeight: FontWeight.w500, + ? ColorsManager.grey700.withValues(alpha: 0.1) + : const Color(0xFFEDF2F7), + borderRadius: + isSelected ? BorderRadius.circular(15) : BorderRadius.zero, + ), + child: Text( + _monthNames[index], + style: context.textTheme.titleSmall?.copyWith( + fontSize: 12, + color: isSelected + ? ColorsManager.whiteColors + : isFutureMonth + ? ColorsManager.blackColor.withValues(alpha: 0.3) + : ColorsManager.blackColor.withValues(alpha: 0.8), + fontWeight: FontWeight.w500, + ), ), ), ), diff --git a/lib/pages/analytics/modules/analytics/widgets/year_picker_widget.dart b/lib/pages/analytics/modules/analytics/widgets/year_picker_widget.dart index 2bcb223b..22eb6646 100644 --- a/lib/pages/analytics/modules/analytics/widgets/year_picker_widget.dart +++ b/lib/pages/analytics/modules/analytics/widgets/year_picker_widget.dart @@ -109,7 +109,6 @@ class _YearPickerWidgetState extends State { shrinkWrap: true, itemCount: years.length, physics: const NeverScrollableScrollPhysics(), - padding: const EdgeInsets.all(8), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, childAspectRatio: 2.5, @@ -120,23 +119,35 @@ class _YearPickerWidgetState extends State { final isSelected = _currentYear == years[index]; return InkWell( onTap: () => setState(() => _currentYear = years[index]), - child: Container( - alignment: Alignment.center, + child: DecoratedBox( decoration: BoxDecoration( - color: isSelected - ? ColorsManager.vividBlue.withValues(alpha: 0.7) - : const Color(0xFFEDF2F7), - borderRadius: - isSelected ? BorderRadius.circular(15) : BorderRadius.zero, + color: const Color(0xFFEDF2F7), + borderRadius: BorderRadius.only( + topLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero, + bottomLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero, + topRight: index % 3 == 2 ? const Radius.circular(16) : Radius.zero, + bottomRight: + index % 3 == 2 ? const Radius.circular(16) : Radius.zero, + ), ), - child: Text( - years[index].toString(), - style: context.textTheme.titleSmall?.copyWith( - fontSize: 12, + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( color: isSelected - ? ColorsManager.whiteColors - : ColorsManager.blackColor.withValues(alpha: 0.8), - fontWeight: FontWeight.w500, + ? ColorsManager.vividBlue.withValues(alpha: 0.7) + : const Color(0xFFEDF2F7), + borderRadius: + isSelected ? BorderRadius.circular(15) : BorderRadius.zero, + ), + child: Text( + years[index].toString(), + style: context.textTheme.titleSmall?.copyWith( + fontSize: 12, + color: isSelected + ? ColorsManager.whiteColors + : ColorsManager.blackColor.withValues(alpha: 0.8), + fontWeight: FontWeight.w500, + ), ), ), ), From fd2a09cada222b339c1ab12742ae8bdf446f2ad2 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 20 May 2025 14:22:23 +0300 Subject: [PATCH 20/25] Deleted unused `FakeEnergyConsumptionPerDeviceService`. --- ...energy_consumption_per_device_service.dart | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 lib/pages/analytics/services/energy_consumption_per_device/fake_energy_consumption_per_device_service.dart diff --git a/lib/pages/analytics/services/energy_consumption_per_device/fake_energy_consumption_per_device_service.dart b/lib/pages/analytics/services/energy_consumption_per_device/fake_energy_consumption_per_device_service.dart deleted file mode 100644 index b1608eea..00000000 --- a/lib/pages/analytics/services/energy_consumption_per_device/fake_energy_consumption_per_device_service.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'dart:math' as math show Random; - -import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart'; -import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart'; -import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart'; -import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart'; - -class FakeEnergyConsumptionPerDeviceService - implements EnergyConsumptionPerDeviceService { - @override - Future> load( - GetEnergyConsumptionPerDeviceParam param, - ) { - final random = math.Random(); - return Future.delayed(const Duration(milliseconds: 500), () { - return [ - (Colors.redAccent, 1), - (Colors.lightBlueAccent, 2), - (Colors.purpleAccent, 3), - ].map((e) { - final (color, index) = e; - return DeviceEnergyDataModel( - color: color, - energy: List.generate(30, (i) => i) - .map( - (index) => EnergyDataModel( - date: DateTime(2025, 1, index + 1), - value: random.nextInt(100) + (index * 100), - ), - ) - .toList(), - deviceName: 'Device $index', - deviceId: 'device_$index', - ); - }).toList(); - }); - } -} From a15b5439f0ff5f4812e99d74e971f22ba1bbeb9f Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 20 May 2025 16:39:10 +0300 Subject: [PATCH 21/25] Refactor user dropdown menu to display user's full name and arrow icon in a row for better layout consistency --- lib/utils/user_drop_down_menu.dart | 25 ++++++++++++++++++------- lib/web_layout/web_app_bar.dart | 23 ----------------------- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/lib/utils/user_drop_down_menu.dart b/lib/utils/user_drop_down_menu.dart index c9fe492e..15da1f3a 100644 --- a/lib/utils/user_drop_down_menu.dart +++ b/lib/utils/user_drop_down_menu.dart @@ -41,13 +41,24 @@ class _UserDropdownMenuState extends State { _isDropdownOpen = false; }); }, - child: Transform.rotate( - angle: _isDropdownOpen ? -1.5708 : 1.5708, - child: const Icon( - Icons.arrow_forward_ios, - color: Colors.white, - size: 16, - ), + child: Row( + children: [ + const SizedBox(width: 12), + if (widget.user != null) + Text( + '${widget.user!.firstName} ${widget.user!.lastName}', + style: Theme.of(context).textTheme.bodyLarge, + ), + const SizedBox(width: 12), + Transform.rotate( + angle: _isDropdownOpen ? -1.5708 : 1.5708, + child: const Icon( + Icons.arrow_forward_ios, + color: Colors.white, + size: 16, + ), + ), + ], ), ), ], diff --git a/lib/web_layout/web_app_bar.dart b/lib/web_layout/web_app_bar.dart index 02b81522..3cfe171e 100644 --- a/lib/web_layout/web_app_bar.dart +++ b/lib/web_layout/web_app_bar.dart @@ -92,13 +92,6 @@ class DesktopAppBar extends StatelessWidget { if (rightBody != null) rightBody!, const SizedBox(width: 24), _UserAvatar(), - const SizedBox(width: 12), - if (user != null) - Text( - '${user.firstName} ${user.lastName}', - style: Theme.of(context).textTheme.bodyLarge, - ), - const SizedBox(width: 12), UserDropdownMenu(user: user), ], ); @@ -146,14 +139,6 @@ class TabletAppBar extends StatelessWidget { if (rightBody != null) rightBody!, const SizedBox(width: 16), _UserAvatar(), - if (user != null) ...[ - const SizedBox(width: 8), - Text( - '${user.firstName} ${user.lastName}', - style: - Theme.of(context).textTheme.bodyLarge?.copyWith(fontSize: 14), - ), - ], UserDropdownMenu(user: user), ], ); @@ -215,14 +200,6 @@ class MobileAppBar extends StatelessWidget { return Row( children: [ _UserAvatar(), - if (user != null) ...[ - const SizedBox(width: 8), - Text( - '${user.firstName} ${user.lastName}', - style: - Theme.of(context).textTheme.bodyLarge?.copyWith(fontSize: 14), - ), - ], UserDropdownMenu(user: user), ], ); From b2bf3866a9962d79486705303e8526f5f25870ce Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 21 May 2025 09:09:32 +0300 Subject: [PATCH 22/25] Deleted `pubspec.lock`, and added it to `.gitignore`. --- .gitignore | 1 + pubspec.lock | 906 --------------------------------------------------- 2 files changed, 1 insertion(+), 906 deletions(-) delete mode 100644 pubspec.lock diff --git a/.gitignore b/.gitignore index 29a3a501..fa9d8443 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ migrate_working_dir/ .pub-cache/ .pub/ /build/ +pubspec.lock # Symbolication related app.*.symbols diff --git a/pubspec.lock b/pubspec.lock deleted file mode 100644 index fb0d6a22..00000000 --- a/pubspec.lock +++ /dev/null @@ -1,906 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _flutterfire_internals: - dependency: transitive - description: - name: _flutterfire_internals - sha256: e051259913915ea5bc8fe18664596bea08592fd123930605d562969cd7315fcd - url: "https://pub.dev" - source: hosted - version: "1.3.51" - args: - dependency: transitive - description: - name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" - url: "https://pub.dev" - source: hosted - version: "2.5.0" - async: - dependency: transitive - description: - name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" - url: "https://pub.dev" - source: hosted - version: "2.11.0" - bloc: - dependency: "direct main" - description: - name: bloc - sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" - url: "https://pub.dev" - source: hosted - version: "8.1.4" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - characters: - dependency: transitive - description: - name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" - url: "https://pub.dev" - source: hosted - version: "1.3.0" - clock: - dependency: transitive - description: - name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf - url: "https://pub.dev" - source: hosted - version: "1.1.1" - collection: - dependency: transitive - description: - name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf - url: "https://pub.dev" - source: hosted - version: "1.19.0" - crypto: - dependency: transitive - description: - name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" - url: "https://pub.dev" - source: hosted - version: "3.0.6" - csslib: - dependency: transitive - description: - name: csslib - sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f" - url: "https://pub.dev" - source: hosted - version: "0.17.3" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.dev" - source: hosted - version: "1.0.8" - data_table_2: - dependency: "direct main" - description: - name: data_table_2 - sha256: f02ec9b24f44420816a87370ff4f4e533e15b274f6267e4c9a88a585ad1a0473 - url: "https://pub.dev" - source: hosted - version: "2.5.15" - dio: - dependency: "direct main" - description: - name: dio - sha256: e17f6b3097b8c51b72c74c9f071a605c47bcc8893839bd66732457a5ebe73714 - url: "https://pub.dev" - source: hosted - version: "5.5.0+1" - dio_web_adapter: - dependency: transitive - description: - name: dio_web_adapter - sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac" - url: "https://pub.dev" - source: hosted - version: "1.0.1" - dropdown_button2: - dependency: "direct main" - description: - name: dropdown_button2 - sha256: b0fe8d49a030315e9eef6c7ac84ca964250155a6224d491c1365061bc974a9e1 - url: "https://pub.dev" - source: hosted - version: "2.3.9" - dropdown_search: - dependency: "direct main" - description: - name: dropdown_search - sha256: "55106e8290acaa97ed15bea1fdad82c3cf0c248dd410e651f5a8ac6870f783ab" - url: "https://pub.dev" - source: hosted - version: "5.0.6" - equatable: - dependency: "direct main" - description: - name: equatable - sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 - url: "https://pub.dev" - source: hosted - version: "2.0.5" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" - url: "https://pub.dev" - source: hosted - version: "1.3.1" - ffi: - dependency: transitive - description: - name: ffi - sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - file: - dependency: transitive - description: - name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - firebase_analytics: - dependency: "direct main" - description: - name: firebase_analytics - sha256: "47428047a0778f72af53a3c7cb5d556e1cb25e2327cc8aa40d544971dc6245b2" - url: "https://pub.dev" - source: hosted - version: "11.4.2" - firebase_analytics_platform_interface: - dependency: transitive - description: - name: firebase_analytics_platform_interface - sha256: "1076f4b041f76143e14878c70f0758f17fe5910c0cd992db9e93bd3c3584512b" - url: "https://pub.dev" - source: hosted - version: "4.3.2" - firebase_analytics_web: - dependency: transitive - description: - name: firebase_analytics_web - sha256: "8f6dd64ea6d28b7f5b9e739d183a9e1c7f17027794a3e9aba1879621d42426ef" - url: "https://pub.dev" - source: hosted - version: "0.5.10+8" - firebase_core: - dependency: "direct main" - description: - name: firebase_core - sha256: "93dc4dd12f9b02c5767f235307f609e61ed9211047132d07f9e02c668f0bfc33" - url: "https://pub.dev" - source: hosted - version: "3.11.0" - firebase_core_platform_interface: - dependency: transitive - description: - name: firebase_core_platform_interface - sha256: d7253d255ff10f85cfd2adaba9ac17bae878fa3ba577462451163bd9f1d1f0bf - url: "https://pub.dev" - source: hosted - version: "5.4.0" - firebase_core_web: - dependency: transitive - description: - name: firebase_core_web - sha256: "0e13c80f0de8acaa5d0519cbe23c8b4cc138a2d5d508b5755c861bdfc9762678" - url: "https://pub.dev" - source: hosted - version: "2.20.0" - firebase_crashlytics: - dependency: "direct main" - description: - name: firebase_crashlytics - sha256: "6273ed71bcd8a6fb4d0ca13d3abddbb3301796807efaad8782b5f90156f26f03" - url: "https://pub.dev" - source: hosted - version: "4.3.2" - firebase_crashlytics_platform_interface: - dependency: transitive - description: - name: firebase_crashlytics_platform_interface - sha256: "94f3986e1a10e5a883f2ad5e3d719aef98a8a0f9a49357f6e45b7d3696ea6a97" - url: "https://pub.dev" - source: hosted - version: "3.8.2" - firebase_database: - dependency: "direct main" - description: - name: firebase_database - sha256: cd2354dfef68e52c0713b5efbb7f4e10dfc2aff2f945c7bc8db34d1934170627 - url: "https://pub.dev" - source: hosted - version: "11.3.2" - firebase_database_platform_interface: - dependency: transitive - description: - name: firebase_database_platform_interface - sha256: d430983f4d877c9f72f88b3d715cca9a50021dd7ccd8e3ae6fb79603853317de - url: "https://pub.dev" - source: hosted - version: "0.2.6+2" - firebase_database_web: - dependency: transitive - description: - name: firebase_database_web - sha256: f64edae62c5beaa08e9e611a0736d64ab11a812983a0aa132695d2d191311ea7 - url: "https://pub.dev" - source: hosted - version: "0.2.6+8" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" - source: hosted - version: "1.1.1" - fl_chart: - dependency: "direct main" - description: - name: fl_chart - sha256: "94307bef3a324a0d329d3ab77b2f0c6e5ed739185ffc029ed28c0f9b019ea7ef" - url: "https://pub.dev" - source: hosted - version: "0.69.0" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_bloc: - dependency: "direct main" - description: - name: flutter_bloc - sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2 - url: "https://pub.dev" - source: hosted - version: "8.1.5" - flutter_dotenv: - dependency: "direct main" - description: - name: flutter_dotenv - sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77" - url: "https://pub.dev" - source: hosted - version: "5.1.0" - flutter_html: - dependency: "direct main" - description: - name: flutter_html - sha256: "02ad69e813ecfc0728a455e4bf892b9379983e050722b1dce00192ee2e41d1ee" - url: "https://pub.dev" - source: hosted - version: "3.0.0-beta.2" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - flutter_secure_storage: - dependency: "direct main" - description: - name: flutter_secure_storage - sha256: "165164745e6afb5c0e3e3fcc72a012fb9e58496fb26ffb92cf22e16a821e85d0" - url: "https://pub.dev" - source: hosted - version: "9.2.2" - flutter_secure_storage_linux: - dependency: transitive - description: - name: flutter_secure_storage_linux - sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b" - url: "https://pub.dev" - source: hosted - version: "1.2.1" - flutter_secure_storage_macos: - dependency: transitive - description: - name: flutter_secure_storage_macos - sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81" - url: "https://pub.dev" - source: hosted - version: "3.1.2" - flutter_secure_storage_platform_interface: - dependency: transitive - description: - name: flutter_secure_storage_platform_interface - sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 - url: "https://pub.dev" - source: hosted - version: "1.1.2" - flutter_secure_storage_web: - dependency: transitive - description: - name: flutter_secure_storage_web - sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - flutter_secure_storage_windows: - dependency: transitive - description: - name: flutter_secure_storage_windows - sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 - url: "https://pub.dev" - source: hosted - version: "3.1.2" - flutter_svg: - dependency: "direct main" - description: - name: flutter_svg - sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" - url: "https://pub.dev" - source: hosted - version: "2.0.10+1" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - get_it: - dependency: "direct main" - description: - name: get_it - sha256: d85128a5dae4ea777324730dc65edd9c9f43155c109d5cc0a69cab74139fbac1 - url: "https://pub.dev" - source: hosted - version: "7.7.0" - go_router: - dependency: "direct main" - description: - name: go_router - sha256: "2ddb88e9ad56ae15ee144ed10e33886777eb5ca2509a914850a5faa7b52ff459" - url: "https://pub.dev" - source: hosted - version: "14.2.7" - graphview: - dependency: "direct main" - description: - name: graphview - sha256: bdba183583b23c30c71edea09ad5f0beef612572d3e39e855467a925bd08392f - url: "https://pub.dev" - source: hosted - version: "1.2.0" - html: - dependency: transitive - description: - name: html - sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" - url: "https://pub.dev" - source: hosted - version: "0.15.4" - http: - dependency: transitive - description: - name: http - sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 - url: "https://pub.dev" - source: hosted - version: "1.2.2" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" - url: "https://pub.dev" - source: hosted - version: "4.0.2" - intl: - dependency: "direct main" - description: - name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf - url: "https://pub.dev" - source: hosted - version: "0.19.0" - intl_phone_field: - dependency: "direct main" - description: - name: intl_phone_field - sha256: "73819d3dfcb68d2c85663606f6842597c3ddf6688ac777f051b17814fe767bbf" - url: "https://pub.dev" - source: hosted - version: "3.2.0" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" - url: "https://pub.dev" - source: hosted - version: "10.0.7" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" - url: "https://pub.dev" - source: hosted - version: "3.0.8" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.dev" - source: hosted - version: "3.0.1" - lints: - dependency: transitive - description: - name: lints - sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 - url: "https://pub.dev" - source: hosted - version: "3.0.0" - list_counter: - dependency: transitive - description: - name: list_counter - sha256: c447ae3dfcd1c55f0152867090e67e219d42fe6d4f2807db4bbe8b8d69912237 - url: "https://pub.dev" - source: hosted - version: "1.0.2" - logging: - dependency: transitive - description: - name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb - url: "https://pub.dev" - source: hosted - version: "0.12.16+1" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.dev" - source: hosted - version: "0.11.1" - meta: - dependency: transitive - description: - name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 - url: "https://pub.dev" - source: hosted - version: "1.15.0" - nested: - dependency: transitive - description: - name: nested - sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - number_pagination: - dependency: "direct main" - description: - name: number_pagination - sha256: "75d3a28616196e7c8df431d0fb7c48e811e462155f4cf3b5b4167b3408421327" - url: "https://pub.dev" - source: hosted - version: "1.1.6" - path: - dependency: transitive - description: - name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" - url: "https://pub.dev" - source: hosted - version: "1.9.0" - path_parsing: - dependency: transitive - description: - name: path_parsing - sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf - url: "https://pub.dev" - source: hosted - version: "1.0.1" - path_provider: - dependency: transitive - description: - name: path_provider - sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 - url: "https://pub.dev" - source: hosted - version: "2.1.3" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - sha256: e84c8a53fe1510ef4582f118c7b4bdf15b03002b51d7c2b66983c65843d61193 - url: "https://pub.dev" - source: hosted - version: "2.2.8" - path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 - url: "https://pub.dev" - source: hosted - version: "2.4.0" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" - source: hosted - version: "2.2.1" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" - source: hosted - version: "2.3.0" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 - url: "https://pub.dev" - source: hosted - version: "6.0.2" - platform: - dependency: transitive - description: - name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" - url: "https://pub.dev" - source: hosted - version: "3.1.5" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" - source: hosted - version: "2.1.8" - provider: - dependency: transitive - description: - name: provider - sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c - url: "https://pub.dev" - source: hosted - version: "6.1.2" - shared_preferences: - dependency: "direct main" - description: - name: shared_preferences - sha256: c3f888ba2d659f3e75f4686112cc1e71f46177f74452d40d8307edc332296ead - url: "https://pub.dev" - source: hosted - version: "2.3.0" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - sha256: "041be4d9d2dc6079cf342bc8b761b03787e3b71192d658220a56cac9c04a0294" - url: "https://pub.dev" - source: hosted - version: "2.3.0" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833" - url: "https://pub.dev" - source: hosted - version: "2.5.0" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - sha256: "2ba0510d3017f91655b7543e9ee46d48619de2a2af38e5c790423f7007c7ccc1" - url: "https://pub.dev" - source: hosted - version: "2.4.0" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e - url: "https://pub.dev" - source: hosted - version: "2.4.2" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - sha256: "398084b47b7f92110683cac45c6dc4aae853db47e470e5ddcd52cab7f7196ab2" - url: "https://pub.dev" - source: hosted - version: "2.4.0" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - source_span: - dependency: transitive - description: - name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.dev" - source: hosted - version: "1.10.0" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" - url: "https://pub.dev" - source: hosted - version: "1.12.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 - url: "https://pub.dev" - source: hosted - version: "2.1.2" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" - url: "https://pub.dev" - source: hosted - version: "1.3.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - test_api: - dependency: transitive - description: - name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" - url: "https://pub.dev" - source: hosted - version: "0.7.3" - time_picker_spinner: - dependency: "direct main" - description: - name: time_picker_spinner - sha256: "53d824801d108890d22756501e7ade9db48b53dac1ec41580499dd4ebd128e3c" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c - url: "https://pub.dev" - source: hosted - version: "1.3.2" - url_launcher: - dependency: "direct main" - description: - name: url_launcher - sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" - url: "https://pub.dev" - source: hosted - version: "6.3.1" - url_launcher_android: - dependency: transitive - description: - name: url_launcher_android - sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" - url: "https://pub.dev" - source: hosted - version: "6.3.14" - url_launcher_ios: - dependency: transitive - description: - name: url_launcher_ios - sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" - url: "https://pub.dev" - source: hosted - version: "6.3.2" - url_launcher_linux: - dependency: transitive - description: - name: url_launcher_linux - sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" - url: "https://pub.dev" - source: hosted - version: "3.2.1" - url_launcher_macos: - dependency: transitive - description: - name: url_launcher_macos - sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" - url: "https://pub.dev" - source: hosted - version: "3.2.2" - url_launcher_platform_interface: - dependency: transitive - description: - name: url_launcher_platform_interface - sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" - url: "https://pub.dev" - source: hosted - version: "2.3.2" - url_launcher_web: - dependency: transitive - description: - name: url_launcher_web - sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" - url: "https://pub.dev" - source: hosted - version: "2.4.0" - url_launcher_windows: - dependency: transitive - description: - name: url_launcher_windows - sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" - url: "https://pub.dev" - source: hosted - version: "3.1.4" - uuid: - dependency: "direct main" - description: - name: uuid - sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff - url: "https://pub.dev" - source: hosted - version: "4.5.1" - vector_graphics: - dependency: transitive - description: - name: vector_graphics - sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" - url: "https://pub.dev" - source: hosted - version: "1.1.11+1" - vector_graphics_codec: - dependency: transitive - description: - name: vector_graphics_codec - sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da - url: "https://pub.dev" - source: hosted - version: "1.1.11+1" - vector_graphics_compiler: - dependency: transitive - description: - name: vector_graphics_compiler - sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" - url: "https://pub.dev" - source: hosted - version: "1.1.11+1" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b - url: "https://pub.dev" - source: hosted - version: "14.3.0" - web: - dependency: transitive - description: - name: web - sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb - url: "https://pub.dev" - source: hosted - version: "1.1.0" - win32: - dependency: transitive - description: - name: win32 - sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 - url: "https://pub.dev" - source: hosted - version: "5.5.1" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d - url: "https://pub.dev" - source: hosted - version: "1.0.4" - xml: - dependency: transitive - description: - name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 - url: "https://pub.dev" - source: hosted - version: "6.5.0" -sdks: - dart: ">=3.6.0 <4.0.0" - flutter: ">=3.27.0" From 4257f7f0f3d5200ba846bbd542f0dd29e98568fa Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 21 May 2025 09:55:17 +0300 Subject: [PATCH 23/25] Corrected color of titles in charts. --- .../analytics/models/analytics_device.dart | 86 ++++++++++--------- .../energy_management_charts_helper.dart | 2 +- .../widgets/analytics_device_dropdown.dart | 2 +- ...y_consumption_per_device_devices_list.dart | 2 +- .../occupancy/widgets/occupancy_chart.dart | 4 +- 5 files changed, 51 insertions(+), 45 deletions(-) diff --git a/lib/pages/analytics/models/analytics_device.dart b/lib/pages/analytics/models/analytics_device.dart index d2301e4a..88f18ec5 100644 --- a/lib/pages/analytics/models/analytics_device.dart +++ b/lib/pages/analytics/models/analytics_device.dart @@ -2,64 +2,70 @@ class AnalyticsDevice { const AnalyticsDevice({ required this.uuid, required this.name, - required this.createdAt, - required this.updatedAt, - required this.deviceTuyaUuid, - required this.isActive, - required this.productDevice, + this.createdAt, + this.updatedAt, + this.deviceTuyaUuid, + this.isActive, + this.productDevice, + this.spaceUuid, }); final String uuid; final String name; - final DateTime createdAt; - final DateTime updatedAt; - final String deviceTuyaUuid; - final bool isActive; - final ProductDevice productDevice; + final DateTime? createdAt; + final DateTime? updatedAt; + final String? deviceTuyaUuid; + final bool? isActive; + final ProductDevice? productDevice; + final String? spaceUuid; factory AnalyticsDevice.fromJson(Map json) { return AnalyticsDevice( - uuid: json['uuid'] as String? ?? '', - name: json['name'] as String? ?? '', - createdAt: DateTime.parse(json['createdAt'] as String? ?? ''), - updatedAt: DateTime.parse(json['updatedAt'] as String? ?? ''), - deviceTuyaUuid: json['deviceTuyaUuid'] as String? ?? '', - isActive: json['isActive'] as bool? ?? false, - productDevice: ProductDevice.fromJson( - json['productDevice'] as Map? ?? {}, - ), + uuid: json['uuid'] as String, + name: json['name'] as String, + createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null, + updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null, + deviceTuyaUuid: json['deviceTuyaUuid'] as String?, + isActive: json['isActive'] as bool?, + productDevice: json['productDevice'] != null + ? ProductDevice.fromJson(json['productDevice'] as Map) + : null, + spaceUuid: (json['spaces'] as List?) + ?.map((e) => e['uuid']) + .firstOrNull + ?.toString(), ); } } class ProductDevice { const ProductDevice({ - required this.uuid, - required this.createdAt, - required this.updatedAt, - required this.catName, - required this.prodId, - required this.name, - required this.prodType, + this.uuid, + this.createdAt, + this.updatedAt, + this.catName, + this.prodId, + this.name, + this.prodType, }); - final String uuid; - final DateTime createdAt; - final DateTime updatedAt; - final String catName; - final String prodId; - final String name; - final String prodType; + final String? uuid; + final DateTime? createdAt; + final DateTime? updatedAt; + final String? catName; + final String? prodId; + final String? name; + final String? prodType; factory ProductDevice.fromJson(Map json) { return ProductDevice( - uuid: json['uuid'] as String? ?? '', - createdAt: DateTime.parse(json['createdAt'] as String? ?? ''), - updatedAt: DateTime.parse(json['updatedAt'] as String? ?? ''), - catName: json['catName'] as String? ?? '', - prodId: json['prodId'] as String? ?? '', - name: json['name'] as String? ?? '', - prodType: json['prodType'] as String? ?? '', + uuid: json['uuid'] as String?, + createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null, + updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null, + catName: json['catName'] as String?, + prodId: json['prodId'] as String?, + name: json['name'] as String?, + prodType: json['prodType'] as String?, ); } } diff --git a/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart b/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart index dadfc70f..567e03ed 100644 --- a/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart +++ b/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart @@ -27,7 +27,7 @@ abstract final class EnergyManagementChartsHelper { child: Text( value.toString(), style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.greyColor, + color: ColorsManager.lightGreyColor, fontSize: 12, ), ), diff --git a/lib/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart b/lib/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart index 044cf1fb..4158da05 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart @@ -49,7 +49,7 @@ class AnalyticsDeviceDropdown extends StatelessWidget { } Widget _buildDevicesDropdown(BuildContext context, AnalyticsDevicesState state) { - final spaceUuid = context.read().state.selectedSpaces.firstOrNull; + final spaceUuid = state.selectedDevice?.spaceUuid; return DropdownButton( value: state.selectedDevice, isDense: true, diff --git a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_devices_list.dart b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_devices_list.dart index 5ef434e1..e6996f53 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_devices_list.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_devices_list.dart @@ -41,7 +41,7 @@ class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget { .color; return Tooltip( - message: '${device.name}\n${device.productDevice.uuid}', + message: '${device.name}\n${device.productDevice?.uuid ?? ''}', child: Container( height: MediaQuery.sizeOf(context).height * 0.0365, padding: const EdgeInsetsDirectional.symmetric( diff --git a/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart b/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart index 8a93ccf1..3e362f5d 100644 --- a/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart +++ b/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart @@ -19,7 +19,7 @@ class OccupancyChart extends StatelessWidget { maxY: 1.0, gridData: EnergyManagementChartsHelper.gridData().copyWith( checkToShowHorizontalLine: (value) => true, - horizontalInterval: 0.25, + horizontalInterval: 0.2, ), borderData: EnergyManagementChartsHelper.borderData(), barTouchData: _barTouchData(context), @@ -108,7 +108,7 @@ class OccupancyChart extends StatelessWidget { final leftTitles = titlesData.leftTitles.copyWith( sideTitles: titlesData.leftTitles.sideTitles.copyWith( reservedSize: 70, - interval: 0.25, + interval: 0.2, getTitlesWidget: (value, meta) => Padding( padding: const EdgeInsetsDirectional.only(end: 12), child: FittedBox( From 000fe7066389b808aac29c000053a83d6a79b69d Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 21 May 2025 09:59:50 +0300 Subject: [PATCH 24/25] format. --- .../energy_management/widgets/analytics_device_dropdown.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart b/lib/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart index 4158da05..65157aa4 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/analytics/models/analytics_device.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart'; -import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -63,9 +62,7 @@ class AnalyticsDeviceDropdown extends StatelessWidget { style: _getTextStyle(context), padding: _defaultPadding, selectedItemBuilder: (context) { - return state.devices.map((e) { - return Text(e.name); - }).toList(); + return state.devices.map((e) => Text(e.name)).toList(); }, items: state.devices.map((e) { return DropdownMenuItem( From 06b320a75d72bee1832ea6f23c4262d83725266c Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 21 May 2025 10:16:12 +0300 Subject: [PATCH 25/25] move icon to the center and change subspace title name --- lib/pages/common/custom_table.dart | 83 ++++++++++++------- .../shared/device_control_dialog.dart | 3 +- 2 files changed, 53 insertions(+), 33 deletions(-) diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart index 60abc0d2..3ec902ef 100644 --- a/lib/pages/common/custom_table.dart +++ b/lib/pages/common/custom_table.dart @@ -63,7 +63,8 @@ class _DynamicTableState extends State { } } - bool _compareListOfLists(List> oldList, List> newList) { + bool _compareListOfLists( + List> oldList, List> newList) { // Check if the old and new lists are the same if (oldList.length != newList.length) return false; @@ -132,46 +133,60 @@ class _DynamicTableState extends State { children: [ if (widget.withCheckBox) _buildSelectAllCheckbox(), ...List.generate(widget.headers.length, (index) { - return _buildTableHeaderCell(widget.headers[index], index); + return _buildTableHeaderCell( + widget.headers[index], index); }) //...widget.headers.map((header) => _buildTableHeaderCell(header)), ], ), ), widget.isEmpty - ? Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Column( - children: [ - SvgPicture.asset(Assets.emptyTable), - const SizedBox( - height: 15, - ), - Text( - widget.tableName == 'AccessManagement' ? 'No Password ' : 'No Devices', - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith(color: ColorsManager.grayColor), - ) - ], - ), - ], - ), - ], + ? SizedBox( + height: widget.size.height * 0.5, + width: widget.size.width, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + SvgPicture.asset(Assets.emptyTable), + const SizedBox( + height: 15, + ), + Text( + widget.tableName == 'AccessManagement' + ? 'No Password ' + : 'No Devices', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + color: + ColorsManager.grayColor), + ) + ], + ), + ], + ), + ], + ), ) : Column( - children: List.generate(widget.data.length, (index) { + children: + List.generate(widget.data.length, (index) { final row = widget.data[index]; return Row( children: [ - if (widget.withCheckBox) _buildRowCheckbox(index, widget.size.height * 0.08), - ...row.map((cell) => _buildTableCell(cell.toString(), widget.size.height * 0.08)), + if (widget.withCheckBox) + _buildRowCheckbox( + index, widget.size.height * 0.08), + ...row.map((cell) => _buildTableCell( + cell.toString(), + widget.size.height * 0.08)), ], ); }), @@ -196,7 +211,9 @@ class _DynamicTableState extends State { ), child: Checkbox( value: _selectAll, - onChanged: widget.withSelectAll && widget.data.isNotEmpty ? _toggleSelectAll : null, + onChanged: widget.withSelectAll && widget.data.isNotEmpty + ? _toggleSelectAll + : null, ), ); } @@ -238,7 +255,9 @@ class _DynamicTableState extends State { constraints: const BoxConstraints.expand(height: 40), alignment: Alignment.centerLeft, child: Padding( - padding: EdgeInsets.symmetric(horizontal: index == widget.headers.length - 1 ? 12 : 8.0, vertical: 4), + padding: EdgeInsets.symmetric( + horizontal: index == widget.headers.length - 1 ? 12 : 8.0, + vertical: 4), child: Text( title, style: context.textTheme.titleSmall!.copyWith( diff --git a/lib/pages/device_managment/shared/device_control_dialog.dart b/lib/pages/device_managment/shared/device_control_dialog.dart index 7304dd07..c9cd4648 100644 --- a/lib/pages/device_managment/shared/device_control_dialog.dart +++ b/lib/pages/device_managment/shared/device_control_dialog.dart @@ -97,7 +97,8 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode { children: [ _buildInfoRow('Space Name:', device.spaces?.firstOrNull?.spaceName ?? 'N/A'), - _buildInfoRow('Room:', device.subspace?.subspaceName ?? 'N/A'), + _buildInfoRow( + 'Sub space:', device.subspace?.subspaceName ?? 'N/A'), ], ), TableRow(