From 745205063e5e4ec6c4b84d256af60ec08c24a6ef Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 15 May 2025 12:46:12 +0300 Subject: [PATCH 01/29] added correct behavior to `OccupancyDataLoadingStrategy`. --- .../analytics/strategies/occupancy_data_loading_strategy.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart index fb93ec30..5a9382a4 100644 --- a/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart +++ b/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart @@ -73,7 +73,9 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy { CommunityModel community, SpaceModel child, ) { - // Do nothing + if (child.children.isNotEmpty) { + return onSpaceSelected(context, community, child); + } } @override From baaf5111b1fb83978b3c38dcc276af567258b5a9 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 15 May 2025 12:48:18 +0300 Subject: [PATCH 02/29] Applied correct business logic in `EnergyManagementDataLoadingStrategy`. --- .../strategies/energy_management_data_loading_strategy.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pages/analytics/modules/analytics/strategies/energy_management_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/energy_management_data_loading_strategy.dart index 1e251a41..c0d03879 100644 --- a/lib/pages/analytics/modules/analytics/strategies/energy_management_data_loading_strategy.dart +++ b/lib/pages/analytics/modules/analytics/strategies/energy_management_data_loading_strategy.dart @@ -14,7 +14,6 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg CommunityModel community, List spaces, ) { - // Add to space tree bloc first context.read().add( OnCommunitySelected( community.uuid, @@ -69,7 +68,9 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg CommunityModel community, SpaceModel child, ) { - // Do nothing else as per original implementation + if (child.children.isNotEmpty) { + return onSpaceSelected(context, community, child); + } } @override From 0a94557eeecea93934edaa1512cfcd56a927429f Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 21 May 2025 10:23:31 +0300 Subject: [PATCH 03/29] SP-1510-Occupancy Chart API Integration. --- lib/pages/analytics/models/occupacy.dart | 24 +++++++++++--- .../analytics/views/analytics_page.dart | 8 +++-- .../helpers/fetch_occupancy_data_helper.dart | 5 +-- .../occupancy/widgets/occupancy_chart.dart | 14 ++++---- .../widgets/occupancy_chart_box.dart | 3 -- .../analytics/params/get_occupancy_param.dart | 4 --- .../occupacy/fake_occupacy_service.dart | 19 ----------- .../occupacy/remote_occupancy_service.dart | 33 +++++++++++++++++++ 8 files changed, 66 insertions(+), 44 deletions(-) delete mode 100644 lib/pages/analytics/services/occupacy/fake_occupacy_service.dart create mode 100644 lib/pages/analytics/services/occupacy/remote_occupancy_service.dart diff --git a/lib/pages/analytics/models/occupacy.dart b/lib/pages/analytics/models/occupacy.dart index ab53e5c2..b4b8dac9 100644 --- a/lib/pages/analytics/models/occupacy.dart +++ b/lib/pages/analytics/models/occupacy.dart @@ -1,18 +1,32 @@ import 'package:equatable/equatable.dart'; class Occupacy extends Equatable { - final String date; + final DateTime date; final String occupancy; + final String spaceUuid; + final int occupiedSeconds; - const Occupacy({required this.date, required this.occupancy}); + const Occupacy({ + required this.date, + required this.occupancy, + required this.spaceUuid, + required this.occupiedSeconds, + }); factory Occupacy.fromJson(Map json) { return Occupacy( - date: json['date'] as String, - occupancy: json['occupancy'] as String, + date: DateTime.parse(json['event_date'] as String? ?? '${DateTime.now()}'), + occupancy: (json['occupancy_percentage'] ?? 0).toString(), + spaceUuid: json['space_uuid'] as String? ?? '', + occupiedSeconds: json['occupied_seconds'] as int? ?? 0, ); } @override - List get props => [date, occupancy]; + List get props => [ + date, + occupancy, + spaceUuid, + occupiedSeconds, + ]; } diff --git a/lib/pages/analytics/modules/analytics/views/analytics_page.dart b/lib/pages/analytics/modules/analytics/views/analytics_page.dart index 72cc7d51..18f86a90 100644 --- a/lib/pages/analytics/modules/analytics/views/analytics_page.dart +++ b/lib/pages/analytics/modules/analytics/views/analytics_page.dart @@ -17,7 +17,7 @@ import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_en 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/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/occupacy/remote_occupancy_service.dart'; import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/remote_occupancy_heat_map_service.dart'; import 'package:syncrow_web/pages/analytics/services/power_clamp_info/remote_power_clamp_info_service.dart'; import 'package:syncrow_web/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart'; @@ -75,7 +75,11 @@ class _AnalyticsPageState extends State { FirebaseRealtimeDeviceService(), ), ), - BlocProvider(create: (context) => OccupancyBloc(FakeOccupacyService())), + BlocProvider( + create: (context) => OccupancyBloc( + RemoteOccupancyService(_httpService), + ), + ), BlocProvider( create: (context) => OccupancyHeatMapBloc( RemoteOccupancyHeatMapService(_httpService), 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 5882ada5..0b01fda2 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 @@ -30,7 +30,6 @@ abstract final class FetchOccupancyDataHelper { loadOccupancyChartData( context, - communityUuid: communityId, spaceUuid: spaceId, date: datePickerState.monthlyDate, ); @@ -59,16 +58,14 @@ 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}', + monthDate: '${date.year}-${date.month.toString().padLeft(2, '0')}', spaceUuid: spaceUuid, - communityUuid: communityUuid, ), ), ); diff --git a/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart b/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart index 8a93ccf1..4a44c5a1 100644 --- a/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart +++ b/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart @@ -16,10 +16,10 @@ class OccupancyChart extends StatelessWidget { Widget build(BuildContext context) { return BarChart( BarChartData( - maxY: 1.0, + maxY: 100.0, gridData: EnergyManagementChartsHelper.gridData().copyWith( checkToShowHorizontalLine: (value) => true, - horizontalInterval: 0.25, + horizontalInterval: 20, ), borderData: EnergyManagementChartsHelper.borderData(), barTouchData: _barTouchData(context), @@ -39,8 +39,8 @@ class OccupancyChart extends StatelessWidget { groupVertically: true, barRods: [ BarChartRodData( - toY: 1.0, - fromY: double.parse(actual.occupancy) + 0.025, + toY: 100.0, + fromY: double.parse(actual.occupancy) + 2.5, color: ColorsManager.graysColor, width: _chartWidth, borderRadius: BorderRadius.circular(10), @@ -88,7 +88,7 @@ class OccupancyChart extends StatelessWidget { final data = chartData; final occupancyValue = double.parse(data[group.x.toInt()].occupancy); - final percentage = '${(occupancyValue * 100).toStringAsFixed(0)}%'; + final percentage = '${(occupancyValue).toStringAsFixed(0)}%'; return BarTooltipItem( percentage, @@ -108,14 +108,14 @@ class OccupancyChart extends StatelessWidget { final leftTitles = titlesData.leftTitles.copyWith( sideTitles: titlesData.leftTitles.sideTitles.copyWith( reservedSize: 70, - interval: 0.25, + interval: 20, getTitlesWidget: (value, meta) => Padding( padding: const EdgeInsetsDirectional.only(end: 12), child: FittedBox( alignment: AlignmentDirectional.centerStart, fit: BoxFit.scaleDown, child: Text( - '${(value * 100).toStringAsFixed(0)}%', + '${(value).toStringAsFixed(0)}%', style: context.textTheme.bodySmall?.copyWith( fontSize: 12, color: ColorsManager.greyColor, 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 8c41da48..ab1d1699 100644 --- a/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart_box.dart +++ b/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart_box.dart @@ -50,9 +50,6 @@ class OccupancyChartBox extends StatelessWidget { if (spaceTreeState.selectedSpaces.isNotEmpty) { FetchOccupancyDataHelper.loadOccupancyChartData( context, - communityUuid: - spaceTreeState.selectedCommunities.firstOrNull ?? - '', spaceUuid: spaceTreeState.selectedSpaces.firstOrNull ?? '', date: value, diff --git a/lib/pages/analytics/params/get_occupancy_param.dart b/lib/pages/analytics/params/get_occupancy_param.dart index ed1b9375..79faa18c 100644 --- a/lib/pages/analytics/params/get_occupancy_param.dart +++ b/lib/pages/analytics/params/get_occupancy_param.dart @@ -1,19 +1,15 @@ class GetOccupancyParam { final String monthDate; final String? spaceUuid; - final String communityUuid; GetOccupancyParam({ required this.monthDate, required this.spaceUuid, - required this.communityUuid, }); Map toJson() { return { 'monthDate': monthDate, - 'spaceUuid': spaceUuid, - 'communityUuid': communityUuid, }; } } diff --git a/lib/pages/analytics/services/occupacy/fake_occupacy_service.dart b/lib/pages/analytics/services/occupacy/fake_occupacy_service.dart deleted file mode 100644 index 503a358b..00000000 --- a/lib/pages/analytics/services/occupacy/fake_occupacy_service.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:syncrow_web/pages/analytics/models/occupacy.dart'; -import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart'; -import 'package:syncrow_web/pages/analytics/services/occupacy/occupacy_service.dart'; - -class FakeOccupacyService implements OccupacyService { - @override - Future> load(GetOccupancyParam param) async { - return await Future.delayed( - const Duration(seconds: 1), - () => List.generate( - 30, - (index) => Occupacy( - date: DateTime.now().subtract(Duration(days: index)).toString(), - occupancy: ((index / 100)).toString(), - ), - ), - ); - } -} diff --git a/lib/pages/analytics/services/occupacy/remote_occupancy_service.dart b/lib/pages/analytics/services/occupacy/remote_occupancy_service.dart new file mode 100644 index 00000000..7c52733b --- /dev/null +++ b/lib/pages/analytics/services/occupacy/remote_occupancy_service.dart @@ -0,0 +1,33 @@ +import 'package:syncrow_web/pages/analytics/models/occupacy.dart'; +import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart'; +import 'package:syncrow_web/pages/analytics/services/occupacy/occupacy_service.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; + +final class RemoteOccupancyService implements OccupacyService { + const RemoteOccupancyService(this._httpService); + + final HTTPService _httpService; + + @override + Future> load(GetOccupancyParam param) async { + try { + final response = await _httpService.get( + // path: '/occupancy/duration/space/${param.spaceUuid}', + path: '/occupancy/duration/space/25c96044-fadf-44bb-93c7-3c079e527ce6', + showServerMessage: true, + queryParameters: param.toJson(), + expectedResponseModel: (data) { + final json = data as Map? ?? {}; + final mappedData = json['data'] as List? ?? []; + return mappedData.map((e) { + final jsonData = e as Map; + return Occupacy.fromJson(jsonData); + }).toList(); + }, + ); + return response; + } catch (e) { + throw Exception('Failed to load energy consumption per phase: $e'); + } + } +} \ No newline at end of file From c9d15d102ba1be5f47a071d66ded14a88adf2253 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 21 May 2025 10:57:53 +0300 Subject: [PATCH 04/29] fixes in `OccupancyChart` for a more pleasant UI. --- .../analytics/modules/occupancy/widgets/occupancy_chart.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart b/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart index 4a44c5a1..4ff85841 100644 --- a/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart +++ b/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart @@ -33,6 +33,7 @@ class OccupancyChart extends StatelessWidget { ), barGroups: List.generate(chartData.length, (index) { final actual = chartData[index]; + final occupancyValue = double.parse(actual.occupancy); return BarChartGroupData( x: index, barsSpace: 0, @@ -40,13 +41,13 @@ class OccupancyChart extends StatelessWidget { barRods: [ BarChartRodData( toY: 100.0, - fromY: double.parse(actual.occupancy) + 2.5, + fromY: occupancyValue == 0 ? occupancyValue : occupancyValue + 2.5, color: ColorsManager.graysColor, width: _chartWidth, borderRadius: BorderRadius.circular(10), ), BarChartRodData( - toY: double.parse(actual.occupancy), + toY: occupancyValue, color: ColorsManager.vividBlue.withValues(alpha: 0.8), width: _chartWidth, borderRadius: BorderRadius.circular(10), From ecf588cfcb2ac27b2828e0c4fdff1ae9e227c287 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 21 May 2025 10:58:21 +0300 Subject: [PATCH 05/29] reverted to dynamic endpoint. --- .../analytics/services/occupacy/remote_occupancy_service.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pages/analytics/services/occupacy/remote_occupancy_service.dart b/lib/pages/analytics/services/occupacy/remote_occupancy_service.dart index 7c52733b..b8cce70a 100644 --- a/lib/pages/analytics/services/occupacy/remote_occupancy_service.dart +++ b/lib/pages/analytics/services/occupacy/remote_occupancy_service.dart @@ -12,8 +12,7 @@ final class RemoteOccupancyService implements OccupacyService { Future> load(GetOccupancyParam param) async { try { final response = await _httpService.get( - // path: '/occupancy/duration/space/${param.spaceUuid}', - path: '/occupancy/duration/space/25c96044-fadf-44bb-93c7-3c079e527ce6', + path: '/occupancy/duration/space/${param.spaceUuid}', showServerMessage: true, queryParameters: param.toJson(), expectedResponseModel: (data) { From e0ad7855d3698144dfb1483d7a3bfaf7263cc97a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 21 May 2025 10:59:04 +0300 Subject: [PATCH 06/29] converted `GetOccupancyParam.toJson` to an expression method. --- lib/pages/analytics/params/get_occupancy_param.dart | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/pages/analytics/params/get_occupancy_param.dart b/lib/pages/analytics/params/get_occupancy_param.dart index 79faa18c..b083197b 100644 --- a/lib/pages/analytics/params/get_occupancy_param.dart +++ b/lib/pages/analytics/params/get_occupancy_param.dart @@ -7,9 +7,5 @@ class GetOccupancyParam { required this.spaceUuid, }); - Map toJson() { - return { - 'monthDate': monthDate, - }; - } + Map toJson() => {'monthDate': monthDate}; } From c508d016c271b358653b3c263dd585e979acd5c3 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 21 May 2025 11:08:00 +0300 Subject: [PATCH 07/29] SP-1580-FE-Watermark-Does-Not-Match-Design-Specification --- assets/images/web_Background.png | Bin 0 -> 40842 bytes lib/utils/constants/assets.dart | 1 + lib/web_layout/web_scaffold.dart | 9 +++------ 3 files changed, 4 insertions(+), 6 deletions(-) create mode 100644 assets/images/web_Background.png diff --git a/assets/images/web_Background.png b/assets/images/web_Background.png new file mode 100644 index 0000000000000000000000000000000000000000..1d1dac6ed51b9dbc85575e9c7b618930bee5e2cf GIT binary patch literal 40842 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 Date: Wed, 21 May 2025 16:08:48 +0300 Subject: [PATCH 08/29] Apply correct business logic in `OccupancyDataLoadingStrategy`. --- .../occupancy_data_loading_strategy.dart | 43 ++++--------------- 1 file changed, 8 insertions(+), 35 deletions(-) diff --git a/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart index 5a9382a4..e22bbb9b 100644 --- a/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart +++ b/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart @@ -14,23 +14,7 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy { CommunityModel community, List spaces, ) { - context.read().add( - OnCommunitySelected( - community.uuid, - spaces.isNotEmpty ? [spaces.first] : [], - ), - ); - - final spaceTreeState = context.read().state; - if (spaceTreeState.selectedCommunities.contains(community.uuid)) { - clearData(context); - return; - } - FetchOccupancyDataHelper.loadOccupancyData( - context, - communityId: community.uuid, - spaceId: spaces.isNotEmpty ? spaces.first.uuid ?? '' : '', - ); + // Do Nothing } @override @@ -40,26 +24,17 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy { SpaceModel space, ) { final spaceTreeBloc = context.read(); - final selectedSpacesIds = spaceTreeBloc.state.selectedSpaces; - final isSpaceSelected = selectedSpacesIds.contains(space.uuid); + final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid); - if (selectedSpacesIds.isEmpty) { - spaceTreeBloc.add(OnCommunitySelected(community.uuid, [space])); - } else if (isSpaceSelected) { - spaceTreeBloc.add(const SpaceTreeClearSelectionEvent()); - } else { - spaceTreeBloc - ..add(const SpaceTreeClearSelectionEvent()) - ..add(OnSpaceSelected(community, space.uuid ?? '', [])); - } - - final spaceTreeState = context.read().state; - if (spaceTreeState.selectedCommunities.contains(community.uuid) || - spaceTreeState.selectedSpaces.contains(space.uuid)) { + if (isSpaceSelected) { clearData(context); return; } + spaceTreeBloc + ..add(const SpaceTreeClearSelectionEvent()) + ..add(OnSpaceSelected(community, space.uuid ?? '', [])); + FetchOccupancyDataHelper.loadOccupancyData( context, communityId: community.uuid, @@ -73,9 +48,7 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy { CommunityModel community, SpaceModel child, ) { - if (child.children.isNotEmpty) { - return onSpaceSelected(context, community, child); - } + onSpaceSelected(context, community, child); } @override From 568b6be354c9ec8f7eedd33f04027dfd1c5a0abc Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 21 May 2025 16:46:38 +0300 Subject: [PATCH 09/29] Created `AirQualityView` widget for the new Air Quality analytics module. --- .../modules/air_quality/views/air_quality_view.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 lib/pages/analytics/modules/air_quality/views/air_quality_view.dart diff --git a/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart b/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart new file mode 100644 index 00000000..8844eb9f --- /dev/null +++ b/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class AirQualityView extends StatelessWidget { + const AirQualityView({super.key}); + + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} From 5a61647fe4d4e68d4412b8fb93ec9948a3273244 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 21 May 2025 16:49:30 +0300 Subject: [PATCH 10/29] Prepared and created the necessary component for the air quality loading strategy for the side bar selection, and for loading data in different parts of the UI. --- .../fetch_air_quality_data_helper.dart | 19 ++++++++++ .../analytics/enums/analytics_page_tab.dart | 5 +++ .../air_quality_data_loading_strategy.dart | 38 +++++++++++++++++++ ...alytics_data_loading_strategy_factory.dart | 2 + 4 files changed, 64 insertions(+) create mode 100644 lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart create mode 100644 lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart diff --git a/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart b/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart new file mode 100644 index 00000000..722b9210 --- /dev/null +++ b/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; + +abstract final class FetchAirQualityDataHelper { + const FetchAirQualityDataHelper._(); + + static void loadAirQualityData( + BuildContext context, + CommunityModel community, + SpaceModel space, + ) { + // TODO: implement loadAirQualityData + } + + static void clearAllData(BuildContext context) { + // TODO: implement clearAllData + } +} diff --git a/lib/pages/analytics/modules/analytics/enums/analytics_page_tab.dart b/lib/pages/analytics/modules/analytics/enums/analytics_page_tab.dart index b26cfc95..6552f6cf 100644 --- a/lib/pages/analytics/modules/analytics/enums/analytics_page_tab.dart +++ b/lib/pages/analytics/modules/analytics/enums/analytics_page_tab.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/views/air_quality_view.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/views/analytics_energy_management_view.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart'; @@ -10,6 +11,10 @@ enum AnalyticsPageTab { occupancy( title: 'Occupancy', child: AnalyticsOccupancyView(), + ), + airQuality( + title: 'Air Quality', + child: AirQualityView(), ); const AnalyticsPageTab({ diff --git a/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart new file mode 100644 index 00000000..636bc53d --- /dev/null +++ b/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; + +final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrategy { + @override + void onCommunitySelected( + BuildContext context, + CommunityModel community, + List spaces, + ) { + // TODO: implement onCommunitySelected + } + + @override + void onSpaceSelected( + BuildContext context, + CommunityModel community, + SpaceModel space, + ) { + // TODO: implement onSpaceSelected + } + + @override + void onChildSpaceSelected( + BuildContext context, + CommunityModel community, + SpaceModel child, + ) { + // TODO: implement onChildSpaceSelected + } + + @override + void clearData(BuildContext context) { + // TODO: implement clearData + } +} diff --git a/lib/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy_factory.dart b/lib/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy_factory.dart index 8b8bb60f..19b0aff2 100644 --- a/lib/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy_factory.dart +++ b/lib/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy_factory.dart @@ -1,4 +1,5 @@ import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart'; +import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/energy_management_data_loading_strategy.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart'; @@ -9,6 +10,7 @@ abstract final class AnalyticsDataLoadingStrategyFactory { return switch (tab) { AnalyticsPageTab.energyManagement => EnergyManagementDataLoadingStrategy(), AnalyticsPageTab.occupancy => OccupancyDataLoadingStrategy(), + AnalyticsPageTab.airQuality => AirQualityDataLoadingStrategy(), }; } } From d2eea337141650e9235b17709640eb28008e13e6 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 22 May 2025 12:24:13 +0300 Subject: [PATCH 11/29] Prepared `AirQualityView` layout and structure with PlaceHolder widgets. --- .../air_quality/views/air_quality_view.dart | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart b/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart index 8844eb9f..ef2b8f51 100644 --- a/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart +++ b/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart @@ -3,8 +3,57 @@ import 'package:flutter/material.dart'; class AirQualityView extends StatelessWidget { const AirQualityView({super.key}); + static const _padding = EdgeInsetsDirectional.all(32); + @override Widget build(BuildContext context) { - return const Placeholder(); + return LayoutBuilder( + builder: (context, constraints) { + final isMediumOrLess = constraints.maxWidth <= 900; + final height = MediaQuery.sizeOf(context).height; + if (isMediumOrLess) { + return SingleChildScrollView( + padding: _padding, + child: Column( + spacing: 32, + children: [ + SizedBox(height: height * 1.2, child: const Placeholder()), + SizedBox(height: height * 0.5, child: const Placeholder()), + SizedBox(height: height * 0.5, child: const Placeholder()), + ], + ), + ); + } + + return SingleChildScrollView( + child: Container( + padding: _padding, + height: height * 1, + child: const Column( + children: [ + Expanded( + child: Row( + spacing: 32, + children: [ + Expanded( + flex: 2, + child: Column( + spacing: 20, + children: [ + Expanded(child: Placeholder()), + Expanded(child: Placeholder()), + ], + ), + ), + Expanded(child: Placeholder()), + ], + ), + ), + ], + ), + ), + ); + }, + ); } } From 9eaa367d320065b18224606b14b4ec805021a734 Mon Sep 17 00:00:00 2001 From: Rafeek Alkhoudare Date: Thu, 22 May 2025 04:52:23 -0500 Subject: [PATCH 12/29] fix horizontal scroll bar --- lib/pages/common/custom_table.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart index 3ec902ef..04334393 100644 --- a/lib/pages/common/custom_table.dart +++ b/lib/pages/common/custom_table.dart @@ -112,8 +112,8 @@ class _DynamicTableState extends State { trackVisibility: true, child: Scrollbar( controller: _horizontalScrollController, - thumbVisibility: false, - trackVisibility: false, + thumbVisibility: true, + trackVisibility: true, notificationPredicate: (notif) => notif.depth == 1, child: SingleChildScrollView( controller: _verticalScrollController, From e792dbd72f73c0d65dc10276a16ced48d951b3e0 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 22 May 2025 14:58:42 +0300 Subject: [PATCH 13/29] SP-1591/ Implement business logic in `AirQualityDataLoadingStrategy` for community structure loading strategy. --- .../fetch_air_quality_data_helper.dart | 10 ++-- .../air_quality_data_loading_strategy.dart | 55 +++++++++++++++++-- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart b/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart index 722b9210..aa2da2da 100644 --- a/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart +++ b/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart @@ -1,15 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; abstract final class FetchAirQualityDataHelper { const FetchAirQualityDataHelper._(); static void loadAirQualityData( - BuildContext context, - CommunityModel community, - SpaceModel space, - ) { + BuildContext context, { + required String communityUuid, + required String spaceUuid, + }) { // TODO: implement loadAirQualityData } diff --git a/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart index 636bc53d..af355e6d 100644 --- a/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart +++ b/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart @@ -1,5 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; @@ -10,7 +14,24 @@ final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrateg CommunityModel community, List spaces, ) { - // TODO: implement onCommunitySelected + context.read().add( + OnCommunitySelected( + community.uuid, + spaces.isNotEmpty ? [spaces.first] : [], + ), + ); + + final spaceTreeState = context.read().state; + if (spaceTreeState.selectedCommunities.contains(community.uuid)) { + clearData(context); + return; + } + + FetchAirQualityDataHelper.loadAirQualityData( + context, + communityUuid: community.uuid, + spaceUuid: spaces.isNotEmpty ? (spaces.first.uuid ?? '') : '', + ); } @override @@ -19,7 +40,32 @@ final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrateg CommunityModel community, SpaceModel space, ) { - // TODO: implement onSpaceSelected + final spaceTreeBloc = context.read(); + final selectedSpacesIds = spaceTreeBloc.state.selectedSpaces; + final isSpaceSelected = selectedSpacesIds.contains(space.uuid); + + if (selectedSpacesIds.isEmpty) { + spaceTreeBloc.add(OnCommunitySelected(community.uuid, [space])); + } else if (isSpaceSelected) { + spaceTreeBloc.add(const SpaceTreeClearSelectionEvent()); + } else { + spaceTreeBloc + ..add(const SpaceTreeClearSelectionEvent()) + ..add(OnSpaceSelected(community, space.uuid ?? '', [])); + } + + final spaceTreeState = context.read().state; + if (spaceTreeState.selectedCommunities.contains(community.uuid) || + spaceTreeState.selectedSpaces.contains(space.uuid)) { + clearData(context); + return; + } + + FetchAirQualityDataHelper.loadAirQualityData( + context, + communityUuid: community.uuid, + spaceUuid: space.uuid ?? '', + ); } @override @@ -28,11 +74,12 @@ final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrateg CommunityModel community, SpaceModel child, ) { - // TODO: implement onChildSpaceSelected + // Do nothing } @override void clearData(BuildContext context) { - // TODO: implement clearData + context.read().add(const SpaceTreeClearSelectionEvent()); + FetchAirQualityDataHelper.clearAllData(context); } } From 9adbbb9a2d00996df1e7e1b719b79652e3de2b2d Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 22 May 2025 15:19:50 +0300 Subject: [PATCH 14/29] Integrated and implemented devices dropdown into the newly created widget `AirQualityEndSideWidget`. --- .../fetch_air_quality_data_helper.dart | 39 +++++++- .../air_quality/views/air_quality_view.dart | 8 +- .../widgets/air_quality_end_side_widget.dart | 88 +++++++++++++++++++ 3 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart diff --git a/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart b/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart index aa2da2da..dd646063 100644 --- a/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart +++ b/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart @@ -1,4 +1,8 @@ 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/realtime_device_changes/realtime_device_changes_bloc.dart'; +import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart'; abstract final class FetchAirQualityDataHelper { const FetchAirQualityDataHelper._(); @@ -8,10 +12,41 @@ abstract final class FetchAirQualityDataHelper { required String communityUuid, required String spaceUuid, }) { - // TODO: implement loadAirQualityData + loadAnalyticsDevices( + context, + communityUuid: communityUuid, + spaceUuid: spaceUuid, + ); } static void clearAllData(BuildContext context) { - // TODO: implement clearAllData + context.read().add( + const ClearAnalyticsDeviceEvent(), + ); + context.read().add( + const RealtimeDeviceChangesClosed(), + ); + } + + static void loadAnalyticsDevices( + BuildContext context, { + required String communityUuid, + required String spaceUuid, + }) { + context.read().add( + LoadAnalyticsDevicesEvent( + param: GetAnalyticsDevicesParam( + communityUuid: communityUuid, + spaceUuid: spaceUuid, + deviceTypes: ['AQI'], + requestType: AnalyticsDeviceRequestType.energyManagement, + ), + onSuccess: (device) { + context.read() + ..add(const RealtimeDeviceChangesClosed()) + ..add(RealtimeDeviceChangesStarted(device.uuid)); + }, + ), + ); } } diff --git a/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart b/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart index ef2b8f51..3b950e55 100644 --- a/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart +++ b/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart'; class AirQualityView extends StatelessWidget { const AirQualityView({super.key}); @@ -17,7 +18,10 @@ class AirQualityView extends StatelessWidget { child: Column( spacing: 32, children: [ - SizedBox(height: height * 1.2, child: const Placeholder()), + SizedBox( + height: height * 1.2, + child: const AirQualityEndSideWidget(), + ), SizedBox(height: height * 0.5, child: const Placeholder()), SizedBox(height: height * 0.5, child: const Placeholder()), ], @@ -45,7 +49,7 @@ class AirQualityView extends StatelessWidget { ], ), ), - Expanded(child: Placeholder()), + Expanded(child: AirQualityEndSideWidget()), ], ), ), diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart new file mode 100644 index 00000000..2d6ace36 --- /dev/null +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart @@ -0,0 +1,88 @@ +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/helpers/fetch_energy_management_data_helper.dart'; +import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class AirQualityEndSideWidget extends StatelessWidget { + const AirQualityEndSideWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: subSectionContainerDecoration.copyWith( + borderRadius: BorderRadius.circular(30), + ), + padding: const EdgeInsetsDirectional.all(32), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(context), + Text( + 'Device ID:', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 12, + ), + ), + const SizedBox(height: 6), + SelectableText( + context.watch().state.selectedDevice?.uuid ?? + 'N/A', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontWeight: FontWeight.w400, + fontSize: 12, + ), + ), + ], + ), + ); + } + + Widget _buildHeader(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + flex: 3, + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: AlignmentDirectional.centerStart, + child: SelectableText( + 'AQI Sensor', + style: context.textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.w700, + color: ColorsManager.vividBlue.withValues(alpha: 0.6), + fontSize: 18, + ), + ), + ), + ), + const Spacer(), + Expanded( + flex: 2, + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: AlignmentDirectional.centerEnd, + child: AnalyticsDeviceDropdown( + onChanged: (value) { + context.read().add( + SelectAnalyticsDeviceEvent(value), + ); + FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges( + context, + deviceUuid: value.uuid, + ); + }, + ), + ), + ), + ], + ); + } +} From 717d6983783f5517759fbf64dcf3c15351f9f6a3 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 22 May 2025 15:23:42 +0300 Subject: [PATCH 15/29] can select child spaces with children in `AirQualityDataLoadingStrategy`. --- .../analytics/strategies/air_quality_data_loading_strategy.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart index af355e6d..c207d2ae 100644 --- a/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart +++ b/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart @@ -74,7 +74,7 @@ final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrateg CommunityModel community, SpaceModel child, ) { - // Do nothing + if (child.children.isNotEmpty) return onSpaceSelected(context, community, child); } @override From 5eeac01666de755b42a2d7ebf8f7e00aec017baf Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 22 May 2025 15:35:04 +0300 Subject: [PATCH 16/29] cannot select a community in `AirQualityDataLoadingStrategy`. --- .../air_quality_data_loading_strategy.dart | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart index c207d2ae..0a29a933 100644 --- a/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart +++ b/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart @@ -14,24 +14,7 @@ final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrateg CommunityModel community, List spaces, ) { - context.read().add( - OnCommunitySelected( - community.uuid, - spaces.isNotEmpty ? [spaces.first] : [], - ), - ); - - final spaceTreeState = context.read().state; - if (spaceTreeState.selectedCommunities.contains(community.uuid)) { - clearData(context); - return; - } - - FetchAirQualityDataHelper.loadAirQualityData( - context, - communityUuid: community.uuid, - spaceUuid: spaces.isNotEmpty ? (spaces.first.uuid ?? '') : '', - ); + // Do nothing } @override From 4c5b3908876a51ea72cf7ddb7ed6307627bc6bea Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 22 May 2025 15:42:49 +0300 Subject: [PATCH 17/29] Fixed typos. --- lib/pages/visitor_password/view/visitor_password_dialog.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index 4db5017c..6385f3a6 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -38,7 +38,7 @@ class VisitorPasswordDialog extends StatelessWidget { if (visitorBloc.passwordStatus!.failedOperations.isNotEmpty) Column( children: [ - const Text('Failed Devises'), + const Text('Failed Devices'), SizedBox( width: 200, height: 50, @@ -63,7 +63,7 @@ class VisitorPasswordDialog extends StatelessWidget { if (visitorBloc.passwordStatus!.successOperations.isNotEmpty) Column( children: [ - const Text('Success Devises'), + const Text('Success Devices'), SizedBox( width: 200, height: 50, From d43c1847ff76848818e0c013115c814910e06646 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 22 May 2025 15:44:19 +0300 Subject: [PATCH 18/29] SP-1591 --- .../air_quality_data_loading_strategy.dart | 2 +- .../occupancy_data_loading_strategy.dart | 22 +++---------------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart index 0a29a933..90a4da61 100644 --- a/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart +++ b/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart @@ -28,7 +28,7 @@ final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrateg final isSpaceSelected = selectedSpacesIds.contains(space.uuid); if (selectedSpacesIds.isEmpty) { - spaceTreeBloc.add(OnCommunitySelected(community.uuid, [space])); + spaceTreeBloc.add(OnSpaceSelected(community, space.uuid ?? '', [])); } else if (isSpaceSelected) { spaceTreeBloc.add(const SpaceTreeClearSelectionEvent()); } else { diff --git a/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart index fb93ec30..534d275f 100644 --- a/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart +++ b/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart @@ -14,23 +14,7 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy { CommunityModel community, List spaces, ) { - context.read().add( - OnCommunitySelected( - community.uuid, - spaces.isNotEmpty ? [spaces.first] : [], - ), - ); - - final spaceTreeState = context.read().state; - if (spaceTreeState.selectedCommunities.contains(community.uuid)) { - clearData(context); - return; - } - FetchOccupancyDataHelper.loadOccupancyData( - context, - communityId: community.uuid, - spaceId: spaces.isNotEmpty ? spaces.first.uuid ?? '' : '', - ); + // Do nothing } @override @@ -44,7 +28,7 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy { final isSpaceSelected = selectedSpacesIds.contains(space.uuid); if (selectedSpacesIds.isEmpty) { - spaceTreeBloc.add(OnCommunitySelected(community.uuid, [space])); + spaceTreeBloc.add(OnSpaceSelected(community, space.uuid ?? '', [])); } else if (isSpaceSelected) { spaceTreeBloc.add(const SpaceTreeClearSelectionEvent()); } else { @@ -73,7 +57,7 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy { CommunityModel community, SpaceModel child, ) { - // Do nothing + if (child.children.isNotEmpty) return onSpaceSelected(context, community, child); } @override From 8c53d5322a0e88517b1a595ed679178feefb4d8c Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 22 May 2025 15:53:18 +0300 Subject: [PATCH 19/29] SP-1591 --- .../air_quality_data_loading_strategy.dart | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart index 90a4da61..19f9775d 100644 --- a/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart +++ b/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart @@ -24,26 +24,17 @@ final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrateg SpaceModel space, ) { final spaceTreeBloc = context.read(); - final selectedSpacesIds = spaceTreeBloc.state.selectedSpaces; - final isSpaceSelected = selectedSpacesIds.contains(space.uuid); + final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid); - if (selectedSpacesIds.isEmpty) { - spaceTreeBloc.add(OnSpaceSelected(community, space.uuid ?? '', [])); - } else if (isSpaceSelected) { - spaceTreeBloc.add(const SpaceTreeClearSelectionEvent()); - } else { - spaceTreeBloc - ..add(const SpaceTreeClearSelectionEvent()) - ..add(OnSpaceSelected(community, space.uuid ?? '', [])); - } - - final spaceTreeState = context.read().state; - if (spaceTreeState.selectedCommunities.contains(community.uuid) || - spaceTreeState.selectedSpaces.contains(space.uuid)) { + if (isSpaceSelected) { clearData(context); return; } + spaceTreeBloc + ..add(const SpaceTreeClearSelectionEvent()) + ..add(OnSpaceSelected(community, space.uuid ?? '', [])); + FetchAirQualityDataHelper.loadAirQualityData( context, communityUuid: community.uuid, From 5b13962d41c774eaf916b85de3998fe4d19f8fae Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 22 May 2025 15:57:03 +0300 Subject: [PATCH 20/29] removed unnecessary * 1 calculation of height. --- .../analytics/modules/air_quality/views/air_quality_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart b/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart index 3b950e55..38f62cd7 100644 --- a/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart +++ b/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart @@ -32,7 +32,7 @@ class AirQualityView extends StatelessWidget { return SingleChildScrollView( child: Container( padding: _padding, - height: height * 1, + height: height, child: const Column( children: [ Expanded( From e9abac79337d0491a65ec2cb317776181d7669f0 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 22 May 2025 16:44:22 +0300 Subject: [PATCH 21/29] added analytics icon. --- assets/icons/landing_analytics.svg | 19 +++++ lib/utils/constants/assets.dart | 112 ++++++++++------------------- 2 files changed, 58 insertions(+), 73 deletions(-) create mode 100644 assets/icons/landing_analytics.svg diff --git a/assets/icons/landing_analytics.svg b/assets/icons/landing_analytics.svg new file mode 100644 index 00000000..6f9fbbf0 --- /dev/null +++ b/assets/icons/landing_analytics.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index c86b7458..51053c9f 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -14,13 +14,14 @@ class Assets { static const String rightLine = "assets/images/right_line.png"; static const String google = "assets/images/google.svg"; static const String facebook = "assets/images/facebook.svg"; - static const String invisiblePassword = - "assets/images/Password_invisible.svg"; + static const String invisiblePassword = "assets/images/Password_invisible.svg"; static const String visiblePassword = "assets/images/password_visible.svg"; static const String accessIcon = "assets/images/access_icon.svg"; static const String spaseManagementIcon = "assets/images/spase_management_icon.svg"; static const String devicesIcon = "assets/images/devices_icon.svg"; + static const String analyticsIcon = "assets/icons/landing_analytics.svg"; + static const String moveinIcon = "assets/images/movein_icon.svg"; static const String constructionIcon = "assets/images/construction_icon.svg"; static const String energyIcon = "assets/images/energy_icon.svg"; @@ -32,8 +33,7 @@ class Assets { static const String emptyTable = "assets/images/empty_table.svg"; // General assets - static const String motionlessDetection = - "assets/icons/motionless_detection.svg"; + static const String motionlessDetection = "assets/icons/motionless_detection.svg"; static const String acHeating = "assets/icons/ac_heating.svg"; static const String acPowerOff = "assets/icons/ac_power_off.svg"; static const String acFanMiddle = "assets/icons/ac_fan_middle.svg"; @@ -70,22 +70,19 @@ class Assets { "assets/icons/automation_functions/temp_password_unlock.svg"; static const String doorlockNormalOpen = "assets/icons/automation_functions/doorlock_normal_open.svg"; - static const String doorbell = - "assets/icons/automation_functions/doorbell.svg"; + static const String doorbell = "assets/icons/automation_functions/doorbell.svg"; static const String remoteUnlockViaApp = "assets/icons/automation_functions/remote_unlock_via_app.svg"; static const String doubleLock = "assets/icons/automation_functions/double_lock.svg"; static const String selfTestResult = "assets/icons/automation_functions/self_test_result.svg"; - static const String lockAlarm = - "assets/icons/automation_functions/lock_alarm.svg"; + static const String lockAlarm = "assets/icons/automation_functions/lock_alarm.svg"; static const String presenceState = "assets/icons/automation_functions/presence_state.svg"; static const String currentTemp = "assets/icons/automation_functions/current_temp.svg"; - static const String presence = - "assets/icons/automation_functions/presence.svg"; + static const String presence = "assets/icons/automation_functions/presence.svg"; static const String residualElectricity = "assets/icons/automation_functions/residual_electricity.svg"; static const String hijackAlarm = @@ -102,15 +99,12 @@ class Assets { // Presence Sensor Assets static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg"; - static const String sensorPresenceIcon = - "assets/icons/sensor_presence_ic.svg"; + static const String sensorPresenceIcon = "assets/icons/sensor_presence_ic.svg"; static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg"; static const String illuminanceRecordIcon = "assets/icons/illuminance_record_ic.svg"; - static const String presenceRecordIcon = - "assets/icons/presence_record_ic.svg"; - static const String helpDescriptionIcon = - "assets/icons/help_description_ic.svg"; + static const String presenceRecordIcon = "assets/icons/presence_record_ic.svg"; + static const String helpDescriptionIcon = "assets/icons/help_description_ic.svg"; static const String lightPulp = "assets/icons/light_pulb.svg"; static const String acDevice = "assets/icons/ac_device.svg"; @@ -160,12 +154,10 @@ class Assets { static const String unit = 'assets/icons/unit_icon.svg'; static const String villa = 'assets/icons/villa_icon.svg'; static const String iconEdit = 'assets/icons/icon_edit_icon.svg'; - static const String textFieldSearch = - 'assets/icons/textfield_search_icon.svg'; + static const String textFieldSearch = 'assets/icons/textfield_search_icon.svg'; static const String roundedAddIcon = 'assets/icons/rounded_add_icon.svg'; static const String addIcon = 'assets/icons/add_icon.svg'; - static const String smartThermostatIcon = - 'assets/icons/smart_thermostat_icon.svg'; + static const String smartThermostatIcon = 'assets/icons/smart_thermostat_icon.svg'; static const String smartLightIcon = 'assets/icons/smart_light_icon.svg'; static const String presenceSensor = 'assets/icons/presence_sensor.svg'; static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg'; @@ -213,8 +205,7 @@ class Assets { //assets/icons/water_leak_normal.svg static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg'; //assets/icons/water_leak_detected.svg - static const String waterLeakDetected = - 'assets/icons/water_leak_detected.svg'; + static const String waterLeakDetected = 'assets/icons/water_leak_detected.svg'; //assets/icons/automation_records.svg static const String automationRecords = 'assets/icons/automation_records.svg'; @@ -285,16 +276,13 @@ class Assets { "assets/icons/functions_icons/sensitivity.svg"; static const String assetsSensitivityOperationIcon = "assets/icons/functions_icons/sesitivity_operation_icon.svg"; - static const String assetsAcPower = - "assets/icons/functions_icons/ac_power.svg"; + static const String assetsAcPower = "assets/icons/functions_icons/ac_power.svg"; static const String assetsAcPowerOFF = "assets/icons/functions_icons/ac_power_off.svg"; static const String assetsChildLock = "assets/icons/functions_icons/child_lock.svg"; - static const String assetsFreezing = - "assets/icons/functions_icons/freezing.svg"; - static const String assetsFanSpeed = - "assets/icons/functions_icons/fan_speed.svg"; + static const String assetsFreezing = "assets/icons/functions_icons/freezing.svg"; + static const String assetsFanSpeed = "assets/icons/functions_icons/fan_speed.svg"; static const String assetsAcCooling = "assets/icons/functions_icons/ac_cooling.svg"; static const String assetsAcHeating = @@ -303,8 +291,7 @@ class Assets { "assets/icons/functions_icons/celsius_degrees.svg"; static const String assetsTempreture = "assets/icons/functions_icons/tempreture.svg"; - static const String assetsAcFanLow = - "assets/icons/functions_icons/ac_fan_low.svg"; + static const String assetsAcFanLow = "assets/icons/functions_icons/ac_fan_low.svg"; static const String assetsAcFanMiddle = "assets/icons/functions_icons/ac_fan_middle.svg"; static const String assetsAcFanHigh = @@ -323,8 +310,7 @@ class Assets { "assets/icons/functions_icons/far_detection.svg"; static const String assetsFarDetectionFunction = "assets/icons/functions_icons/far_detection_function.svg"; - static const String assetsIndicator = - "assets/icons/functions_icons/indicator.svg"; + static const String assetsIndicator = "assets/icons/functions_icons/indicator.svg"; static const String assetsMotionDetection = "assets/icons/functions_icons/motion_detection.svg"; static const String assetsMotionlessDetection = @@ -337,8 +323,7 @@ class Assets { "assets/icons/functions_icons/master_state.svg"; static const String assetsSwitchAlarmSound = "assets/icons/functions_icons/switch_alarm_sound.svg"; - static const String assetsResetOff = - "assets/icons/functions_icons/reset_off.svg"; + static const String assetsResetOff = "assets/icons/functions_icons/reset_off.svg"; // Assets for automation_functions static const String assetsCardUnlock = @@ -382,14 +367,12 @@ class Assets { static const String activeUser = 'assets/icons/active_user.svg'; static const String deActiveUser = 'assets/icons/deactive_user.svg'; static const String invitedIcon = 'assets/icons/invited_icon.svg'; - static const String rectangleCheckBox = - 'assets/icons/rectangle_check_box.png'; + static const String rectangleCheckBox = 'assets/icons/rectangle_check_box.png'; static const String CheckBoxChecked = 'assets/icons/box_checked.png'; static const String emptyBox = 'assets/icons/empty_box.png'; static const String completeProcessIcon = 'assets/icons/compleate_process_icon.svg'; - static const String currentProcessIcon = - 'assets/icons/current_process_icon.svg'; + static const String currentProcessIcon = 'assets/icons/current_process_icon.svg'; static const String uncomplete_ProcessIcon = 'assets/icons/uncompleate_process_icon.svg'; static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg'; @@ -410,11 +393,9 @@ class Assets { static const String successIcon = 'assets/icons/success_icon.svg'; static const String spaceLocationIcon = 'assets/icons/spaseLocationIcon.svg'; static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.png'; - static const String scenesPlayIconCheck = - 'assets/icons/scenesPlayIconCheck.png'; + static const String scenesPlayIconCheck = 'assets/icons/scenesPlayIconCheck.png'; static const String presenceStateIcon = 'assets/icons/presence_state.svg'; - static const String currentDistanceIcon = - 'assets/icons/current_distance_icon.svg'; + static const String currentDistanceIcon = 'assets/icons/current_distance_icon.svg'; static const String farDetectionIcon = 'assets/icons/far_detection_icon.svg'; static const String motionDetectionSensitivityIcon = @@ -437,44 +418,29 @@ class Assets { static const String cpsMode4 = 'assets/icons/cps_mode4.svg'; static const String closeToMotion = 'assets/icons/close_to_motion.svg'; static const String farAwayMotion = 'assets/icons/far_away_motion.svg'; - static const String communicationFault = - 'assets/icons/communication_fault.svg'; + static const String communicationFault = 'assets/icons/communication_fault.svg'; static const String radarFault = 'assets/icons/radar_fault.svg'; - static const String selfTestingSuccess = - 'assets/icons/self_testing_success.svg'; - static const String selfTestingFailure = - 'assets/icons/self_testing_failure.svg'; - static const String selfTestingTimeout = - 'assets/icons/self_testing_timeout.svg'; + static const String selfTestingSuccess = 'assets/icons/self_testing_success.svg'; + static const String selfTestingFailure = 'assets/icons/self_testing_failure.svg'; + static const String selfTestingTimeout = 'assets/icons/self_testing_timeout.svg'; static const String movingSpeed = 'assets/icons/moving_speed.svg'; static const String boundary = 'assets/icons/boundary.svg'; static const String motionMeter = 'assets/icons/motion_meter.svg'; - static const String spatialStaticValue = - 'assets/icons/spatial_static_value.svg'; - static const String spatialMotionValue = - 'assets/icons/spatial_motion_value.svg'; + static const String spatialStaticValue = 'assets/icons/spatial_static_value.svg'; + static const String spatialMotionValue = 'assets/icons/spatial_motion_value.svg'; static const String presenceJudgementThrshold = 'assets/icons/presence_judgement_threshold.svg'; static const String spaceType = 'assets/icons/space_type.svg'; static const String sportsPara = 'assets/icons/sports_para.svg'; - static const String sensitivityFeature1 = - 'assets/icons/sensitivity_feature_1.svg'; - static const String sensitivityFeature2 = - 'assets/icons/sensitivity_feature_2.svg'; - static const String sensitivityFeature3 = - 'assets/icons/sensitivity_feature_3.svg'; - static const String sensitivityFeature4 = - 'assets/icons/sensitivity_feature_4.svg'; - static const String sensitivityFeature5 = - 'assets/icons/sensitivity_feature_5.svg'; - static const String sensitivityFeature6 = - 'assets/icons/sensitivity_feature_6.svg'; - static const String sensitivityFeature7 = - 'assets/icons/sensitivity_feature_7.svg'; - static const String sensitivityFeature8 = - 'assets/icons/sensitivity_feature_8.svg'; - static const String sensitivityFeature9 = - 'assets/icons/sensitivity_feature_9.svg'; + static const String sensitivityFeature1 = 'assets/icons/sensitivity_feature_1.svg'; + static const String sensitivityFeature2 = 'assets/icons/sensitivity_feature_2.svg'; + static const String sensitivityFeature3 = 'assets/icons/sensitivity_feature_3.svg'; + static const String sensitivityFeature4 = 'assets/icons/sensitivity_feature_4.svg'; + static const String sensitivityFeature5 = 'assets/icons/sensitivity_feature_5.svg'; + static const String sensitivityFeature6 = 'assets/icons/sensitivity_feature_6.svg'; + static const String sensitivityFeature7 = 'assets/icons/sensitivity_feature_7.svg'; + static const String sensitivityFeature8 = 'assets/icons/sensitivity_feature_8.svg'; + static const String sensitivityFeature9 = 'assets/icons/sensitivity_feature_9.svg'; static const String deviceTagIcon = 'assets/icons/device_tag_ic.svg'; static const String targetConfirmTimeIcon = 'assets/icons/target_confirm_time_icon.svg'; @@ -482,5 +448,5 @@ class Assets { static const String indentLevelIcon = 'assets/icons/indent_level_icon.svg'; static const String triggerLevelIcon = 'assets/icons/trigger_level_icon.svg'; static const String blankCalendar = 'assets/icons/blank_calendar.svg'; - static const String refreshStatusIcon = 'assets/icons/refresh_status_icon.svg'; + static const String refreshStatusIcon = 'assets/icons/refresh_status_icon.svg'; } From 7aa9e7e5dc90eb59c47f0362f874324cc12a04db Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 22 May 2025 16:44:32 +0300 Subject: [PATCH 22/29] fixed typos. --- lib/pages/visitor_password/view/visitor_password_dialog.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index 4db5017c..fff845f5 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -32,7 +32,7 @@ class VisitorPasswordDialog extends StatelessWidget { .stateDialog( context: context, message: 'Password Created Successfully', - title: 'Send Success', + title: 'Sent Successfully', widgeta: Column( children: [ if (visitorBloc.passwordStatus!.failedOperations.isNotEmpty) @@ -95,7 +95,7 @@ class VisitorPasswordDialog extends StatelessWidget { visitorBloc.stateDialog( context: context, message: state.message, - title: 'Something Wrong', + title: 'Something went wrong', ); } }, From 92abcdc4f9173ab26bb04f70a69bb0f140e95293 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 25 May 2025 10:57:23 +0300 Subject: [PATCH 23/29] SP-1492-landing_page_analytics_button_design. --- lib/pages/home/bloc/home_bloc.dart | 78 ++--------------------- lib/pages/home/view/home_card.dart | 30 ++------- lib/pages/home/view/home_page_mobile.dart | 57 +---------------- lib/pages/home/view/home_page_web.dart | 3 +- 4 files changed, 16 insertions(+), 152 deletions(-) diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index 33d55628..f6aab9eb 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -11,16 +11,11 @@ import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; import 'package:syncrow_web/services/home_api.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/routes_const.dart'; import 'package:syncrow_web/utils/navigation_service.dart'; class HomeBloc extends Bloc { - // final Graph graph = Graph()..isTree = true; - // final BuchheimWalkerConfiguration builder = BuchheimWalkerConfiguration(); - // List sourcesList = []; - // List destinationsList = []; UserModel? user; String terms = ''; String policy = ''; @@ -33,22 +28,6 @@ class HomeBloc extends Bloc { on(_confirmUserAgreement); } - // void _createNode(CreateNewNode event, Emitter emit) async { - // emit(HomeInitial()); - // sourcesList.add(event.sourceNode); - // destinationsList.add(event.destinationNode); - // for (int i = 0; i < sourcesList.length; i++) { - // graph.addEdge(sourcesList[i], destinationsList[i]); - // } - - // builder - // ..siblingSeparation = (100) - // ..levelSeparation = (150) - // ..subtreeSeparation = (150) - // ..orientation = (BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM); - // emit(HomeUpdateTree(graph: graph, builder: builder)); - // } - Future _fetchUserInfo(FetchUserInfo event, Emitter emit) async { try { var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey); @@ -99,16 +78,6 @@ class HomeBloc extends Bloc { } } -// static Future fetchUserInfo() async { -// try { -// var uuid = -// await const FlutterSecureStorage().read(key: UserModel.userUuidKey); -// user = await HomeApi().fetchUserInfo(uuid); -// } catch (e) { -// return; -// } -// } - List homeItems = [ HomeItemModel( title: 'Access Management', @@ -118,7 +87,7 @@ class HomeBloc extends Bloc { context.read().add(ClearCachedData()); context.go(RoutesConst.accessManagementPage); }, - color: null, + color: const Color(0xFF0036E6), ), HomeItemModel( title: 'Space Management', @@ -128,7 +97,7 @@ class HomeBloc extends Bloc { context.read().add(ClearCachedData()); context.go(RoutesConst.spacesManagementPage); }, - color: ColorsManager.primaryColor, + color: const Color(0xFF0026A2), ), HomeItemModel( title: 'Devices Management', @@ -140,12 +109,11 @@ class HomeBloc extends Bloc { .add(const TriggerSwitchTabsEvent(isRoutineTab: false)); context.go(RoutesConst.deviceManagementPage); }, - color: ColorsManager.primaryColor, + color: const Color(0xFF00165E), ), - HomeItemModel( title: 'Syncrow Analytics', - icon: Assets.devicesIcon, + icon: Assets.analyticsIcon, active: true, onPress: (context) { context.read().add(ClearCachedData()); @@ -153,43 +121,7 @@ class HomeBloc extends Bloc { .add(const TriggerSwitchTabsEvent(isRoutineTab: false)); context.go(RoutesConst.analytics); }, - color: ColorsManager.primaryColor, + color: const Color(0xFF023DFE), ), - - // HomeItemModel( - // title: 'Move in', - // icon: Assets.moveinIcon, - // active: false, - // onPress: (context) {}, - // color: ColorsManager.primaryColor, - // ), - // HomeItemModel( - // title: 'Construction', - // icon: Assets.constructionIcon, - // active: false, - // onPress: (context) {}, - // color: ColorsManager.primaryColor, - // ), - // HomeItemModel( - // title: 'Energy', - // icon: Assets.energyIcon, - // active: false, - // onPress: (context) {}, - // color: ColorsManager.slidingBlueColor.withOpacity(0.2), - // ), - // HomeItemModel( - // title: 'Integrations', - // icon: Assets.integrationsIcon, - // active: false, - // onPress: (context) {}, - // color: ColorsManager.slidingBlueColor.withOpacity(0.2), - // ), - // HomeItemModel( - // title: 'Asset', - // icon: Assets.assetIcon, - // active: false, - // onPress: (context) {}, - // color: ColorsManager.slidingBlueColor.withOpacity(0.2), - // ), ]; } diff --git a/lib/pages/home/view/home_card.dart b/lib/pages/home/view/home_card.dart index d2e71608..ef3bd8de 100644 --- a/lib/pages/home/view/home_card.dart +++ b/lib/pages/home/view/home_card.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; class HomeCard extends StatelessWidget { final bool active; @@ -8,6 +7,7 @@ class HomeCard extends StatelessWidget { final int index; final String name; final Function()? onTap; + final Color? color; const HomeCard({ super.key, required this.name, @@ -15,28 +15,16 @@ class HomeCard extends StatelessWidget { this.active = false, required this.img, required this.onTap, + required this.color, }); @override Widget build(BuildContext context) { - // bool evenNumbers = index % 2 == 0; return InkWell( onTap: active ? onTap : null, child: Container( padding: const EdgeInsets.only(left: 10, right: 10, bottom: 10), decoration: BoxDecoration( - color: index == 0 && active - ? ColorsManager.blue1.withOpacity(0.9) - : index == 1 && active - ? ColorsManager.blue2.withOpacity(0.9) - : index == 2 && active - ? ColorsManager.blue3 - : index == 4 && active == false - ? ColorsManager.blue4.withOpacity(0.2) - : index == 7 && active == false - ? ColorsManager.blue4.withOpacity(0.2) - : ColorsManager.blueColor.withOpacity(0.2), - // (active ?ColorsManager.blueColor - // : ColorsManager.blueColor.withOpacity(0.2)), + color: color, borderRadius: BorderRadius.circular(30), ), child: Column( @@ -64,15 +52,9 @@ class HomeCard extends StatelessWidget { ), const SizedBox(height: 10), Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - SizedBox( - child: SvgPicture.asset( - img, - ), - ), - ], + child: Align( + alignment: AlignmentDirectional.bottomEnd, + child: SvgPicture.asset(img), ), ), ], diff --git a/lib/pages/home/view/home_page_mobile.dart b/lib/pages/home/view/home_page_mobile.dart index d0719c3e..ad019ea8 100644 --- a/lib/pages/home/view/home_page_mobile.dart +++ b/lib/pages/home/view/home_page_mobile.dart @@ -50,7 +50,7 @@ class HomeMobilePage extends StatelessWidget { height: size.height * 0.6, width: size.width * 0.68, child: GridView.builder( - itemCount: homeItems.length, + itemCount: homeBloc.homeItems.length, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, @@ -61,7 +61,8 @@ class HomeMobilePage extends StatelessWidget { itemBuilder: (context, index) { return HomeCard( index: index, - active: homeBloc.homeItems[index].active!, + active: true, + color: homeBloc.homeItems[index].color, name: homeBloc.homeItems[index].title!, img: homeBloc.homeItems[index].icon!, onTap: () => @@ -78,56 +79,4 @@ class HomeMobilePage extends StatelessWidget { ), ); } - - final dynamic homeItems = [ - { - 'title': 'Access', - 'icon': Assets.accessIcon, - 'active': true, - }, - { - 'title': 'Space\nManagement', - 'icon': Assets.spaseManagementIcon, - 'color': ColorsManager.primaryColor, - 'active': true, - }, - { - 'title': 'Devices', - 'icon': Assets.devicesIcon, - 'active': true, - }, - { - 'title': 'Syncrow Analytics', - 'icon': Assets.iconEdit, - 'active': true, - }, - // { - // 'title': 'Move in', - // 'icon': Assets.moveinIcon, - // 'active': false, - // }, - // { - // 'title': 'Construction', - // 'icon': Assets.constructionIcon, - // 'active': false, - // }, - // { - // 'title': 'Energy', - // 'icon': Assets.energyIcon, - // 'color': ColorsManager.slidingBlueColor.withOpacity(0.2), - // 'active': false, - // }, - // { - // 'title': 'Integrations', - // 'icon': Assets.integrationsIcon, - // 'color': ColorsManager.slidingBlueColor.withOpacity(0.2), - // 'active': false, - // }, - // { - // 'title': 'Asset', - // 'icon': Assets.assetIcon, - // 'color': ColorsManager.slidingBlueColor.withOpacity(0.2), - // 'active': false, - // }, - ]; } diff --git a/lib/pages/home/view/home_page_web.dart b/lib/pages/home/view/home_page_web.dart index 9a59f51c..334cec4d 100644 --- a/lib/pages/home/view/home_page_web.dart +++ b/lib/pages/home/view/home_page_web.dart @@ -106,8 +106,9 @@ class _HomeWebPageState extends State { ), itemBuilder: (context, index) { return HomeCard( + color: homeBloc.homeItems[index].color, index: index, - active: homeBloc.homeItems[index].active!, + active: true, name: homeBloc.homeItems[index].title!, img: homeBloc.homeItems[index].icon!, onTap: () => homeBloc.homeItems[index].onPress(context), From 660649145833f476adb1854c9ca18e309778e773 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 25 May 2025 10:59:41 +0300 Subject: [PATCH 24/29] made `active` dynamic --- lib/pages/home/view/home_page_web.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/home/view/home_page_web.dart b/lib/pages/home/view/home_page_web.dart index 334cec4d..fb35fa04 100644 --- a/lib/pages/home/view/home_page_web.dart +++ b/lib/pages/home/view/home_page_web.dart @@ -108,7 +108,7 @@ class _HomeWebPageState extends State { return HomeCard( color: homeBloc.homeItems[index].color, index: index, - active: true, + active: homeBloc.homeItems[index].active!, name: homeBloc.homeItems[index].title!, img: homeBloc.homeItems[index].icon!, onTap: () => homeBloc.homeItems[index].onPress(context), From a878b9328a587e96ea2622ddb2c0fec28e23e5cb Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 25 May 2025 11:06:36 +0300 Subject: [PATCH 25/29] SP-1493 rework, can select a subspace in sidebar even when the space has no child-spaces. --- .../strategies/air_quality_data_loading_strategy.dart | 2 +- .../strategies/energy_management_data_loading_strategy.dart | 4 +--- .../analytics/strategies/occupancy_data_loading_strategy.dart | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart index 19f9775d..dc3b1c5e 100644 --- a/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart +++ b/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart @@ -48,7 +48,7 @@ final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrateg CommunityModel community, SpaceModel child, ) { - if (child.children.isNotEmpty) return onSpaceSelected(context, community, child); + return onSpaceSelected(context, community, child); } @override diff --git a/lib/pages/analytics/modules/analytics/strategies/energy_management_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/energy_management_data_loading_strategy.dart index c0d03879..e73b5179 100644 --- a/lib/pages/analytics/modules/analytics/strategies/energy_management_data_loading_strategy.dart +++ b/lib/pages/analytics/modules/analytics/strategies/energy_management_data_loading_strategy.dart @@ -68,9 +68,7 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg CommunityModel community, SpaceModel child, ) { - if (child.children.isNotEmpty) { - return onSpaceSelected(context, community, child); - } + return onSpaceSelected(context, community, child); } @override diff --git a/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart index 158f5128..5241564c 100644 --- a/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart +++ b/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart @@ -48,7 +48,7 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy { CommunityModel community, SpaceModel child, ) { - if (child.children.isNotEmpty) return onSpaceSelected(context, community, child); + return onSpaceSelected(context, community, child); } @override From 9d27ed2dc57a605ff6fa4fa267ecc8af209f6f2f Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 25 May 2025 11:13:24 +0300 Subject: [PATCH 26/29] SP-1506 rework, coloring and padding. --- .../helpers/energy_management_charts_helper.dart | 4 ++-- .../widgets/energy_consumption_by_phases_chart.dart | 2 +- .../widgets/energy_consumption_by_phases_chart_box.dart | 6 ++++-- 3 files changed, 7 insertions(+), 5 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 567e03ed..11c088e8 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: true, + minIncluded: false, interval: leftTitlesInterval, reservedSize: 110, getTitlesWidget: (value, meta) => Padding( @@ -50,7 +50,7 @@ abstract final class EnergyManagementChartsHelper { value.formatNumberToKwh, style: context.textTheme.bodySmall?.copyWith( fontSize: 12, - color: ColorsManager.greyColor, + color: ColorsManager.lightGreyColor, ), ), ), 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 1497d0fd..001f4d2c 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 @@ -170,7 +170,7 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget { child: Text( month, style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.greyColor, + color: ColorsManager.lightGreyColor, fontSize: 11, ), ), diff --git a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart_box.dart b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart_box.dart index 1766266c..1bd1ed9e 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart_box.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart_box.dart @@ -19,10 +19,12 @@ class EnergyConsumptionByPhasesChartBox extends StatelessWidget { decoration: secondarySection, child: Column( mainAxisSize: MainAxisSize.min, - spacing: 20, children: [ AnalyticsErrorWidget(state.errorMessage), - EnergyConsumptionByPhasesTitle(isLoading: state.status == EnergyConsumptionByPhasesStatus.loading,), + EnergyConsumptionByPhasesTitle( + isLoading: state.status == EnergyConsumptionByPhasesStatus.loading, + ), + const SizedBox(height: 20), Expanded( child: EnergyConsumptionByPhasesChart( energyData: state.chartData, From 12deceb7d319d17bb73213885d7363cb105df954 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 25 May 2025 11:35:01 +0300 Subject: [PATCH 27/29] SP-1513-rework --- .../power_clamp_phases_data_widget.dart | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart b/lib/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart index 1cb20aac..dc0aa050 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart @@ -125,7 +125,48 @@ class PowerClampPhasesDataWidget extends StatelessWidget { (e) => e.code == code, orElse: () => DataPoint(value: '--'), ); + final value = element?.value; + if (code.contains('Current')) { + return _formatCurrentValue(value?.toString()); + } + if (code.contains('PowerFactor')) { + return _formatPowerFactor(value?.toString()); + } + if (code.contains('Voltage')) { + return _formatVoltage(value?.toString()); + } + return value?.toString() ?? '--'; + } - return element?.value.toString() ?? '--'; + String _formatCurrentValue(String? value) { + if (value == null) return '--'; + String str = value; + if (str.isEmpty || str == '--') return '--'; + str = str.replaceAll(RegExp(r'[^0-9]'), ''); + if (str.isEmpty) return '--'; + if (str.length == 1) return '${str[0]}.0'; + return '${str[0]}.${str.substring(1)}'; + } + + String _formatPowerFactor(String? value) { + if (value == null) return '--'; + String str = value; + if (str.isEmpty || str == '--') return '--'; + str = str.replaceAll(RegExp(r'[^0-9]'), ''); + if (str.isEmpty) return '--'; + final intValue = int.tryParse(str); + if (intValue == null) return '--'; + final doubleValue = intValue / 100; + return doubleValue.toStringAsFixed(2); + } + + String _formatVoltage(String? value) { + if (value == null) return '--'; + String str = value; + if (str.isEmpty || str == '--') return '--'; + str = str.replaceAll(RegExp(r'[^0-9]'), ''); + if (str.isEmpty) return '--'; + if (str.length == 1) return '0.${str[0]}'; + return '${str.substring(0, str.length - 1)}.${str.substring(str.length - 1)}'; } } From 5a8ef578c3e9d27f5984372ed07d522bbaf36e14 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 26 May 2025 14:16:43 +0300 Subject: [PATCH 28/29] SP-1493-data-formatting --- .../widgets/power_clamp_energy_data_widget.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 8ae6cd7f..e8f802cd 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 @@ -69,13 +69,18 @@ class PowerClampEnergyDataWidget extends StatelessWidget { PowerClampEnergyStatus( iconPath: Assets.powerActiveIcon, title: 'Active', - value: _valueFromCode('EnergyConsumed', generalDataPoints), + value: _valueFromCode('ActivePower', generalDataPoints), unit: 'W', ), PowerClampEnergyStatus( iconPath: Assets.voltMeterIcon, title: 'Current', - value: _valueFromCode('Current', generalDataPoints), + value: _valueFromCode('Current', generalDataPoints) + .replaceAllMapped( + RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), + (Match m) => '${m[1]}.', + ) + .replaceAll('.0', ''), unit: 'A', ), PowerClampEnergyStatus( From 0b4337fb6ccf5940ebee04ec04e3572afa20005e Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 26 May 2025 15:17:29 +0300 Subject: [PATCH 29/29] sp-1493-data-formatting-2.0. --- .../widgets/power_clamp_phases_data_widget.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart b/lib/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart index dc0aa050..a96a7298 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart @@ -55,7 +55,7 @@ class PowerClampPhasesDataWidget extends StatelessWidget { iconPath: Assets.powerActiveIcon, title: 'Active Power', value: _valueFromCode( - code: 'ReactivePower$phaseSuffix', + code: 'ActivePower$phaseSuffix', points: phase?.dataPoints, ), unit: 'W',