From 17422edd0d29afdd5abe7cebba969dff5901ffe7 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 15:21:20 +0300 Subject: [PATCH 1/6] sp-1268-rework-v2. --- lib/features/scene/view/scene_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/features/scene/view/scene_view.dart b/lib/features/scene/view/scene_view.dart index 13fa92c..c62cbd8 100644 --- a/lib/features/scene/view/scene_view.dart +++ b/lib/features/scene/view/scene_view.dart @@ -111,7 +111,7 @@ class SceneView extends StatelessWidget { if (state is SceneLoaded) { final scenes = state.scenes; final automationList = state.automationList; - + if (scenes.isEmpty) return const EmptyDevicesWidget(); return pageType ? SizedBox( height: context.height * 0.1, From fbdf3817aba6ad9664ada6d7ff193c5da022d858 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 15:25:48 +0300 Subject: [PATCH 2/6] Created a factory helper to create a `SceneBloc`. --- .../scene/helper/scene_bloc_factory.dart | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 lib/features/scene/helper/scene_bloc_factory.dart diff --git a/lib/features/scene/helper/scene_bloc_factory.dart b/lib/features/scene/helper/scene_bloc_factory.dart new file mode 100644 index 0000000..e47a7b6 --- /dev/null +++ b/lib/features/scene/helper/scene_bloc_factory.dart @@ -0,0 +1,48 @@ +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/app_layout/model/community_model.dart'; +import 'package:syncrow_app/features/app_layout/model/space_model.dart'; +import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_event.dart'; + +abstract final class SceneBlocFactory { + static SceneBloc create({ + required bool pageType, + required HomeCubit homeCubit, + }) { + final selectedSpace = homeCubit.selectedSpace; + final defaultSpace = SpaceModel( + id: '-1', + name: '', + community: Community( + uuid: '-1', + name: '', + ), + ); + + final spaceId = selectedSpace?.id ?? defaultSpace.id; + final space = selectedSpace ?? defaultSpace; + final communityUuid = + selectedSpace?.community.uuid ?? defaultSpace.community.uuid; + + final sceneBloc = SceneBloc(); + + sceneBloc.add( + LoadScenes( + spaceId, + space, + showInDevice: pageType, + ), + ); + + if (!pageType) { + sceneBloc.add( + LoadAutomation( + spaceId, + communityUuid, + ), + ); + } + + return sceneBloc; + } +} From 8219de68216b817e4b76f44720d79bc221b20595 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 15:28:44 +0300 Subject: [PATCH 3/6] Injects `SceneBloc` using the new factory. --- lib/features/scene/view/scene_view.dart | 233 +++++++++++------------- 1 file changed, 103 insertions(+), 130 deletions(-) diff --git a/lib/features/scene/view/scene_view.dart b/lib/features/scene/view/scene_view.dart index c62cbd8..b78e3b5 100644 --- a/lib/features/scene/view/scene_view.dart +++ b/lib/features/scene/view/scene_view.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; -import 'package:syncrow_app/features/app_layout/model/community_model.dart'; import 'package:syncrow_app/features/app_layout/model/space_model.dart'; import 'package:syncrow_app/features/devices/view/widgets/scene_listview.dart'; import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_bloc.dart'; import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_event.dart'; import 'package:syncrow_app/features/scene/bloc/smart_scene/smart_scene_select_dart_bloc.dart'; +import 'package:syncrow_app/features/scene/helper/scene_bloc_factory.dart'; import 'package:syncrow_app/features/scene/widgets/empty_devices_widget.dart'; import 'package:syncrow_app/features/scene/widgets/scene_view_widget/scene_grid_view.dart'; import 'package:syncrow_app/features/scene/widgets/scene_view_widget/scene_header.dart'; @@ -22,71 +22,30 @@ class SceneView extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (BuildContext context) { - if (pageType) { - return SceneBloc() - ..add(LoadScenes( - HomeCubit.getInstance().selectedSpace?.id ?? '', - HomeCubit.getInstance().selectedSpace ?? - SpaceModel( - id: '-1', - name: '', - community: Community( - uuid: '-1', - name: '', - )), - showInDevice: pageType)); - } else { - return SceneBloc() - ..add(LoadScenes( - HomeCubit.getInstance().selectedSpace?.id ?? '', - HomeCubit.getInstance().selectedSpace ?? - SpaceModel( - id: '-1', - name: '', - community: Community( - uuid: '-1', - name: '', - )), - showInDevice: pageType)) - ..add(LoadAutomation( - HomeCubit.getInstance().selectedSpace?.id ?? '', - HomeCubit.getInstance().selectedSpace?.community.uuid ?? '')); - } - }, + create: (context) => SceneBlocFactory.create( + pageType: pageType, + homeCubit: HomeCubit.getInstance(), + ), child: BlocBuilder( builder: (context, state) { + final selectedSpace = HomeCubit.getInstance().selectedSpace; if (state is DeleteSceneSuccess) { if (state.success) { - BlocProvider.of(context).add(LoadScenes( - HomeCubit.getInstance().selectedSpace!.id, - HomeCubit.getInstance().selectedSpace!, - showInDevice: pageType)); - BlocProvider.of(context).add(LoadAutomation( - HomeCubit.getInstance().selectedSpace!.id, - HomeCubit.getInstance().selectedSpace!.community.uuid)); + _loadScenesAndAutomations(context, selectedSpace); } } if (state is CreateSceneWithTasks) { - if (state.success == true) { - BlocProvider.of(context).add(LoadScenes( - HomeCubit.getInstance().selectedSpace!.id, - HomeCubit.getInstance().selectedSpace!, - showInDevice: pageType)); - BlocProvider.of(context).add(LoadAutomation( - HomeCubit.getInstance().selectedSpace!.id, - HomeCubit.getInstance().selectedSpace!.community.uuid)); - context - .read() - .add(const SmartSceneClearEvent()); + if (state.success) { + _loadScenesAndAutomations(context, selectedSpace); + context.read().add(const SmartSceneClearEvent()); } } return BlocListener( listener: (context, state) { if (state is SceneTriggerSuccess) { context.showCustomSnackbar( - message: - 'Scene ${state.sceneName} triggered successfully!'); + message: 'Scene ${state.sceneName} triggered successfully!', + ); } }, child: HomeCubit.getInstance().spaces.isEmpty @@ -95,13 +54,14 @@ class SceneView extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - if (pageType == false) const SceneHeader(), - if (pageType == false) const SizedBox(height: 8), + if (!pageType) ...[ + const SceneHeader(), + const SizedBox(height: 8), + ], BlocBuilder( builder: (context, state) { if (state is SceneLoading) { - return const Center( - child: CircularProgressIndicator()); + return const Center(child: CircularProgressIndicator()); } if (state is SceneError) { return Center( @@ -112,83 +72,79 @@ class SceneView extends StatelessWidget { final scenes = state.scenes; final automationList = state.automationList; if (scenes.isEmpty) return const EmptyDevicesWidget(); - return pageType - ? SizedBox( - height: context.height * 0.1, - child: SceneListview( - scenes: scenes, - loadingSceneId: state.loadingSceneId, - ), - ) - : Expanded( - child: ListView( + + if (pageType) { + return SizedBox( + height: context.height * 0.1, + child: SceneListview( + scenes: scenes, + loadingSceneId: state.loadingSceneId, + ), + ); + } + + return Theme( + data: Theme.of(context).copyWith( + dividerColor: Colors.transparent, + ), + child: Expanded( + child: ListView( + children: [ + ExpansionTile( + tilePadding: const EdgeInsets.symmetric( + horizontal: 6, + ), + initiallyExpanded: true, + iconColor: ColorsManager.grayColor, + title: const BodyMedium( + text: 'Tap to run routines', + ), children: [ - Theme( - data: ThemeData().copyWith( - dividerColor: Colors.transparent), - child: ExpansionTile( - tilePadding: - const EdgeInsets.symmetric( - horizontal: 6), - initiallyExpanded: true, - iconColor: ColorsManager.grayColor, - title: const BodyMedium( - text: 'Tap to run routines'), - children: [ - scenes.isNotEmpty - ? SceneGrid( - scenes: scenes, - loadingSceneId: - state.loadingSceneId, - disablePlayButton: false, - loadingStates: state - .loadingStates, // Add this line - ) - : const Center( - child: BodyMedium( - text: - 'No scenes have been added yet', - ), - ), - const SizedBox(height: 10), - ], + if (scenes.isNotEmpty) + SceneGrid( + scenes: scenes, + loadingSceneId: state.loadingSceneId, + disablePlayButton: false, + loadingStates: state.loadingStates, + ) + else + const Center( + child: BodyMedium( + text: 'No scenes have been added yet', + ), ), - ), - Theme( - data: ThemeData().copyWith( - dividerColor: Colors.transparent), - child: ExpansionTile( - initiallyExpanded: true, - iconColor: ColorsManager.grayColor, - tilePadding: - const EdgeInsets.symmetric( - horizontal: 6), - title: const BodyMedium( - text: 'Automation'), - children: [ - automationList.isNotEmpty - ? SceneGrid( - scenes: automationList, - loadingSceneId: - state.loadingSceneId, - disablePlayButton: true, - loadingStates: state - .loadingStates, // Add this line - ) - : const Center( - child: BodyMedium( - text: - 'No automations have been added yet', - ), - ), - const SizedBox(height: 10), - ], - ), - ), - const SizedBox(height: 15), + const SizedBox(height: 10), ], ), - ); + ExpansionTile( + initiallyExpanded: true, + iconColor: ColorsManager.grayColor, + tilePadding: const EdgeInsets.symmetric( + horizontal: 6, + ), + title: const BodyMedium(text: 'Automation'), + children: [ + automationList.isNotEmpty + ? SceneGrid( + scenes: automationList, + loadingSceneId: state.loadingSceneId, + disablePlayButton: true, + loadingStates: state.loadingStates, + ) + : const Center( + child: BodyMedium( + text: + 'No automations have been added yet', + ), + ), + const SizedBox(height: 10), + ], + ), + const SizedBox(height: 15), + ], + ), + ), + ); } return const SizedBox.shrink(); }, @@ -200,4 +156,21 @@ class SceneView extends StatelessWidget { ), ); } + + void _loadScenesAndAutomations(BuildContext context, SpaceModel? selectedSpace) { + BlocProvider.of(context) + ..add( + LoadScenes( + selectedSpace!.id, + selectedSpace, + showInDevice: pageType, + ), + ) + ..add( + LoadAutomation( + selectedSpace.id, + selectedSpace.community.uuid, + ), + ); + } } From 31025e917696957e79f22dae37cae84da9ab5053 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 15:49:13 +0300 Subject: [PATCH 4/6] Refactors SceneView constructor and improves conditional rendering for automation list --- lib/features/scene/view/scene_view.dart | 37 +++++++++++++------------ 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/features/scene/view/scene_view.dart b/lib/features/scene/view/scene_view.dart index b78e3b5..b9999bf 100644 --- a/lib/features/scene/view/scene_view.dart +++ b/lib/features/scene/view/scene_view.dart @@ -16,8 +16,12 @@ import 'package:syncrow_app/utils/context_extension.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; class SceneView extends StatelessWidget { + const SceneView({ + this.pageType = false, + super.key, + }); + final bool pageType; - const SceneView({super.key, this.pageType = false}); @override Widget build(BuildContext context) { @@ -30,9 +34,7 @@ class SceneView extends StatelessWidget { builder: (context, state) { final selectedSpace = HomeCubit.getInstance().selectedSpace; if (state is DeleteSceneSuccess) { - if (state.success) { - _loadScenesAndAutomations(context, selectedSpace); - } + if (state.success) _loadScenesAndAutomations(context, selectedSpace); } if (state is CreateSceneWithTasks) { if (state.success) { @@ -124,19 +126,20 @@ class SceneView extends StatelessWidget { ), title: const BodyMedium(text: 'Automation'), children: [ - automationList.isNotEmpty - ? SceneGrid( - scenes: automationList, - loadingSceneId: state.loadingSceneId, - disablePlayButton: true, - loadingStates: state.loadingStates, - ) - : const Center( - child: BodyMedium( - text: - 'No automations have been added yet', - ), - ), + if (automationList.isNotEmpty) + SceneGrid( + scenes: automationList, + loadingSceneId: state.loadingSceneId, + disablePlayButton: true, + loadingStates: state.loadingStates, + ) + else + const Center( + child: BodyMedium( + text: + 'No automations have been added yet', + ), + ), const SizedBox(height: 10), ], ), From dcdbc02ca089cc47629532eb058c3298d6243d62 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 15:50:31 +0300 Subject: [PATCH 5/6] Adds `AppLoadingIndicator` widget and replaces `CircularProgressIndicator` in `SceneView`. --- lib/features/scene/view/scene_view.dart | 3 ++- .../shared_widgets/app_loading_indicator.dart | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 lib/features/shared_widgets/app_loading_indicator.dart diff --git a/lib/features/scene/view/scene_view.dart b/lib/features/scene/view/scene_view.dart index b9999bf..c6cadc9 100644 --- a/lib/features/scene/view/scene_view.dart +++ b/lib/features/scene/view/scene_view.dart @@ -11,6 +11,7 @@ import 'package:syncrow_app/features/scene/helper/scene_bloc_factory.dart'; import 'package:syncrow_app/features/scene/widgets/empty_devices_widget.dart'; import 'package:syncrow_app/features/scene/widgets/scene_view_widget/scene_grid_view.dart'; import 'package:syncrow_app/features/scene/widgets/scene_view_widget/scene_header.dart'; +import 'package:syncrow_app/features/shared_widgets/app_loading_indicator.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; import 'package:syncrow_app/utils/context_extension.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; @@ -63,7 +64,7 @@ class SceneView extends StatelessWidget { BlocBuilder( builder: (context, state) { if (state is SceneLoading) { - return const Center(child: CircularProgressIndicator()); + return const AppLoadingIndicator(); } if (state is SceneError) { return Center( diff --git a/lib/features/shared_widgets/app_loading_indicator.dart b/lib/features/shared_widgets/app_loading_indicator.dart new file mode 100644 index 0000000..0ca3fe3 --- /dev/null +++ b/lib/features/shared_widgets/app_loading_indicator.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class AppLoadingIndicator extends StatelessWidget { + const AppLoadingIndicator({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: CircularProgressIndicator.adaptive(), + ); + } +} From 408e78962cd2ae0a83b1e0b91398a5aead668515 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 16:18:49 +0300 Subject: [PATCH 6/6] SP-1398 --- .../view/widgets/garage_door/garage_list.dart | 46 +++++++++---------- .../view/widgets/water_heater/wh_list.dart | 39 ++++++++-------- 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/lib/features/devices/view/widgets/garage_door/garage_list.dart b/lib/features/devices/view/widgets/garage_door/garage_list.dart index 4ddef1b..8d5256d 100644 --- a/lib/features/devices/view/widgets/garage_door/garage_list.dart +++ b/lib/features/devices/view/widgets/garage_door/garage_list.dart @@ -8,8 +8,7 @@ import 'package:syncrow_app/features/shared_widgets/devices_default_switch.dart' import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; class GarageList extends StatelessWidget { - const GarageList( - {super.key, required this.garageList, required this.allSwitches}); + const GarageList({super.key, required this.garageList, required this.allSwitches}); final List garageList; final bool allSwitches; @@ -23,43 +22,42 @@ class GarageList extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 10), - const BodySmall(text: 'All Lights'), + const BodySmall(text: 'All Garages'), const SizedBox(height: 5), DevicesDefaultSwitch( - off: 'OFF', - on: 'ON', + off: 'Close', + on: 'Open', switchValue: allSwitches, - action: () { - BlocProvider.of(context) - .add(GroupAllOnEvent()); - }, - secondAction: () { - BlocProvider.of(context) - .add(GroupAllOffEvent()); - }, + action: () => BlocProvider.of(context).add( + GroupAllOnEvent(), + ), + secondAction: () => BlocProvider.of(context).add( + GroupAllOffEvent(), + ), ), ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - padding: const EdgeInsets.all(0), + padding: EdgeInsetsDirectional.zero, itemCount: garageList.length, itemBuilder: (context, index) { + final garageDoor = garageList[index]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 10), - BodySmall(text: garageList[index].deviceName), + BodySmall(text: garageDoor.deviceName), const SizedBox(height: 5), DevicesDefaultSwitch( - off: 'OFF', - on: 'ON', - switchValue: garageList[index].firstSwitch, - action: () { - BlocProvider.of(context).add( - ChangeFirstWizardSwitchStatusEvent( - value: garageList[index].firstSwitch, - deviceId: garageList[index].deviceId)); - }, + off: 'Close', + on: 'Open', + switchValue: garageDoor.firstSwitch, + action: () => BlocProvider.of(context).add( + ChangeFirstWizardSwitchStatusEvent( + value: garageDoor.firstSwitch, + deviceId: garageDoor.deviceId, + ), + ), ), ], ); diff --git a/lib/features/devices/view/widgets/water_heater/wh_list.dart b/lib/features/devices/view/widgets/water_heater/wh_list.dart index ebc2ddd..a3f672d 100644 --- a/lib/features/devices/view/widgets/water_heater/wh_list.dart +++ b/lib/features/devices/view/widgets/water_heater/wh_list.dart @@ -8,7 +8,11 @@ import 'package:syncrow_app/features/shared_widgets/devices_default_switch.dart' import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; class WHList extends StatelessWidget { - const WHList({super.key, required this.whList, required this.allSwitches}); + const WHList({ + required this.whList, + required this.allSwitches, + super.key, + }); final List whList; final bool allSwitches; @@ -22,43 +26,42 @@ class WHList extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 10), - const BodySmall(text: 'All Lights'), + const BodySmall(text: 'All Water Heaters'), const SizedBox(height: 5), DevicesDefaultSwitch( off: 'OFF', on: 'ON', switchValue: allSwitches, - action: () { - BlocProvider.of(context) - .add(GroupAllOnEvent()); - }, - secondAction: () { - BlocProvider.of(context) - .add(GroupAllOffEvent()); - }, + action: () => context.read().add( + GroupAllOnEvent(), + ), + secondAction: () => context.read().add( + GroupAllOffEvent(), + ), ), ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - padding: const EdgeInsets.all(0), + padding: EdgeInsetsDirectional.zero, itemCount: whList.length, itemBuilder: (context, index) { + final waterHeater = whList[index]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 10), - BodySmall(text: whList[index].deviceName), + BodySmall(text: waterHeater.deviceName), const SizedBox(height: 5), DevicesDefaultSwitch( off: 'OFF', on: 'ON', - switchValue: whList[index].firstSwitch, - action: () { - BlocProvider.of(context).add( + switchValue: waterHeater.firstSwitch, + action: () => context.read().add( ChangeFirstWizardSwitchStatusEvent( - value: whList[index].firstSwitch, - deviceId: whList[index].deviceId)); - }, + value: waterHeater.firstSwitch, + deviceId: waterHeater.deviceId, + ), + ), ), ], );