From a6e2681b6a48e2ba9209216a6ad8ed7ccbb8d42e Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Wed, 13 Nov 2024 12:09:31 +0300 Subject: [PATCH 01/40] adding tabs and create widget --- lib/pages/common/custom_table.dart | 2 +- .../device_managment_bloc.dart | 0 .../device_managment_event.dart | 0 .../device_managment_state.dart | 0 .../bloc/switch_tabs/switch_tabs_bloc.dart | 28 +++++ .../bloc/switch_tabs/switch_tabs_event.dart | 21 ++++ .../bloc/switch_tabs/switch_tabs_state.dart | 26 +++++ .../view/device_managment_page.dart | 102 ++++++++++++++---- .../widgets/device_managment_body.dart | 69 ++++-------- .../widgets/device_search_filters.dart | 14 +-- .../view/create_new_routine_view.dart | 10 ++ lib/pages/routiens/view/routines_view.dart | 61 +++++++++++ 12 files changed, 252 insertions(+), 81 deletions(-) rename lib/pages/device_managment/all_devices/bloc/{ => device_mgmt_bloc}/device_managment_bloc.dart (100%) rename lib/pages/device_managment/all_devices/bloc/{ => device_mgmt_bloc}/device_managment_event.dart (100%) rename lib/pages/device_managment/all_devices/bloc/{ => device_mgmt_bloc}/device_managment_state.dart (100%) create mode 100644 lib/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart create mode 100644 lib/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_event.dart create mode 100644 lib/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_state.dart create mode 100644 lib/pages/routiens/view/create_new_routine_view.dart create mode 100644 lib/pages/routiens/view/routines_view.dart diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart index 22baba36..60abc0d2 100644 --- a/lib/pages/common/custom_table.dart +++ b/lib/pages/common/custom_table.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; diff --git a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart similarity index 100% rename from lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart rename to lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart diff --git a/lib/pages/device_managment/all_devices/bloc/device_managment_event.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_event.dart similarity index 100% rename from lib/pages/device_managment/all_devices/bloc/device_managment_event.dart rename to lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_event.dart diff --git a/lib/pages/device_managment/all_devices/bloc/device_managment_state.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_state.dart similarity index 100% rename from lib/pages/device_managment/all_devices/bloc/device_managment_state.dart rename to lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_state.dart diff --git a/lib/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart b/lib/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart new file mode 100644 index 00000000..3eaccf70 --- /dev/null +++ b/lib/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart @@ -0,0 +1,28 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; + +part 'switch_tabs_event.dart'; +part 'switch_tabs_state.dart'; + +class SwitchTabsBloc extends Bloc { + SwitchTabsBloc() : super(SwitchTabsInitial()) { + on(_switchTab); + on(_newRoutineView); + } + + FutureOr _switchTab( + TriggerSwitchTabsEvent event, + Emitter emit, + ) { + emit(SelectedTabState(event.isRoutineView)); + } + + FutureOr _newRoutineView( + CreateNewRoutineViewEvent event, + Emitter emit, + ) { + emit(ShowCreateRoutineState(event.showCreateNewRoutineView)); + } +} diff --git a/lib/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_event.dart b/lib/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_event.dart new file mode 100644 index 00000000..98cad361 --- /dev/null +++ b/lib/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_event.dart @@ -0,0 +1,21 @@ +part of 'switch_tabs_bloc.dart'; + +sealed class SwitchTabsEvent extends Equatable { + const SwitchTabsEvent(); +} + +class TriggerSwitchTabsEvent extends SwitchTabsEvent { + final bool isRoutineView; + const TriggerSwitchTabsEvent(this.isRoutineView); + + @override + List get props => [isRoutineView]; +} + +class CreateNewRoutineViewEvent extends SwitchTabsEvent { + final bool showCreateNewRoutineView; + const CreateNewRoutineViewEvent(this.showCreateNewRoutineView); + + @override + List get props => [showCreateNewRoutineView]; +} diff --git a/lib/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_state.dart b/lib/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_state.dart new file mode 100644 index 00000000..dd01aeaa --- /dev/null +++ b/lib/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_state.dart @@ -0,0 +1,26 @@ +part of 'switch_tabs_bloc.dart'; + +sealed class SwitchTabsState extends Equatable { + const SwitchTabsState(); +} + +final class SwitchTabsInitial extends SwitchTabsState { + @override + List get props => []; +} + +class SelectedTabState extends SwitchTabsState { + final bool selectedTab; + const SelectedTabState(this.selectedTab); + + @override + List get props => [selectedTab]; +} + +class ShowCreateRoutineState extends SwitchTabsState { + final bool showCreateRoutine; + const ShowCreateRoutineState(this.showCreateRoutine); + + @override + List get props => [showCreateRoutine]; +} diff --git a/lib/pages/device_managment/all_devices/view/device_managment_page.dart b/lib/pages/device_managment/all_devices/view/device_managment_page.dart index 8ed8c35e..d163a718 100644 --- a/lib/pages/device_managment/all_devices/view/device_managment_page.dart +++ b/lib/pages/device_managment/all_devices/view/device_managment_page.dart @@ -1,18 +1,30 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; -import 'package:syncrow_web/web_layout/web_scaffold.dart'; +import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart'; +import 'package:syncrow_web/pages/routiens/view/routines_view.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; +import 'package:syncrow_web/web_layout/web_scaffold.dart'; class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { const DeviceManagementPage({super.key}); @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => DeviceManagementBloc()..add(FetchDevices()), + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => DeviceManagementBloc()..add(FetchDevices()), + ), + BlocProvider( + create: (context) => SwitchTabsBloc()..add(const TriggerSwitchTabsEvent(false)), + ), + ], child: WebScaffold( appBarTitle: FittedBox( child: Text( @@ -20,26 +32,74 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { style: Theme.of(context).textTheme.headlineLarge, ), ), + centerBody: BlocBuilder(builder: (context, state) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + TextButton( + style: TextButton.styleFrom( + backgroundColor: null, + ), + onPressed: () { + context.read().add(const TriggerSwitchTabsEvent(false)); + }, + child: Text( + 'Devices', + style: context.textTheme.titleMedium?.copyWith( + color: state is SelectedTabState && state.selectedTab == false + ? ColorsManager.whiteColors + : ColorsManager.grayColor, + fontWeight: + (state is SelectedTabState) && state.selectedTab == false ? FontWeight.w700 : FontWeight.w400, + ), + ), + ), + TextButton( + style: TextButton.styleFrom( + backgroundColor: null, + ), + onPressed: () { + context.read().add(const TriggerSwitchTabsEvent(true)); + }, + child: Text( + 'Routines', + style: context.textTheme.titleMedium?.copyWith( + color: (state is SelectedTabState) && state.selectedTab == true + ? ColorsManager.whiteColors + : ColorsManager.grayColor, + fontWeight: + (state is SelectedTabState) && state.selectedTab == true ? FontWeight.w700 : FontWeight.w400, + ), + ), + ), + ], + ); + }), rightBody: const NavigateHomeGridView(), - scaffoldBody: BlocBuilder( - builder: (context, state) { - if (state is DeviceManagementLoading) { - return const Center(child: CircularProgressIndicator()); - } else if (state is DeviceManagementLoaded || state is DeviceManagementFiltered) { - final devices = state is DeviceManagementLoaded - ? state.devices - : (state as DeviceManagementFiltered).filteredDevices; + scaffoldBody: BlocBuilder(builder: (context, state) { + if (state is SelectedTabState && state.selectedTab) { + return const RoutinesView(); + } + if (state is ShowCreateRoutineState && state.showCreateRoutine) { + return const CreateNewRoutineView(); + } - return DeviceManagementBody(devices: devices); - } else { - return const Center(child: Text('Error fetching Devices')); - } - }, - ), + return BlocBuilder( + builder: (context, deviceState) { + if (deviceState is DeviceManagementLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (deviceState is DeviceManagementLoaded || deviceState is DeviceManagementFiltered) { + final devices = + (deviceState as dynamic).devices ?? (deviceState as DeviceManagementFiltered).filteredDevices; + + return DeviceManagementBody(devices: devices); + } else { + return const Center(child: Text('Error fetching Devices')); + } + }, + ); + }), ), ); } } - - - diff --git a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart index 12c66403..0788e08d 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart @@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/custom_table.dart'; import 'package:syncrow_web/pages/common/filter/filter_widget.dart'; -import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart'; @@ -57,15 +57,12 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { 'Low Battery ($lowBatteryCount)', ]; - final buttonLabel = - (selectedDevices.length > 1) ? 'Batch Control' : 'Control'; + final buttonLabel = (selectedDevices.length > 1) ? 'Batch Control' : 'Control'; return Column( children: [ Container( - padding: isLargeScreenSize(context) - ? const EdgeInsets.all(30) - : const EdgeInsets.all(15), + padding: isLargeScreenSize(context) ? const EdgeInsets.all(30) : const EdgeInsets.all(15), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -74,9 +71,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { tabs: tabs, selectedIndex: selectedIndex, onTabChanged: (index) { - context - .read() - .add(SelectedFilterChanged(index)); + context.read().add(SelectedFilterChanged(index)); }, ), const SizedBox(height: 20), @@ -98,14 +93,11 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { ), ); } else if (selectedDevices.length > 1) { - final productTypes = selectedDevices - .map((device) => device.productType) - .toSet(); + final productTypes = selectedDevices.map((device) => device.productType).toSet(); if (productTypes.length == 1) { showDialog( context: context, - builder: (context) => - DeviceBatchControlDialog( + builder: (context) => DeviceBatchControlDialog( devices: selectedDevices, ), ); @@ -119,9 +111,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { textAlign: TextAlign.center, style: TextStyle( fontSize: 12, - color: isControlButtonEnabled - ? Colors.white - : Colors.grey, + color: isControlButtonEnabled ? Colors.white : Colors.grey, ), ), ), @@ -132,17 +122,13 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { ), Expanded( child: Padding( - padding: isLargeScreenSize(context) - ? const EdgeInsets.all(30) - : const EdgeInsets.all(15), + padding: isLargeScreenSize(context) ? const EdgeInsets.all(30) : const EdgeInsets.all(15), child: DynamicTable( withSelectAll: true, cellDecoration: containerDecoration, onRowSelected: (index, isSelected, row) { final selectedDevice = devicesToShow[index]; - context - .read() - .add(SelectDevice(selectedDevice)); + context.read().add(SelectDevice(selectedDevice)); }, withCheckBox: true, size: MediaQuery.of(context).size, @@ -160,44 +146,27 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { ], data: devicesToShow.map((device) { final combinedSpaceNames = device.spaces != null - ? device.spaces! - .map((space) => space.spaceName) - .join(' > ') + - (device.community != null - ? ' > ${device.community!.name}' - : '') - : (device.community != null - ? device.community!.name - : ''); + ? device.spaces!.map((space) => space.spaceName).join(' > ') + + (device.community != null ? ' > ${device.community!.name}' : '') + : (device.community != null ? device.community!.name : ''); return [ device.name ?? '', device.productName ?? '', device.uuid ?? '', - (device.spaces != null && device.spaces!.isNotEmpty) - ? device.spaces![0].spaceName - : '', + (device.spaces != null && device.spaces!.isNotEmpty) ? device.spaces![0].spaceName : '', combinedSpaceNames, - device.batteryLevel != null - ? '${device.batteryLevel}%' - : '-', - formatDateTime(DateTime.fromMillisecondsSinceEpoch( - (device.createTime ?? 0) * 1000)), + device.batteryLevel != null ? '${device.batteryLevel}%' : '-', + formatDateTime(DateTime.fromMillisecondsSinceEpoch((device.createTime ?? 0) * 1000)), device.online == true ? 'Online' : 'Offline', - formatDateTime(DateTime.fromMillisecondsSinceEpoch( - (device.updateTime ?? 0) * 1000)), + formatDateTime(DateTime.fromMillisecondsSinceEpoch((device.updateTime ?? 0) * 1000)), ]; }).toList(), onSelectionChanged: (selectedRows) { - context - .read() - .add(UpdateSelection(selectedRows)); + context.read().add(UpdateSelection(selectedRows)); }, - initialSelectedIds: context - .read() - .selectedDevices - .map((device) => device.uuid!) - .toList(), + initialSelectedIds: + context.read().selectedDevices.map((device) => device.uuid!).toList(), isEmpty: devicesToShow.isEmpty, ), ), diff --git a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart index 71974156..d9e47aa6 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart'; -import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart'; +import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -import 'package:syncrow_web/utils/style.dart'; class DeviceSearchFilters extends StatefulWidget { const DeviceSearchFilters({super.key}); @@ -13,8 +12,7 @@ class DeviceSearchFilters extends StatefulWidget { State createState() => _DeviceSearchFiltersState(); } -class _DeviceSearchFiltersState extends State - with HelperResponsiveLayout { +class _DeviceSearchFiltersState extends State with HelperResponsiveLayout { final TextEditingController communityController = TextEditingController(); final TextEditingController unitNameController = TextEditingController(); final TextEditingController productNameController = TextEditingController(); @@ -36,8 +34,7 @@ class _DeviceSearchFiltersState extends State const SizedBox(width: 20), _buildSearchField("Space Name", unitNameController, 200), const SizedBox(width: 20), - _buildSearchField( - "Device Name / Product Name", productNameController, 300), + _buildSearchField("Device Name / Product Name", productNameController, 300), const SizedBox(width: 20), _buildSearchResetButtons(), ], @@ -62,8 +59,7 @@ class _DeviceSearchFiltersState extends State ); } - Widget _buildSearchField( - String title, TextEditingController controller, double width) { + Widget _buildSearchField(String title, TextEditingController controller, double width) { return Container( child: StatefulTextField( title: title, diff --git a/lib/pages/routiens/view/create_new_routine_view.dart b/lib/pages/routiens/view/create_new_routine_view.dart new file mode 100644 index 00000000..dc770946 --- /dev/null +++ b/lib/pages/routiens/view/create_new_routine_view.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class CreateNewRoutineView extends StatelessWidget { + const CreateNewRoutineView({super.key}); + + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/lib/pages/routiens/view/routines_view.dart b/lib/pages/routiens/view/routines_view.dart new file mode 100644 index 00000000..5a19db08 --- /dev/null +++ b/lib/pages/routiens/view/routines_view.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class RoutinesView extends StatelessWidget { + const RoutinesView({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text("Create New Routines", + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + color: ColorsManager.grayColor, + )), + SizedBox( + height: 200, + width: 150, + child: GestureDetector( + onTap: () { + BlocProvider.of(context).add( + const CreateNewRoutineViewEvent(true), + ); + }, + child: Card( + elevation: 3, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + color: ColorsManager.whiteColors, + child: Center( + child: Container( + decoration: BoxDecoration( + color: ColorsManager.graysColor, + borderRadius: BorderRadius.circular(120), + border: Border.all(color: ColorsManager.greyColor, width: 2.0), + ), + height: 70, + width: 70, + child: Icon( + Icons.add, + color: ColorsManager.dialogBlueTitle, + size: 40, + ), + )), + ), + ), + ), + const Spacer(), + ], + ), + ); + } +} From 16dd95c8d1654300fadcb799b511c61008abcb16 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sat, 16 Nov 2024 23:58:33 +0300 Subject: [PATCH 02/40] added devices --- assets/icons/routine/delay.svg | 24 ++ assets/icons/routine/map.svg | 17 ++ assets/icons/routine/notification.svg | 10 + assets/icons/routine/schedule.svg | 17 ++ assets/icons/routine/tab_to_run.svg | 9 + assets/icons/routine/weather.svg | 242 ++++++++++++++++++ .../common/text_field/custom_text_field.dart | 109 +++++--- .../all_devices/models/devices_model.dart | 136 +++++++--- .../view/device_managment_page.dart | 49 ++-- lib/pages/routiens/bloc/routine_bloc.dart | 13 + lib/pages/routiens/bloc/routine_event.dart | 4 + lib/pages/routiens/bloc/routine_state.dart | 6 + .../view/create_new_routine_view.dart | 74 +++++- .../conditions_routines_devices_view.dart | 106 ++++++++ lib/pages/routiens/widgets/dragable_card.dart | 63 +++++ .../widgets/routine_search_and_buttons.dart | 193 ++++++++++++++ .../widgets/routines_title_widget.dart | 38 +++ .../widgets/search_bar_condition_title.dart | 62 +++++ lib/utils/color_manager.dart | 2 +- lib/utils/constants/assets.dart | 18 ++ lib/utils/enum/device_types.dart | 6 +- lib/utils/style.dart | 32 ++- pubspec.yaml | 1 + 23 files changed, 1117 insertions(+), 114 deletions(-) create mode 100644 assets/icons/routine/delay.svg create mode 100644 assets/icons/routine/map.svg create mode 100644 assets/icons/routine/notification.svg create mode 100644 assets/icons/routine/schedule.svg create mode 100644 assets/icons/routine/tab_to_run.svg create mode 100644 assets/icons/routine/weather.svg create mode 100644 lib/pages/routiens/bloc/routine_bloc.dart create mode 100644 lib/pages/routiens/bloc/routine_event.dart create mode 100644 lib/pages/routiens/bloc/routine_state.dart create mode 100644 lib/pages/routiens/widgets/conditions_routines_devices_view.dart create mode 100644 lib/pages/routiens/widgets/dragable_card.dart create mode 100644 lib/pages/routiens/widgets/routine_search_and_buttons.dart create mode 100644 lib/pages/routiens/widgets/routines_title_widget.dart create mode 100644 lib/pages/routiens/widgets/search_bar_condition_title.dart diff --git a/assets/icons/routine/delay.svg b/assets/icons/routine/delay.svg new file mode 100644 index 00000000..49a8d31c --- /dev/null +++ b/assets/icons/routine/delay.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/routine/map.svg b/assets/icons/routine/map.svg new file mode 100644 index 00000000..595a0d8c --- /dev/null +++ b/assets/icons/routine/map.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/routine/notification.svg b/assets/icons/routine/notification.svg new file mode 100644 index 00000000..f196f466 --- /dev/null +++ b/assets/icons/routine/notification.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/routine/schedule.svg b/assets/icons/routine/schedule.svg new file mode 100644 index 00000000..423eb577 --- /dev/null +++ b/assets/icons/routine/schedule.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/routine/tab_to_run.svg b/assets/icons/routine/tab_to_run.svg new file mode 100644 index 00000000..c8660bb8 --- /dev/null +++ b/assets/icons/routine/tab_to_run.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/routine/weather.svg b/assets/icons/routine/weather.svg new file mode 100644 index 00000000..49ed9408 --- /dev/null +++ b/assets/icons/routine/weather.svg @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/pages/common/text_field/custom_text_field.dart b/lib/pages/common/text_field/custom_text_field.dart index b695da4a..23f033b6 100644 --- a/lib/pages/common/text_field/custom_text_field.dart +++ b/lib/pages/common/text_field/custom_text_field.dart @@ -1,59 +1,97 @@ import 'package:flutter/material.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 StatefulTextField extends StatefulWidget { - const StatefulTextField( - {super.key, - required this.title, - this.hintText = 'Please enter', - required this.width, - this.elevation = 0, - required this.controller, - this.onSubmitted}); + const StatefulTextField({ + super.key, + required this.title, + this.hintText = 'Please enter', + required this.width, + this.elevation, + required this.controller, + this.onSubmitted, + this.boxDecoration, + this.borderRadius, + this.height, + this.padding, + this.icon, + this.hintColor, + }); final String title; final String hintText; final double width; - final double elevation; + final double? elevation; final TextEditingController controller; final Function? onSubmitted; + final BoxDecoration? boxDecoration; + final double? borderRadius; + final double? height; + final double? padding; + final IconData? icon; + final Color? hintColor; @override State createState() => _StatefulTextFieldState(); } class _StatefulTextFieldState extends State { + @override + void dispose() { + widget.controller.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - return Container( - child: CustomTextField( - title: widget.title, - controller: widget.controller, - hintText: widget.hintText, - width: widget.width, - elevation: widget.elevation, - onSubmittedFun: widget.onSubmitted), + return CustomTextField( + title: widget.title, + controller: widget.controller, + hintText: widget.hintText, + width: widget.width, + elevation: widget.elevation, + onSubmittedFun: widget.onSubmitted, + boxDecoration: widget.boxDecoration, + borderRadius: widget.borderRadius, + height: widget.height, + padding: widget.padding, + icon: widget.icon, + hintColor: widget.hintColor, ); } } class CustomTextField extends StatelessWidget { - const CustomTextField( - {super.key, - required this.title, - required this.controller, - this.hintText = 'Please enter', - required this.width, - this.elevation = 0, - this.onSubmittedFun}); + const CustomTextField({ + super.key, + required this.title, + required this.controller, + this.hintText = 'Please enter', + required this.width, + this.elevation, + this.onSubmittedFun, + this.boxDecoration, + this.borderRadius, + this.height, + this.padding, + this.icon, + this.hintColor, + }); final String title; final TextEditingController controller; final String hintText; final double width; - final double elevation; + final double? elevation; final Function? onSubmittedFun; + final BoxDecoration? boxDecoration; + final double? borderRadius; + final double? height; + final double? padding; + final IconData? icon; + final Color? hintColor; @override Widget build(BuildContext context) { @@ -71,26 +109,21 @@ class CustomTextField extends StatelessWidget { ), const SizedBox(height: 8), Material( - elevation: elevation, - borderRadius: BorderRadius.circular(8), + elevation: elevation ?? 0, + borderRadius: BorderRadius.circular(borderRadius ?? 8), child: Container( width: width, - height: 45, - decoration: containerDecoration, - - // decoration: BoxDecoration( - // color: Colors.white, - // borderRadius: BorderRadius.circular(8), - // ), + height: height ?? 45, + decoration: boxDecoration ?? containerDecoration, child: TextFormField( controller: controller, style: const TextStyle(color: Colors.black), decoration: InputDecoration( hintText: hintText, - hintStyle: const TextStyle(fontSize: 12), - contentPadding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + hintStyle: TextStyle(fontSize: 12, color: hintColor ?? ColorsManager.blackColor), + contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: padding ?? 10), border: InputBorder.none, + suffixIcon: icon != null ? Icon(icon, color: ColorsManager.greyColor) : null, ), onFieldSubmitted: (_) { onSubmittedFun!(); diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index df80c3e7..50e588b0 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -3,6 +3,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_spa import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/enum/device_types.dart'; class AllDevicesModel { /* @@ -100,12 +101,8 @@ class AllDevicesModel { this.spaces, }); AllDevicesModel.fromJson(Map json) { - room = (json['room'] != null && (json['room'] is Map)) - ? DevicesModelRoom.fromJson(json['room']) - : null; - unit = (json['unit'] != null && (json['unit'] is Map)) - ? DevicesModelUnit.fromJson(json['unit']) - : null; + room = (json['room'] != null && (json['room'] is Map)) ? DevicesModelRoom.fromJson(json['room']) : null; + unit = (json['unit'] != null && (json['unit'] is Map)) ? DevicesModelUnit.fromJson(json['unit']) : null; community = (json['community'] != null && (json['community'] is Map)) ? DeviceCommunityModel.fromJson(json['community']) : null; @@ -117,7 +114,7 @@ class AllDevicesModel { categoryName = json['categoryName']?.toString(); createTime = int.tryParse(json['createTime']?.toString() ?? ''); gatewayId = json['gatewayId']?.toString(); - icon = json['icon'] ?? _getDefaultIcon(productType); + icon = json['icon'] ?? getDefaultIcon(productType); ip = json['ip']?.toString(); lat = json['lat']?.toString(); localKey = json['localKey']?.toString(); @@ -134,38 +131,88 @@ class AllDevicesModel { batteryLevel = int.tryParse(json['battery']?.toString() ?? ''); productName = json['productName']?.toString(); if (json['spaces'] != null && json['spaces'] is List) { - spaces = (json['spaces'] as List) - .map((space) => DeviceSpaceModel.fromJson(space)) - .toList(); + spaces = (json['spaces'] as List).map((space) => DeviceSpaceModel.fromJson(space)).toList(); } } - String _getDefaultIcon(String? productType) { - switch (productType) { - case 'LightBulb': - return Assets.lightBulb; - case 'CeilingSensor': - case 'WallSensor': - return Assets.sensors; - case 'AC': - return Assets.ac; - case 'DoorLock': - return Assets.doorLock; - case 'Curtain': - return Assets.curtain; - case '3G': - case '2G': - case '1G': - return Assets.gangSwitch; - case 'Gateway': - return Assets.gateway; - case 'WH': - return Assets.blackLogo; - case 'DS': - return Assets.sensors; - default: - return Assets.logo; + String getDefaultIcon(String? productType) { + /* + AC +GD +3G +3G +GW +DL +WPS +CPS +AC +CPS +WPS +GW +AC +CUR +DS +1GT +2GT +3GT +1G +1G +2G +2G +DS +WH +1GT +2GT +3GT +GD +WL +WL +3G +CUR +GW +PC +PC +SOS + + */ + DeviceType type = devicesTypesMap[productType] ?? DeviceType.Other; + String tempIcon = ''; + if (type == DeviceType.LightBulb) { + tempIcon = Assets.lightBulb; + } else if (type == DeviceType.CeilingSensor || type == DeviceType.WallSensor) { + tempIcon = Assets.sensors; + } else if (type == DeviceType.AC) { + tempIcon = Assets.ac; + } else if (type == DeviceType.DoorLock) { + tempIcon = Assets.doorLock; + } else if (type == DeviceType.Curtain) { + tempIcon = Assets.curtain; + } else if (type == DeviceType.ThreeGang) { + tempIcon = Assets.gangSwitch; + } else if (type == DeviceType.Gateway) { + tempIcon = Assets.gateway; + } else if (type == DeviceType.OneGang) { + tempIcon = Assets.oneGang; + } else if (type == DeviceType.TwoGang) { + tempIcon = Assets.twoGang; + } else if (type == DeviceType.WH) { + tempIcon = Assets.waterHeater; + } else if (type == DeviceType.DS) { + // tempIcon = Assets.mainDoor; + } else if (type == DeviceType.OneTouch) { + // tempIcon = Assets.oneGang; + } else if (type == DeviceType.TowTouch) { + // tempIcon = Assets.twoGang; + } else if (type == DeviceType.GarageDoor) { + //tempIcon = Assets.; + } else if (type == DeviceType.ThreeTouch) { + // tempIcon = Assets.gang3touch; + } else if (type == DeviceType.WaterLeak) { + tempIcon = Assets.waterLeakNormal; + } else { + tempIcon = Assets.logoHorizontal; } + return tempIcon; } Map toJson() { @@ -271,4 +318,23 @@ class AllDevicesModel { productName.hashCode ^ batteryLevel.hashCode; } + + Map devicesTypesMap = { + "AC": DeviceType.AC, + "GW": DeviceType.Gateway, + "CPS": DeviceType.CeilingSensor, + "DL": DeviceType.DoorLock, + "WPS": DeviceType.WallSensor, + "3G": DeviceType.ThreeGang, + "2G": DeviceType.TwoGang, + "1G": DeviceType.OneGang, + "CUR": DeviceType.Curtain, + "WH": DeviceType.WH, + "DS": DeviceType.DS, + "1GT": DeviceType.OneTouch, + "2GT": DeviceType.TowTouch, + "3GT": DeviceType.ThreeTouch, + "GD": DeviceType.GarageDoor, + "WL": DeviceType.WaterLeak, + }; } diff --git a/lib/pages/device_managment/all_devices/view/device_managment_page.dart b/lib/pages/device_managment/all_devices/view/device_managment_page.dart index d163a718..e0661b50 100644 --- a/lib/pages/device_managment/all_devices/view/device_managment_page.dart +++ b/lib/pages/device_managment/all_devices/view/device_managment_page.dart @@ -2,10 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart'; -import 'package:syncrow_web/pages/routiens/view/routines_view.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; @@ -76,29 +74,30 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { ); }), rightBody: const NavigateHomeGridView(), - scaffoldBody: BlocBuilder(builder: (context, state) { - if (state is SelectedTabState && state.selectedTab) { - return const RoutinesView(); - } - if (state is ShowCreateRoutineState && state.showCreateRoutine) { - return const CreateNewRoutineView(); - } - - return BlocBuilder( - builder: (context, deviceState) { - if (deviceState is DeviceManagementLoading) { - return const Center(child: CircularProgressIndicator()); - } else if (deviceState is DeviceManagementLoaded || deviceState is DeviceManagementFiltered) { - final devices = - (deviceState as dynamic).devices ?? (deviceState as DeviceManagementFiltered).filteredDevices; - - return DeviceManagementBody(devices: devices); - } else { - return const Center(child: Text('Error fetching Devices')); - } - }, - ); - }), + scaffoldBody: CreateNewRoutineView(), + // BlocBuilder(builder: (context, state) { + // if (state is SelectedTabState && state.selectedTab) { + // return const RoutinesView(); + // } + // if (state is ShowCreateRoutineState && state.showCreateRoutine) { + // return const CreateNewRoutineView(); + // } + // + // return BlocBuilder( + // builder: (context, deviceState) { + // if (deviceState is DeviceManagementLoading) { + // return const Center(child: CircularProgressIndicator()); + // } else if (deviceState is DeviceManagementLoaded || deviceState is DeviceManagementFiltered) { + // final devices = + // (deviceState as dynamic).devices ?? (deviceState as DeviceManagementFiltered).filteredDevices; + // + // return DeviceManagementBody(devices: devices); + // } else { + // return const Center(child: Text('Error fetching Devices')); + // } + // }, + // ); + // }), ), ); } diff --git a/lib/pages/routiens/bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc.dart new file mode 100644 index 00000000..c0b2b5c5 --- /dev/null +++ b/lib/pages/routiens/bloc/routine_bloc.dart @@ -0,0 +1,13 @@ +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; + +part 'routine_event.dart'; +part 'routine_state.dart'; + +class RoutineBloc extends Bloc { + RoutineBloc() : super(RoutineInitial()) { + on((event, emit) { + // TODO: implement event handler + }); + } +} diff --git a/lib/pages/routiens/bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_event.dart new file mode 100644 index 00000000..52ca0690 --- /dev/null +++ b/lib/pages/routiens/bloc/routine_event.dart @@ -0,0 +1,4 @@ +part of 'routine_bloc.dart'; + +@immutable +sealed class RoutineEvent {} diff --git a/lib/pages/routiens/bloc/routine_state.dart b/lib/pages/routiens/bloc/routine_state.dart new file mode 100644 index 00000000..aaf5cb97 --- /dev/null +++ b/lib/pages/routiens/bloc/routine_state.dart @@ -0,0 +1,6 @@ +part of 'routine_bloc.dart'; + +@immutable +sealed class RoutineState {} + +final class RoutineInitial extends RoutineState {} diff --git a/lib/pages/routiens/view/create_new_routine_view.dart b/lib/pages/routiens/view/create_new_routine_view.dart index dc770946..764eb374 100644 --- a/lib/pages/routiens/view/create_new_routine_view.dart +++ b/lib/pages/routiens/view/create_new_routine_view.dart @@ -1,10 +1,82 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/routiens/widgets/conditions_routines_devices_view.dart'; +import 'package:syncrow_web/pages/routiens/widgets/routine_search_and_buttons.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; class CreateNewRoutineView extends StatelessWidget { const CreateNewRoutineView({super.key}); @override Widget build(BuildContext context) { - return const Placeholder(); + return Container( + alignment: Alignment.topLeft, + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const RoutineSearchAndButtons(), + const SizedBox(height: 20), + Flexible( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Card( + child: Container( + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(15), + ), + child: const ConditionsRoutinesDevicesView()), + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: Column( + children: [ + Expanded( + child: Card( + margin: EdgeInsets.zero, + child: Container( + decoration: const BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(15), + topRight: Radius.circular(15), + ), + ), + ), + ), + ), + Container( + height: 2, + width: double.infinity, + color: ColorsManager.dialogBlueTitle, + ), + Expanded( + child: Card( + margin: EdgeInsets.zero, + child: Container( + decoration: const BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(15), + bottomRight: Radius.circular(15), + ), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ); } } diff --git a/lib/pages/routiens/widgets/conditions_routines_devices_view.dart b/lib/pages/routiens/widgets/conditions_routines_devices_view.dart new file mode 100644 index 00000000..6e6ef4a2 --- /dev/null +++ b/lib/pages/routiens/widgets/conditions_routines_devices_view.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; +import 'package:syncrow_web/pages/routiens/widgets/routines_title_widget.dart'; +import 'package:syncrow_web/pages/routiens/widgets/search_bar_condition_title.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class ConditionsRoutinesDevicesView extends StatelessWidget { + const ConditionsRoutinesDevicesView({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 8.0), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const ConditionTitleAndSearchBar(), + const SizedBox( + height: 10, + ), + const Wrap( + spacing: 10, + runSpacing: 10, + children: [ + DraggableCard( + imagePath: Assets.tabToRun, + title: 'Tab to run', + ), + DraggableCard( + imagePath: Assets.map, + title: 'Location', + ), + DraggableCard( + imagePath: Assets.weather, + title: 'Weather', + ), + DraggableCard( + imagePath: Assets.schedule, + title: 'Schedule', + ), + ], + ), + const SizedBox( + height: 10, + ), + const TitleRoutine( + title: 'Conditions', + subtitle: '(THEN)', + ), + const Wrap( + spacing: 10, + runSpacing: 10, + children: [ + DraggableCard( + imagePath: Assets.notification, + title: 'Send Notification', + ), + DraggableCard( + imagePath: Assets.delay, + title: 'Delay the action', + ), + ], + ), + const SizedBox( + height: 10, + ), + const TitleRoutine( + title: 'Routines', + subtitle: '(THEN)', + ), + const TitleRoutine( + title: 'Devices', + subtitle: '', + ), + BlocProvider( + create: (context) => DeviceManagementBloc() + ..add( + FetchDevices(), + ), + child: BlocBuilder( + builder: (context, state) { + if (state is DeviceManagementLoaded) { + return Wrap( + spacing: 10, + runSpacing: 10, + children: state.devices + .map((device) => DraggableCard( + imagePath: device.getDefaultIcon(device.productType), + title: device.name ?? '', + )) + .toList(), + ); + } + return const Center(child: CircularProgressIndicator()); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart new file mode 100644 index 00000000..490dd570 --- /dev/null +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class DraggableCard extends StatelessWidget { + const DraggableCard({ + super.key, + required this.imagePath, + required this.title, + this.titleColor, + }); + + final String imagePath; + final String title; + final Color? titleColor; + + @override + Widget build(BuildContext context) { + return Card( + color: ColorsManager.whiteColors, + child: SizedBox( + height: 123, + width: 90, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + height: 50, + width: 50, + decoration: BoxDecoration( + color: ColorsManager.CircleImageBackground, + borderRadius: BorderRadius.circular(90), + border: Border.all( + color: ColorsManager.graysColor, + ), + ), + padding: const EdgeInsets.all(8), + child: imagePath.contains('.svg') + ? SvgPicture.asset( + imagePath, + ) + : Image.network(imagePath), + ), + const SizedBox( + height: 8, + ), + Text( + title, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: context.textTheme.bodySmall?.copyWith( + color: titleColor ?? ColorsManager.blackColor, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/routiens/widgets/routine_search_and_buttons.dart b/lib/pages/routiens/widgets/routine_search_and_buttons.dart new file mode 100644 index 00000000..1acce727 --- /dev/null +++ b/lib/pages/routiens/widgets/routine_search_and_buttons.dart @@ -0,0 +1,193 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class RoutineSearchAndButtons extends StatelessWidget { + const RoutineSearchAndButtons({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Wrap( + runSpacing: 16, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: Wrap( + spacing: 12, + runSpacing: 12, + crossAxisAlignment: WrapCrossAlignment.end, + children: [ + ConstrainedBox( + constraints: + BoxConstraints(maxWidth: constraints.maxWidth > 700 ? 450 : constraints.maxWidth - 32), + child: StatefulTextField( + title: 'Routine Name', + height: 40, + controller: TextEditingController(), + hintText: 'Please enter the name', + boxDecoration: containerWhiteDecoration, + elevation: 0, + borderRadius: 15, + width: 450, + ), + ), + (constraints.maxWidth <= 1000) + ? const SizedBox() + : SizedBox( + height: 40, + width: 200, + child: Center( + child: DefaultButton( + onPressed: () {}, + borderRadius: 15, + elevation: 0, + borderColor: ColorsManager.greyColor, + backgroundColor: ColorsManager.boxColor, + child: const Text( + 'Settings', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: ColorsManager.primaryColor, + ), + ), + ), + ), + ), + ], + ), + ), + if (constraints.maxWidth > 1000) + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 40, + width: 200, + child: Center( + child: DefaultButton( + onPressed: () {}, + borderRadius: 15, + elevation: 0, + borderColor: ColorsManager.greyColor, + backgroundColor: ColorsManager.boxColor, + child: const Text( + 'Cancel', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: ColorsManager.blackColor, + ), + ), + ), + ), + ), + const SizedBox(width: 12), + SizedBox( + height: 40, + width: 200, + child: Center( + child: DefaultButton( + onPressed: () {}, + borderRadius: 15, + elevation: 0, + backgroundColor: ColorsManager.primaryColor, + child: const Text( + 'Save', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: ColorsManager.whiteColors, + ), + ), + ), + ), + ), + ], + ), + ], + ), + if (constraints.maxWidth <= 1000) + Wrap( + runSpacing: 12, + children: [ + SizedBox( + height: 40, + width: 200, + child: Center( + child: DefaultButton( + onPressed: () {}, + borderRadius: 15, + elevation: 0, + borderColor: ColorsManager.greyColor, + backgroundColor: ColorsManager.boxColor, + child: const Text( + 'Settings', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: ColorsManager.primaryColor, + ), + ), + ), + ), + ), + const SizedBox(width: 12), + SizedBox( + height: 40, + width: 200, + child: Center( + child: DefaultButton( + onPressed: () {}, + borderRadius: 15, + elevation: 0, + borderColor: ColorsManager.greyColor, + backgroundColor: ColorsManager.boxColor, + child: const Text( + 'Cancel', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: ColorsManager.blackColor, + ), + ), + ), + ), + ), + const SizedBox(width: 12), + SizedBox( + height: 40, + width: 200, + child: Center( + child: DefaultButton( + onPressed: () {}, + borderRadius: 15, + elevation: 0, + backgroundColor: ColorsManager.primaryColor, + child: const Text( + 'Save', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: ColorsManager.whiteColors, + ), + ), + ), + ), + ), + ], + ), + ], + ); + }, + ); + } +} diff --git a/lib/pages/routiens/widgets/routines_title_widget.dart b/lib/pages/routiens/widgets/routines_title_widget.dart new file mode 100644 index 00000000..7d2a272f --- /dev/null +++ b/lib/pages/routiens/widgets/routines_title_widget.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class TitleRoutine extends StatelessWidget { + const TitleRoutine({ + super.key, + required this.title, + required this.subtitle, + }); + + final String title; + final String subtitle; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + title, + style: context.textTheme.titleLarge?.copyWith( + color: ColorsManager.greyColor, + ), + ), + const SizedBox( + width: 4, + ), + Text( + subtitle, + style: context.textTheme.titleLarge?.copyWith( + color: ColorsManager.greyColor, + ), + ), + ], + ); + } +} diff --git a/lib/pages/routiens/widgets/search_bar_condition_title.dart b/lib/pages/routiens/widgets/search_bar_condition_title.dart new file mode 100644 index 00000000..40d6b575 --- /dev/null +++ b/lib/pages/routiens/widgets/search_bar_condition_title.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart'; +import 'package:syncrow_web/pages/routiens/widgets/routines_title_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class ConditionTitleAndSearchBar extends StatelessWidget with HelperResponsiveLayout { + const ConditionTitleAndSearchBar({ + super.key, + }); + + @override + Widget build(BuildContext context) { + final isMedium = isMediumScreenSize(context); + final isSmall = isSmallScreenSize(context); + return isMedium || isSmall + ? Wrap( + spacing: 10, + runSpacing: 10, + children: [ + const TitleRoutine(title: 'Conditions', subtitle: '(IF)'), + StatefulTextField( + title: '', + width: 250, + height: 40, + hintText: 'Search', + elevation: 0, + borderRadius: 15, + icon: Icons.search, + hintColor: ColorsManager.grayColor, + boxDecoration: BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.circular(15), + ), + controller: TextEditingController(), + ), + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + const TitleRoutine(title: 'Conditions', subtitle: '(IF)'), + StatefulTextField( + title: '', + width: 250, + height: 40, + hintText: 'Search', + elevation: 0, + borderRadius: 15, + icon: Icons.search, + hintColor: ColorsManager.grayColor, + boxDecoration: BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.circular(15), + ), + controller: TextEditingController(), + ), + ], + ); + } +} diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 481fb2fa..140ed370 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -41,6 +41,6 @@ abstract class ColorsManager { static const Color blue4 = Color(0xFF001E7E); static const Color textGreen = Color(0xFF008905); static const Color yaGreen = Color(0xFFFFBF44); + static const Color CircleImageBackground = Color(0xFFF4F4F4); } //background: #background: #5D5D5D; - diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index dad9b261..2c0c526f 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -192,4 +192,22 @@ class Assets { //assets/icons/sos_normal.svg static const String sosNormal = 'assets/icons/sos_normal.svg'; + + //assets/icons/routine/tab_to_run.svg + static const String tabToRun = 'assets/icons/routine/tab_to_run.svg'; + + //assets/icons/routine/schedule.svg + static const String schedule = 'assets/icons/routine/schedule.svg'; + + //assets/icons/routine/map.svg + static const String map = 'assets/icons/routine/map.svg'; + + //assets/icons/routine/weather.svg + static const String weather = 'assets/icons/routine/weather.svg'; + + //assets/icons/routine/notification.svg + static const String notification = 'assets/icons/routine/notification.svg'; + + //assets/icons/routine/delay.svg + static const String delay = 'assets/icons/routine/delay.svg'; } diff --git a/lib/utils/enum/device_types.dart b/lib/utils/enum/device_types.dart index 2b1ce8a5..7050f13a 100644 --- a/lib/utils/enum/device_types.dart +++ b/lib/utils/enum/device_types.dart @@ -11,9 +11,13 @@ enum DeviceType { CeilingSensor, WallSensor, WH, - DoorSensor, + DS, + OneTouch, + TowTouch, + ThreeTouch, GarageDoor, WaterLeak, + DoorSensor, Other, } /* diff --git a/lib/utils/style.dart b/lib/utils/style.dart index 145cf8a1..04108333 100644 --- a/lib/utils/style.dart +++ b/lib/utils/style.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; + import 'color_manager.dart'; -InputDecoration? textBoxDecoration({bool suffixIcon = false}) => - InputDecoration( +InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration( focusColor: ColorsManager.grayColor, suffixIcon: suffixIcon ? const Icon(Icons.search) : null, hintText: 'Search', @@ -30,14 +30,20 @@ InputDecoration? textBoxDecoration({bool suffixIcon = false}) => ), ); -BoxDecoration containerDecoration = BoxDecoration( - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.3), - spreadRadius: 2, - blurRadius: 4, - offset: const Offset(0, 5), // changes position of shadow - ), - ], - color: ColorsManager.boxColor, - borderRadius: const BorderRadius.all(Radius.circular(10))); +BoxDecoration containerDecoration = BoxDecoration(boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.3), + spreadRadius: 2, + blurRadius: 4, + offset: const Offset(0, 5), // changes position of shadow + ), +], color: ColorsManager.boxColor, borderRadius: const BorderRadius.all(Radius.circular(10))); + +BoxDecoration containerWhiteDecoration = BoxDecoration(boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.3), + spreadRadius: 2, + blurRadius: 4, + offset: const Offset(0, 5), // changes position of shadow + ), +], color: ColorsManager.whiteColors, borderRadius: const BorderRadius.all(Radius.circular(15))); diff --git a/pubspec.yaml b/pubspec.yaml index 7742e4da..9e35e58f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -76,6 +76,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - assets/icons/automation_functions/ + - assets/icons/routine/ - assets/icons/ - assets/images/ - assets/ From 458ec3976aa0c5d7adde1d551ad377b6797f618c Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 17 Nov 2024 01:11:27 +0300 Subject: [PATCH 03/40] push dragging --- lib/pages/routiens/bloc/routine_bloc.dart | 19 ++- lib/pages/routiens/bloc/routine_event.dart | 26 +++- lib/pages/routiens/bloc/routine_state.dart | 24 +++- .../view/create_new_routine_view.dart | 126 ++++++++++-------- lib/pages/routiens/widgets/dragable_card.dart | 25 ++++ lib/pages/routiens/widgets/if_container.dart | 47 +++++++ .../routiens/widgets/then_container.dart | 47 +++++++ 7 files changed, 247 insertions(+), 67 deletions(-) create mode 100644 lib/pages/routiens/widgets/if_container.dart create mode 100644 lib/pages/routiens/widgets/then_container.dart diff --git a/lib/pages/routiens/bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc.dart index c0b2b5c5..86da3211 100644 --- a/lib/pages/routiens/bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc.dart @@ -1,13 +1,22 @@ import 'package:bloc/bloc.dart'; -import 'package:meta/meta.dart'; +import 'package:equatable/equatable.dart'; part 'routine_event.dart'; part 'routine_state.dart'; class RoutineBloc extends Bloc { - RoutineBloc() : super(RoutineInitial()) { - on((event, emit) { - // TODO: implement event handler - }); + RoutineBloc() : super(const RoutineState()) { + on(_onAddToIfContainer); + on(_onAddToThenContainer); + } + + void _onAddToIfContainer(AddToIfContainer event, Emitter emit) { + final updatedIfItems = List>.from(state.ifItems)..add(event.item); + emit(state.copyWith(ifItems: updatedIfItems)); + } + + void _onAddToThenContainer(AddToThenContainer event, Emitter emit) { + final updatedThenItems = List>.from(state.thenItems)..add(event.item); + emit(state.copyWith(thenItems: updatedThenItems)); } } diff --git a/lib/pages/routiens/bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_event.dart index 52ca0690..6570d637 100644 --- a/lib/pages/routiens/bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_event.dart @@ -1,4 +1,26 @@ part of 'routine_bloc.dart'; -@immutable -sealed class RoutineEvent {} +abstract class RoutineEvent extends Equatable { + const RoutineEvent(); + + @override + List get props => []; +} + +class AddToIfContainer extends RoutineEvent { + final Map item; + + const AddToIfContainer(this.item); + + @override + List get props => [item]; +} + +class AddToThenContainer extends RoutineEvent { + final Map item; + + const AddToThenContainer(this.item); + + @override + List get props => [item]; +} diff --git a/lib/pages/routiens/bloc/routine_state.dart b/lib/pages/routiens/bloc/routine_state.dart index aaf5cb97..3dbd5b9f 100644 --- a/lib/pages/routiens/bloc/routine_state.dart +++ b/lib/pages/routiens/bloc/routine_state.dart @@ -1,6 +1,24 @@ part of 'routine_bloc.dart'; -@immutable -sealed class RoutineState {} +class RoutineState extends Equatable { + final List> ifItems; + final List> thenItems; -final class RoutineInitial extends RoutineState {} + const RoutineState({ + this.ifItems = const [], + this.thenItems = const [], + }); + + RoutineState copyWith({ + List>? ifItems, + List>? thenItems, + }) { + return RoutineState( + ifItems: ifItems ?? this.ifItems, + thenItems: thenItems ?? this.thenItems, + ); + } + + @override + List get props => [ifItems, thenItems]; +} diff --git a/lib/pages/routiens/view/create_new_routine_view.dart b/lib/pages/routiens/view/create_new_routine_view.dart index 764eb374..99b4df71 100644 --- a/lib/pages/routiens/view/create_new_routine_view.dart +++ b/lib/pages/routiens/view/create_new_routine_view.dart @@ -1,6 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/widgets/conditions_routines_devices_view.dart'; +import 'package:syncrow_web/pages/routiens/widgets/if_container.dart'; import 'package:syncrow_web/pages/routiens/widgets/routine_search_and_buttons.dart'; +import 'package:syncrow_web/pages/routiens/widgets/then_container.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateNewRoutineView extends StatelessWidget { @@ -8,74 +12,82 @@ class CreateNewRoutineView extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - alignment: Alignment.topLeft, - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const RoutineSearchAndButtons(), - const SizedBox(height: 20), - Flexible( - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: Card( - child: Container( - decoration: BoxDecoration( - color: ColorsManager.whiteColors, - borderRadius: BorderRadius.circular(15), - ), - child: const ConditionsRoutinesDevicesView()), + return BlocProvider( + create: (context) => RoutineBloc(), + child: Container( + alignment: Alignment.topLeft, + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const RoutineSearchAndButtons(), + const SizedBox(height: 20), + Flexible( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Card( + child: Container( + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(15), + ), + child: const ConditionsRoutinesDevicesView()), + ), ), - ), - const SizedBox( - width: 10, - ), - Expanded( - child: Column( - children: [ - Expanded( - child: Card( - margin: EdgeInsets.zero, - child: Container( - decoration: const BoxDecoration( - color: ColorsManager.whiteColors, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(15), - topRight: Radius.circular(15), + const SizedBox( + width: 10, + ), + Expanded( + child: Column( + children: [ + /// IF Container + Expanded( + child: Card( + margin: EdgeInsets.zero, + child: Container( + decoration: const BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(15), + topRight: Radius.circular(15), + ), ), + child: const IfContainer(), ), ), ), - ), - Container( - height: 2, - width: double.infinity, - color: ColorsManager.dialogBlueTitle, - ), - Expanded( - child: Card( - margin: EdgeInsets.zero, - child: Container( - decoration: const BoxDecoration( - color: ColorsManager.boxColor, - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(15), - bottomRight: Radius.circular(15), + Container( + height: 2, + width: double.infinity, + color: ColorsManager.dialogBlueTitle, + ), + + /// THEN Container + Expanded( + child: Card( + margin: EdgeInsets.zero, + child: Container( + decoration: const BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(15), + bottomRight: Radius.circular(15), + ), ), + child: const ThenContainer(), ), ), ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index 490dd570..1724e04c 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -9,14 +9,28 @@ class DraggableCard extends StatelessWidget { required this.imagePath, required this.title, this.titleColor, + this.isDragged = false, }); final String imagePath; final String title; final Color? titleColor; + final bool isDragged; @override Widget build(BuildContext context) { + return Draggable>( + data: {'key': UniqueKey().toString(), 'imagePath': imagePath, 'title': title}, + feedback: Transform.rotate( + angle: -0.1, + child: _buildCardContent(context), + ), + childWhenDragging: _buildGreyContainer(), + child: isDragged ? _buildGreyContainer() : _buildCardContent(context), + ); + } + + Widget _buildCardContent(BuildContext context) { return Card( color: ColorsManager.whiteColors, child: SizedBox( @@ -60,4 +74,15 @@ class DraggableCard extends StatelessWidget { ), ); } + + Widget _buildGreyContainer() { + return Container( + height: 123, + width: 90, + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(20), + ), + ); + } } diff --git a/lib/pages/routiens/widgets/if_container.dart b/lib/pages/routiens/widgets/if_container.dart new file mode 100644 index 00000000..069f77b4 --- /dev/null +++ b/lib/pages/routiens/widgets/if_container.dart @@ -0,0 +1,47 @@ +// lib/pages/routiens/widgets/if_container.dart + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; + +class IfContainer extends StatelessWidget { + const IfContainer({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return DragTarget>( + builder: (context, candidateData, rejectedData) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('IF', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const SizedBox(height: 16), + Wrap( + spacing: 8, + runSpacing: 8, + children: state.ifItems + .map((item) => DraggableCard( + key: Key(item['key']!), + imagePath: item['imagePath']!, + title: item['title']!, + )) + .toList(), + ), + ], + ), + ); + }, + onAccept: (data) { + context.read().add(AddToIfContainer(data)); + }, + ); + }, + ); + } +} diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart new file mode 100644 index 00000000..bc72bd9f --- /dev/null +++ b/lib/pages/routiens/widgets/then_container.dart @@ -0,0 +1,47 @@ +// lib/pages/routiens/widgets/then_container.dart + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; + +class ThenContainer extends StatelessWidget { + const ThenContainer({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return DragTarget>( + builder: (context, candidateData, rejectedData) { + return Container( + padding: const EdgeInsets.all(16), + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('THEN', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const SizedBox(height: 16), + Wrap( + spacing: 8, + runSpacing: 8, + children: state.thenItems + .map((item) => DraggableCard( + key: Key(item['key']!), + imagePath: item['imagePath']!, + title: item['title']!, + )) + .toList(), + ), + ], + ), + ); + }, + onAccept: (data) { + context.read().add(AddToThenContainer(data)); + }, + ); + }, + ); + } +} From 54143b3ba964779d56d0e41e248011b6c640b97f Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 18 Nov 2024 01:13:28 +0300 Subject: [PATCH 04/40] push get scenes and automation --- lib/pages/routiens/bloc/routine_bloc.dart | 40 ++++ lib/pages/routiens/bloc/routine_event.dart | 18 ++ lib/pages/routiens/bloc/routine_state.dart | 27 ++- lib/pages/routiens/models/routine_model.dart | 30 +++ .../conditions_routines_devices_view.dart | 39 ++-- lib/pages/routiens/widgets/dragable_card.dart | 18 +- .../routiens/widgets/routine_devices.dart | 44 ++++ .../widgets/scenes_and_automations.dart | 42 ++++ lib/services/routines_api.dart | 216 ++++++++++++++++++ lib/utils/constants/api_const.dart | 26 +-- 10 files changed, 452 insertions(+), 48 deletions(-) create mode 100644 lib/pages/routiens/models/routine_model.dart create mode 100644 lib/pages/routiens/widgets/routine_devices.dart create mode 100644 lib/pages/routiens/widgets/scenes_and_automations.dart create mode 100644 lib/services/routines_api.dart diff --git a/lib/pages/routiens/bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc.dart index 86da3211..a7fd7228 100644 --- a/lib/pages/routiens/bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc.dart @@ -1,13 +1,19 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/routiens/models/routine_model.dart'; +import 'package:syncrow_web/services/routines_api.dart'; part 'routine_event.dart'; part 'routine_state.dart'; +const spaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6'; + class RoutineBloc extends Bloc { RoutineBloc() : super(const RoutineState()) { on(_onAddToIfContainer); on(_onAddToThenContainer); + on(_onLoadScenes); + on(_onLoadAutomation); } void _onAddToIfContainer(AddToIfContainer event, Emitter emit) { @@ -19,4 +25,38 @@ class RoutineBloc extends Bloc { final updatedThenItems = List>.from(state.thenItems)..add(event.item); emit(state.copyWith(thenItems: updatedThenItems)); } + + Future _onLoadScenes(LoadScenes event, Emitter emit) async { + emit(state.copyWith(isLoading: true, errorMessage: null)); + + try { + final scenes = await SceneApi.getScenesByUnitId(event.unitId); + emit(state.copyWith( + scenes: scenes, + isLoading: false, + )); + } catch (e) { + emit(state.copyWith( + isLoading: false, + errorMessage: 'Something went wrong', + )); + } + } + + Future _onLoadAutomation(LoadAutomation event, Emitter emit) async { + emit(state.copyWith(isLoading: true, errorMessage: null)); + + try { + final automations = await SceneApi.getAutomationByUnitId(event.unitId); + emit(state.copyWith( + automations: automations, + isLoading: false, + )); + } catch (e) { + emit(state.copyWith( + isLoading: false, + errorMessage: 'Something went wrong', + )); + } + } } diff --git a/lib/pages/routiens/bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_event.dart index 6570d637..6dcacf9c 100644 --- a/lib/pages/routiens/bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_event.dart @@ -24,3 +24,21 @@ class AddToThenContainer extends RoutineEvent { @override List get props => [item]; } + +class LoadScenes extends RoutineEvent { + final String unitId; + + const LoadScenes(this.unitId); + + @override + List get props => [unitId]; +} + +class LoadAutomation extends RoutineEvent { + final String unitId; + + const LoadAutomation(this.unitId); + + @override + List get props => [unitId]; +} diff --git a/lib/pages/routiens/bloc/routine_state.dart b/lib/pages/routiens/bloc/routine_state.dart index 3dbd5b9f..8f715428 100644 --- a/lib/pages/routiens/bloc/routine_state.dart +++ b/lib/pages/routiens/bloc/routine_state.dart @@ -3,22 +3,47 @@ part of 'routine_bloc.dart'; class RoutineState extends Equatable { final List> ifItems; final List> thenItems; + final List> availableCards; + final List scenes; + final List automations; + final bool isLoading; + final String? errorMessage; const RoutineState({ this.ifItems = const [], this.thenItems = const [], + this.availableCards = const [], + this.scenes = const [], + this.automations = const [], + this.isLoading = false, + this.errorMessage, }); RoutineState copyWith({ List>? ifItems, List>? thenItems, + List? scenes, + List? automations, + bool? isLoading, + String? errorMessage, }) { return RoutineState( ifItems: ifItems ?? this.ifItems, thenItems: thenItems ?? this.thenItems, + scenes: scenes ?? this.scenes, + automations: automations ?? this.automations, + isLoading: isLoading ?? this.isLoading, + errorMessage: errorMessage ?? this.errorMessage, ); } @override - List get props => [ifItems, thenItems]; + List get props => [ + ifItems, + thenItems, + scenes, + automations, + isLoading, + errorMessage, + ]; } diff --git a/lib/pages/routiens/models/routine_model.dart b/lib/pages/routiens/models/routine_model.dart new file mode 100644 index 00000000..2db2247f --- /dev/null +++ b/lib/pages/routiens/models/routine_model.dart @@ -0,0 +1,30 @@ +import 'dart:convert'; + +class ScenesModel { + final String id; + final String name; + final String status; + final String type; + final String? icon; + + ScenesModel({required this.id, required this.name, required this.status, required this.type, this.icon}); + + factory ScenesModel.fromRawJson(String str) => ScenesModel.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory ScenesModel.fromJson(Map json) => ScenesModel( + id: json["id"], + name: json["name"] ?? '', + status: json["status"] ?? '', + type: json["type"] ?? '', + icon: json["icon"] as String?, + ); + + Map toJson() => { + "id": id, + "name": name, + "status": status, + "type": type, + }; +} diff --git a/lib/pages/routiens/widgets/conditions_routines_devices_view.dart b/lib/pages/routiens/widgets/conditions_routines_devices_view.dart index 6e6ef4a2..91100220 100644 --- a/lib/pages/routiens/widgets/conditions_routines_devices_view.dart +++ b/lib/pages/routiens/widgets/conditions_routines_devices_view.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; +import 'package:syncrow_web/pages/routiens/widgets/routine_devices.dart'; import 'package:syncrow_web/pages/routiens/widgets/routines_title_widget.dart'; +import 'package:syncrow_web/pages/routiens/widgets/scenes_and_automations.dart'; import 'package:syncrow_web/pages/routiens/widgets/search_bar_condition_title.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -50,6 +50,9 @@ class ConditionsRoutinesDevicesView extends StatelessWidget { title: 'Conditions', subtitle: '(THEN)', ), + const SizedBox( + height: 10, + ), const Wrap( spacing: 10, runSpacing: 10, @@ -71,33 +74,21 @@ class ConditionsRoutinesDevicesView extends StatelessWidget { title: 'Routines', subtitle: '(THEN)', ), + const SizedBox( + height: 10, + ), + const ScenesAndAutomations(), + const SizedBox( + height: 10, + ), const TitleRoutine( title: 'Devices', subtitle: '', ), - BlocProvider( - create: (context) => DeviceManagementBloc() - ..add( - FetchDevices(), - ), - child: BlocBuilder( - builder: (context, state) { - if (state is DeviceManagementLoaded) { - return Wrap( - spacing: 10, - runSpacing: 10, - children: state.devices - .map((device) => DraggableCard( - imagePath: device.getDefaultIcon(device.productType), - title: device.name ?? '', - )) - .toList(), - ); - } - return const Center(child: CircularProgressIndicator()); - }, - ), + const SizedBox( + height: 10, ), + const RoutineDevices(), ], ), ), diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index 1724e04c..7e98da28 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -60,13 +60,17 @@ class DraggableCard extends StatelessWidget { const SizedBox( height: 8, ), - Text( - title, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 2, - style: context.textTheme.bodySmall?.copyWith( - color: titleColor ?? ColorsManager.blackColor, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 3), + child: Text( + title, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: context.textTheme.bodySmall?.copyWith( + color: titleColor ?? ColorsManager.blackColor, + fontSize: 12, + ), ), ), ], diff --git a/lib/pages/routiens/widgets/routine_devices.dart b/lib/pages/routiens/widgets/routine_devices.dart new file mode 100644 index 00000000..9fba225c --- /dev/null +++ b/lib/pages/routiens/widgets/routine_devices.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; + +class RoutineDevices extends StatelessWidget { + const RoutineDevices({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => DeviceManagementBloc() + ..add( + FetchDevices(), + ), + child: BlocBuilder( + builder: (context, state) { + if (state is DeviceManagementLoaded) { + final deviceList = state.devices + .where((device) => + device.productType == 'AC' || + device.productType == '1G' || + device.productType == '2G' || + device.productType == '3G') + .toList(); + return Wrap( + spacing: 10, + runSpacing: 10, + children: deviceList + .map((device) => DraggableCard( + imagePath: device.getDefaultIcon(device.productType), + title: device.name ?? '', + )) + .toList(), + ); + } + return const Center(child: CircularProgressIndicator()); + }, + ), + ); + } +} diff --git a/lib/pages/routiens/widgets/scenes_and_automations.dart b/lib/pages/routiens/widgets/scenes_and_automations.dart new file mode 100644 index 00000000..9819f0fe --- /dev/null +++ b/lib/pages/routiens/widgets/scenes_and_automations.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class ScenesAndAutomations extends StatelessWidget { + const ScenesAndAutomations({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => RoutineBloc() + ..add( + LoadScenes(spaceId), + ) + ..add( + LoadAutomation(spaceId), + ), + child: BlocBuilder( + builder: (context, state) { + if (state.scenes.isNotEmpty || state.automations.isNotEmpty) { + var scenes = [...state.scenes, ...state.automations]; + return Wrap( + spacing: 10, + runSpacing: 10, + children: scenes + .map((scene) => DraggableCard( + imagePath: Assets.logo, + title: scene.name ?? '', + )) + .toList(), + ); + } + return const Center(child: CircularProgressIndicator()); + }, + ), + ); + } +} diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart new file mode 100644 index 00000000..edf865a3 --- /dev/null +++ b/lib/services/routines_api.dart @@ -0,0 +1,216 @@ +import 'package:syncrow_web/pages/routiens/models/routine_model.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; +import 'package:syncrow_web/utils/constants/api_const.dart'; + +class SceneApi { + static final HTTPService _httpService = HTTPService(); + +// //create scene +// static Future> createScene( +// CreateSceneModel createSceneModel) async { +// try { +// final response = await _httpService.post( +// path: ApiEndpoints.createScene, +// body: createSceneModel.toMap(), +// showServerMessage: false, +// expectedResponseModel: (json) { +// return json; +// }, +// ); +// return response; +// } catch (e) { +// rethrow; +// } +// } +// +// // create automation +// static Future> createAutomation( +// CreateAutomationModel createAutomationModel) async { +// try { +// final response = await _httpService.post( +// path: ApiEndpoints.createAutomation, +// body: createAutomationModel.toMap(), +// showServerMessage: false, +// expectedResponseModel: (json) { +// return json; +// }, +// ); +// return response; +// } catch (e) { +// rethrow; +// } +// } + + //get scene by unit id + + static Future> getScenesByUnitId(String unitId) async { + try { + final response = await _httpService.get( + path: ApiEndpoints.getSpaceScenes.replaceAll('{unitUuid}', unitId), + showServerMessage: false, + expectedResponseModel: (json) { + List scenes = []; + for (var scene in json) { + scenes.add(ScenesModel.fromJson(scene)); + } + return scenes; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + //getAutomation + + static Future> getAutomationByUnitId(String unitId) async { + try { + final response = await _httpService.get( + path: ApiEndpoints.getSpaceAutomation.replaceAll('{unitUuid}', unitId), + showServerMessage: false, + expectedResponseModel: (json) { + List scenes = []; + for (var scene in json) { + scenes.add(ScenesModel.fromJson(scene)); + } + return scenes; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + // static Future triggerScene(String sceneId) async { + // try { + // final response = await _httpService.post( + // path: ApiEndpoints.triggerScene.replaceAll('{sceneId}', sceneId), + // showServerMessage: false, + // expectedResponseModel: (json) => json['success'], + // ); + // return response; + // } catch (e) { + // rethrow; + // } + // } + +// //automation details +// static Future getAutomationDetails( +// String automationId) async { +// try { +// final response = await _httpService.get( +// path: ApiEndpoints.getAutomationDetails +// .replaceAll('{automationId}', automationId), +// showServerMessage: false, +// expectedResponseModel: (json) => SceneDetailsModel.fromJson(json), +// ); +// return response; +// } catch (e) { +// rethrow; +// } +// } +// +// //updateAutomationStatus +// static Future updateAutomationStatus(String automationId, +// AutomationStatusUpdate createAutomationEnable) async { +// try { +// final response = await _httpService.put( +// path: ApiEndpoints.updateAutomationStatus +// .replaceAll('{automationId}', automationId), +// body: createAutomationEnable.toMap(), +// expectedResponseModel: (json) => json['success'], +// ); +// return response; +// } catch (e) { +// rethrow; +// } +// } + + // //getScene + // + // static Future getSceneDetails(String sceneId) async { + // try { + // final response = await _httpService.get( + // path: ApiEndpoints.getScene.replaceAll('{sceneId}', sceneId), + // showServerMessage: false, + // expectedResponseModel: (json) => SceneDetailsModel.fromJson(json), + // ); + // return response; + // } catch (e) { + // rethrow; + // } + // } + // + // //update Scene + // static updateScene(CreateSceneModel createSceneModel, String sceneId) async { + // try { + // final response = await _httpService.put( + // path: ApiEndpoints.updateScene.replaceAll('{sceneId}', sceneId), + // body: createSceneModel + // .toJson(sceneId.isNotEmpty == true ? sceneId : null), + // expectedResponseModel: (json) { + // return json; + // }, + // ); + // return response; + // } catch (e) { + // rethrow; + // } + // } + // + // //update automation + // static updateAutomation( + // CreateAutomationModel createAutomationModel, String automationId) async { + // try { + // final response = await _httpService.put( + // path: ApiEndpoints.updateAutomation + // .replaceAll('{automationId}', automationId), + // body: createAutomationModel + // .toJson(automationId.isNotEmpty == true ? automationId : null), + // expectedResponseModel: (json) { + // return json; + // }, + // ); + // return response; + // } catch (e) { + // rethrow; + // } + // } + // + // //delete Scene + // + // static Future deleteScene( + // {required String unitUuid, required String sceneId}) async { + // try { + // final response = await _httpService.delete( + // path: ApiEndpoints.deleteScene + // .replaceAll('{sceneId}', sceneId) + // .replaceAll('{unitUuid}', unitUuid), + // showServerMessage: false, + // expectedResponseModel: (json) => json['statusCode'] == 200, + // ); + // return response; + // } catch (e) { + // rethrow; + // } + // } + // + // // delete automation + // static Future deleteAutomation( + // {required String unitUuid, required String automationId}) async { + // try { + // final response = await _httpService.delete( + // path: ApiEndpoints.deleteAutomation + // .replaceAll('{automationId}', automationId) + // .replaceAll('{unitUuid}', unitUuid), + // showServerMessage: false, + // expectedResponseModel: (json) => json['statusCode'] == 200, + // ); + // return response; + // } catch (e) { + // rethrow; + // } + // } +} diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index bf167ab5..759ef7d7 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -11,16 +11,12 @@ abstract class ApiEndpoints { static const String visitorPassword = '/visitor-password'; static const String getDevices = '/visitor-password/devices'; - static const String sendOnlineOneTime = - '/visitor-password/temporary-password/online/one-time'; - static const String sendOnlineMultipleTime = - '/visitor-password/temporary-password/online/multiple-time'; + static const String sendOnlineOneTime = '/visitor-password/temporary-password/online/one-time'; + static const String sendOnlineMultipleTime = '/visitor-password/temporary-password/online/multiple-time'; //offline Password - static const String sendOffLineOneTime = - '/visitor-password/temporary-password/offline/one-time'; - static const String sendOffLineMultipleTime = - '/visitor-password/temporary-password/offline/multiple-time'; + static const String sendOffLineOneTime = '/visitor-password/temporary-password/offline/one-time'; + static const String sendOffLineMultipleTime = '/visitor-password/temporary-password/offline/multiple-time'; static const String getUser = '/user/{userUuid}'; @@ -40,14 +36,12 @@ abstract class ApiEndpoints { '/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}'; static const String scheduleByDeviceId = '/schedule/{deviceUuid}'; - static const String getScheduleByDeviceId = - '/schedule/{deviceUuid}?category={category}'; - static const String deleteScheduleByDeviceId = - '/schedule/{deviceUuid}/{scheduleUuid}'; - static const String updateScheduleByDeviceId = - '/schedule/enable/{deviceUuid}'; + static const String getScheduleByDeviceId = '/schedule/{deviceUuid}?category={category}'; + static const String deleteScheduleByDeviceId = '/schedule/{deviceUuid}/{scheduleUuid}'; + static const String updateScheduleByDeviceId = '/schedule/enable/{deviceUuid}'; static const String factoryReset = '/device/factory/reset/{deviceUuid}'; - static const String powerClamp = - '/device/{powerClampUuid}/power-clamp/status'; + static const String powerClamp = '/device/{powerClampUuid}/power-clamp/status'; + static const String getSpaceScenes = '/scene/tap-to-run/{unitUuid}'; + static const String getSpaceAutomation = '/automation/{unitUuid}'; } From 57b8f6b03ea1786f28ee4c1ca975e9b9c3ce58e5 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Tue, 19 Nov 2024 00:54:43 +0300 Subject: [PATCH 05/40] push add ac fucntions and handle doplicate adding to the then or if items --- assets/icons/functions_icons/ac_cooling.svg | 12 + assets/icons/functions_icons/ac_fan_auto.svg | 9 + assets/icons/functions_icons/ac_fan_high.svg | 12 + assets/icons/functions_icons/ac_fan_low.svg | 12 + .../icons/functions_icons/ac_fan_middle.svg | 12 + assets/icons/functions_icons/ac_heating.svg | 14 ++ assets/icons/functions_icons/ac_power.svg | 5 + assets/icons/functions_icons/ac_power_off.svg | 5 + .../automation_functions/card_unlock.svg | 14 ++ .../automation_functions/current_temp.svg | 11 + .../automation_functions/doorbell.svg | 13 ++ .../doorlock_normal_open.svg | 12 + .../automation_functions/double_lock.svg | 12 + .../fingerprint_unlock.svg | 79 +++++++ .../automation_functions/hijack_alarm.svg | 13 ++ .../automation_functions/lock_alarm.svg | 149 ++++++++++++ .../automation_functions/motion.svg | 14 ++ .../automation_functions/password_unlock.svg | 16 ++ .../automation_functions/presence.svg | 18 ++ .../automation_functions/presence_state.svg | 18 ++ .../remote_unlock_req.svg | 15 ++ .../remote_unlock_via_app.svg | 40 ++++ .../residual_electricity.svg | 4 + .../automation_functions/self_test_result.svg | 23 ++ .../temp_password_unlock.svg | 16 ++ .../icons/functions_icons/celsius_degrees.svg | 7 + assets/icons/functions_icons/child_lock.svg | 5 + .../icons/functions_icons/factory_reset.svg | 10 + assets/icons/functions_icons/fan_speed.svg | 12 + .../icons/functions_icons/far_detection.svg | 16 ++ .../far_detection_function.svg | 17 ++ assets/icons/functions_icons/freezing.svg | 3 + assets/icons/functions_icons/indicator.svg | 12 + .../icons/functions_icons/light_countdown.svg | 42 ++++ assets/icons/functions_icons/master_state.svg | 18 ++ .../functions_icons/motion_detection.svg | 21 ++ .../functions_icons/motionless_detection.svg | 6 + assets/icons/functions_icons/nobody_time.svg | 4 + assets/icons/functions_icons/reset_off.svg | 6 + .../functions_icons/scene_child_lock.svg | 12 + .../functions_icons/scene_child_unlock.svg | 12 + .../icons/functions_icons/scene_refresh.svg | 6 + assets/icons/functions_icons/sensitivity.svg | 14 ++ .../sesitivity_operation_icon.svg | 5 + .../functions_icons/switch_alarm_sound.svg | 10 + assets/icons/functions_icons/tempreture.svg | 11 + .../all_devices/models/devices_model.dart | 25 ++ lib/pages/routiens/bloc/routine_bloc.dart | 16 +- lib/pages/routiens/bloc/routine_event.dart | 4 +- lib/pages/routiens/bloc/routine_state.dart | 8 +- lib/pages/routiens/enum/ac_values_enum.dart | 9 + lib/pages/routiens/helper/ac_helper.dart | 190 ++++++++++++++++ lib/pages/routiens/models/ac/ac_function.dart | 215 ++++++++++++++++++ .../models/ac/ac_operational_value.dart | 11 + .../routiens/models/device_functions.dart | 17 ++ lib/pages/routiens/models/routine_item.dart | 29 +++ lib/pages/routiens/widgets/dragable_card.dart | 12 +- .../routiens/widgets/routine_devices.dart | 14 +- .../widgets/scenes_and_automations.dart | 14 +- lib/utils/constants/assets.dart | 55 +++++ 60 files changed, 1392 insertions(+), 24 deletions(-) create mode 100644 assets/icons/functions_icons/ac_cooling.svg create mode 100644 assets/icons/functions_icons/ac_fan_auto.svg create mode 100644 assets/icons/functions_icons/ac_fan_high.svg create mode 100644 assets/icons/functions_icons/ac_fan_low.svg create mode 100644 assets/icons/functions_icons/ac_fan_middle.svg create mode 100644 assets/icons/functions_icons/ac_heating.svg create mode 100644 assets/icons/functions_icons/ac_power.svg create mode 100644 assets/icons/functions_icons/ac_power_off.svg create mode 100644 assets/icons/functions_icons/automation_functions/card_unlock.svg create mode 100644 assets/icons/functions_icons/automation_functions/current_temp.svg create mode 100644 assets/icons/functions_icons/automation_functions/doorbell.svg create mode 100644 assets/icons/functions_icons/automation_functions/doorlock_normal_open.svg create mode 100644 assets/icons/functions_icons/automation_functions/double_lock.svg create mode 100644 assets/icons/functions_icons/automation_functions/fingerprint_unlock.svg create mode 100644 assets/icons/functions_icons/automation_functions/hijack_alarm.svg create mode 100644 assets/icons/functions_icons/automation_functions/lock_alarm.svg create mode 100644 assets/icons/functions_icons/automation_functions/motion.svg create mode 100644 assets/icons/functions_icons/automation_functions/password_unlock.svg create mode 100644 assets/icons/functions_icons/automation_functions/presence.svg create mode 100644 assets/icons/functions_icons/automation_functions/presence_state.svg create mode 100644 assets/icons/functions_icons/automation_functions/remote_unlock_req.svg create mode 100644 assets/icons/functions_icons/automation_functions/remote_unlock_via_app.svg create mode 100644 assets/icons/functions_icons/automation_functions/residual_electricity.svg create mode 100644 assets/icons/functions_icons/automation_functions/self_test_result.svg create mode 100644 assets/icons/functions_icons/automation_functions/temp_password_unlock.svg create mode 100644 assets/icons/functions_icons/celsius_degrees.svg create mode 100644 assets/icons/functions_icons/child_lock.svg create mode 100644 assets/icons/functions_icons/factory_reset.svg create mode 100644 assets/icons/functions_icons/fan_speed.svg create mode 100644 assets/icons/functions_icons/far_detection.svg create mode 100644 assets/icons/functions_icons/far_detection_function.svg create mode 100644 assets/icons/functions_icons/freezing.svg create mode 100644 assets/icons/functions_icons/indicator.svg create mode 100644 assets/icons/functions_icons/light_countdown.svg create mode 100644 assets/icons/functions_icons/master_state.svg create mode 100644 assets/icons/functions_icons/motion_detection.svg create mode 100644 assets/icons/functions_icons/motionless_detection.svg create mode 100644 assets/icons/functions_icons/nobody_time.svg create mode 100644 assets/icons/functions_icons/reset_off.svg create mode 100644 assets/icons/functions_icons/scene_child_lock.svg create mode 100644 assets/icons/functions_icons/scene_child_unlock.svg create mode 100644 assets/icons/functions_icons/scene_refresh.svg create mode 100644 assets/icons/functions_icons/sensitivity.svg create mode 100644 assets/icons/functions_icons/sesitivity_operation_icon.svg create mode 100644 assets/icons/functions_icons/switch_alarm_sound.svg create mode 100644 assets/icons/functions_icons/tempreture.svg create mode 100644 lib/pages/routiens/enum/ac_values_enum.dart create mode 100644 lib/pages/routiens/helper/ac_helper.dart create mode 100644 lib/pages/routiens/models/ac/ac_function.dart create mode 100644 lib/pages/routiens/models/ac/ac_operational_value.dart create mode 100644 lib/pages/routiens/models/device_functions.dart create mode 100644 lib/pages/routiens/models/routine_item.dart diff --git a/assets/icons/functions_icons/ac_cooling.svg b/assets/icons/functions_icons/ac_cooling.svg new file mode 100644 index 00000000..e95c0d4e --- /dev/null +++ b/assets/icons/functions_icons/ac_cooling.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/ac_fan_auto.svg b/assets/icons/functions_icons/ac_fan_auto.svg new file mode 100644 index 00000000..0acacfef --- /dev/null +++ b/assets/icons/functions_icons/ac_fan_auto.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/functions_icons/ac_fan_high.svg b/assets/icons/functions_icons/ac_fan_high.svg new file mode 100644 index 00000000..d6131531 --- /dev/null +++ b/assets/icons/functions_icons/ac_fan_high.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/ac_fan_low.svg b/assets/icons/functions_icons/ac_fan_low.svg new file mode 100644 index 00000000..f4bf56b7 --- /dev/null +++ b/assets/icons/functions_icons/ac_fan_low.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/ac_fan_middle.svg b/assets/icons/functions_icons/ac_fan_middle.svg new file mode 100644 index 00000000..ee940238 --- /dev/null +++ b/assets/icons/functions_icons/ac_fan_middle.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/ac_heating.svg b/assets/icons/functions_icons/ac_heating.svg new file mode 100644 index 00000000..47a160c8 --- /dev/null +++ b/assets/icons/functions_icons/ac_heating.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/ac_power.svg b/assets/icons/functions_icons/ac_power.svg new file mode 100644 index 00000000..cc2127f0 --- /dev/null +++ b/assets/icons/functions_icons/ac_power.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/functions_icons/ac_power_off.svg b/assets/icons/functions_icons/ac_power_off.svg new file mode 100644 index 00000000..70f7f9aa --- /dev/null +++ b/assets/icons/functions_icons/ac_power_off.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/functions_icons/automation_functions/card_unlock.svg b/assets/icons/functions_icons/automation_functions/card_unlock.svg new file mode 100644 index 00000000..dd77680a --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/card_unlock.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/current_temp.svg b/assets/icons/functions_icons/automation_functions/current_temp.svg new file mode 100644 index 00000000..42cceb23 --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/current_temp.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/doorbell.svg b/assets/icons/functions_icons/automation_functions/doorbell.svg new file mode 100644 index 00000000..1dc515a9 --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/doorbell.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/doorlock_normal_open.svg b/assets/icons/functions_icons/automation_functions/doorlock_normal_open.svg new file mode 100644 index 00000000..8f4a5901 --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/doorlock_normal_open.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/double_lock.svg b/assets/icons/functions_icons/automation_functions/double_lock.svg new file mode 100644 index 00000000..d8ad971d --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/double_lock.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/fingerprint_unlock.svg b/assets/icons/functions_icons/automation_functions/fingerprint_unlock.svg new file mode 100644 index 00000000..f9f5b84c --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/fingerprint_unlock.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/hijack_alarm.svg b/assets/icons/functions_icons/automation_functions/hijack_alarm.svg new file mode 100644 index 00000000..e32997fb --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/hijack_alarm.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/lock_alarm.svg b/assets/icons/functions_icons/automation_functions/lock_alarm.svg new file mode 100644 index 00000000..8bd2deeb --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/lock_alarm.svg @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/motion.svg b/assets/icons/functions_icons/automation_functions/motion.svg new file mode 100644 index 00000000..8d69463b --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/motion.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/password_unlock.svg b/assets/icons/functions_icons/automation_functions/password_unlock.svg new file mode 100644 index 00000000..1920b69f --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/password_unlock.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/presence.svg b/assets/icons/functions_icons/automation_functions/presence.svg new file mode 100644 index 00000000..d71a474d --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/presence.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/presence_state.svg b/assets/icons/functions_icons/automation_functions/presence_state.svg new file mode 100644 index 00000000..d5de48e1 --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/presence_state.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/remote_unlock_req.svg b/assets/icons/functions_icons/automation_functions/remote_unlock_req.svg new file mode 100644 index 00000000..da128ff7 --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/remote_unlock_req.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/remote_unlock_via_app.svg b/assets/icons/functions_icons/automation_functions/remote_unlock_via_app.svg new file mode 100644 index 00000000..39fc859b --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/remote_unlock_via_app.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/residual_electricity.svg b/assets/icons/functions_icons/automation_functions/residual_electricity.svg new file mode 100644 index 00000000..6a5b6127 --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/residual_electricity.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/functions_icons/automation_functions/self_test_result.svg b/assets/icons/functions_icons/automation_functions/self_test_result.svg new file mode 100644 index 00000000..8739327b --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/self_test_result.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/temp_password_unlock.svg b/assets/icons/functions_icons/automation_functions/temp_password_unlock.svg new file mode 100644 index 00000000..98d7573c --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/temp_password_unlock.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/celsius_degrees.svg b/assets/icons/functions_icons/celsius_degrees.svg new file mode 100644 index 00000000..7acbd6e7 --- /dev/null +++ b/assets/icons/functions_icons/celsius_degrees.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/assets/icons/functions_icons/child_lock.svg b/assets/icons/functions_icons/child_lock.svg new file mode 100644 index 00000000..6b0138bf --- /dev/null +++ b/assets/icons/functions_icons/child_lock.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/functions_icons/factory_reset.svg b/assets/icons/functions_icons/factory_reset.svg new file mode 100644 index 00000000..7a47f24b --- /dev/null +++ b/assets/icons/functions_icons/factory_reset.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/functions_icons/fan_speed.svg b/assets/icons/functions_icons/fan_speed.svg new file mode 100644 index 00000000..07a48834 --- /dev/null +++ b/assets/icons/functions_icons/fan_speed.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/far_detection.svg b/assets/icons/functions_icons/far_detection.svg new file mode 100644 index 00000000..2827d94a --- /dev/null +++ b/assets/icons/functions_icons/far_detection.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/far_detection_function.svg b/assets/icons/functions_icons/far_detection_function.svg new file mode 100644 index 00000000..894b84ed --- /dev/null +++ b/assets/icons/functions_icons/far_detection_function.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/freezing.svg b/assets/icons/functions_icons/freezing.svg new file mode 100644 index 00000000..6c02f2e4 --- /dev/null +++ b/assets/icons/functions_icons/freezing.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/functions_icons/indicator.svg b/assets/icons/functions_icons/indicator.svg new file mode 100644 index 00000000..b58a976e --- /dev/null +++ b/assets/icons/functions_icons/indicator.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/light_countdown.svg b/assets/icons/functions_icons/light_countdown.svg new file mode 100644 index 00000000..94f65b9a --- /dev/null +++ b/assets/icons/functions_icons/light_countdown.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/master_state.svg b/assets/icons/functions_icons/master_state.svg new file mode 100644 index 00000000..0aafae1a --- /dev/null +++ b/assets/icons/functions_icons/master_state.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/motion_detection.svg b/assets/icons/functions_icons/motion_detection.svg new file mode 100644 index 00000000..a9b2d685 --- /dev/null +++ b/assets/icons/functions_icons/motion_detection.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/motionless_detection.svg b/assets/icons/functions_icons/motionless_detection.svg new file mode 100644 index 00000000..25a767c1 --- /dev/null +++ b/assets/icons/functions_icons/motionless_detection.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/functions_icons/nobody_time.svg b/assets/icons/functions_icons/nobody_time.svg new file mode 100644 index 00000000..df80b517 --- /dev/null +++ b/assets/icons/functions_icons/nobody_time.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/functions_icons/reset_off.svg b/assets/icons/functions_icons/reset_off.svg new file mode 100644 index 00000000..eac88f2b --- /dev/null +++ b/assets/icons/functions_icons/reset_off.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/functions_icons/scene_child_lock.svg b/assets/icons/functions_icons/scene_child_lock.svg new file mode 100644 index 00000000..7e56164a --- /dev/null +++ b/assets/icons/functions_icons/scene_child_lock.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/scene_child_unlock.svg b/assets/icons/functions_icons/scene_child_unlock.svg new file mode 100644 index 00000000..4eafbdea --- /dev/null +++ b/assets/icons/functions_icons/scene_child_unlock.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/scene_refresh.svg b/assets/icons/functions_icons/scene_refresh.svg new file mode 100644 index 00000000..c54ffb04 --- /dev/null +++ b/assets/icons/functions_icons/scene_refresh.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/functions_icons/sensitivity.svg b/assets/icons/functions_icons/sensitivity.svg new file mode 100644 index 00000000..b75ebd3e --- /dev/null +++ b/assets/icons/functions_icons/sensitivity.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/sesitivity_operation_icon.svg b/assets/icons/functions_icons/sesitivity_operation_icon.svg new file mode 100644 index 00000000..612148c5 --- /dev/null +++ b/assets/icons/functions_icons/sesitivity_operation_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/functions_icons/switch_alarm_sound.svg b/assets/icons/functions_icons/switch_alarm_sound.svg new file mode 100644 index 00000000..db645338 --- /dev/null +++ b/assets/icons/functions_icons/switch_alarm_sound.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/functions_icons/tempreture.svg b/assets/icons/functions_icons/tempreture.svg new file mode 100644 index 00000000..448083a7 --- /dev/null +++ b/assets/icons/functions_icons/tempreture.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index 50e588b0..b37ff36c 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -2,6 +2,8 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_com import 'package:syncrow_web/pages/device_managment/all_devices/models/device_space_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart'; +import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart'; +import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/enum/device_types.dart'; @@ -69,6 +71,7 @@ class AllDevicesModel { int? batteryLevel; String? productName; List? spaces; + List? _deviceFunctions; AllDevicesModel({ this.room, @@ -215,6 +218,28 @@ SOS return tempIcon; } + List get deviceFunctions { + _deviceFunctions ??= _getDeviceFunctions(); + return _deviceFunctions!; + } + + List _getDeviceFunctions() { + switch (productType) { + case 'AC': + return [ + SwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''), + ModeFunction(deviceId: uuid ?? '', deviceName: name ?? ''), + TempSetFunction(deviceId: uuid ?? '', deviceName: name ?? ''), + LevelFunction(deviceId: uuid ?? '', deviceName: name ?? ''), + ChildLockFunction(deviceId: uuid ?? '', deviceName: name ?? ''), + ]; + + // other product types + default: + return []; + } + } + Map toJson() { final data = {}; if (room != null) { diff --git a/lib/pages/routiens/bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc.dart index a7fd7228..dfbb824c 100644 --- a/lib/pages/routiens/bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc.dart @@ -17,13 +17,21 @@ class RoutineBloc extends Bloc { } void _onAddToIfContainer(AddToIfContainer event, Emitter emit) { - final updatedIfItems = List>.from(state.ifItems)..add(event.item); - emit(state.copyWith(ifItems: updatedIfItems)); + if (!_isDuplicate(state.ifItems, event.item)) { + final updatedIfItems = List>.from(state.ifItems)..add(event.item); + emit(state.copyWith(ifItems: updatedIfItems)); + } } void _onAddToThenContainer(AddToThenContainer event, Emitter emit) { - final updatedThenItems = List>.from(state.thenItems)..add(event.item); - emit(state.copyWith(thenItems: updatedThenItems)); + if (!_isDuplicate(state.thenItems, event.item)) { + final updatedThenItems = List>.from(state.thenItems)..add(event.item); + emit(state.copyWith(thenItems: updatedThenItems)); + } + } + + bool _isDuplicate(List> items, Map newItem) { + return items.any((item) => item['imagePath'] == newItem['imagePath'] && item['title'] == newItem['title']); } Future _onLoadScenes(LoadScenes event, Emitter emit) async { diff --git a/lib/pages/routiens/bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_event.dart index 6dcacf9c..aaee73c7 100644 --- a/lib/pages/routiens/bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_event.dart @@ -8,7 +8,7 @@ abstract class RoutineEvent extends Equatable { } class AddToIfContainer extends RoutineEvent { - final Map item; + final Map item; const AddToIfContainer(this.item); @@ -17,7 +17,7 @@ class AddToIfContainer extends RoutineEvent { } class AddToThenContainer extends RoutineEvent { - final Map item; + final Map item; const AddToThenContainer(this.item); diff --git a/lib/pages/routiens/bloc/routine_state.dart b/lib/pages/routiens/bloc/routine_state.dart index 8f715428..63d2e6f8 100644 --- a/lib/pages/routiens/bloc/routine_state.dart +++ b/lib/pages/routiens/bloc/routine_state.dart @@ -1,8 +1,8 @@ part of 'routine_bloc.dart'; class RoutineState extends Equatable { - final List> ifItems; - final List> thenItems; + final List> ifItems; + final List> thenItems; final List> availableCards; final List scenes; final List automations; @@ -20,8 +20,8 @@ class RoutineState extends Equatable { }); RoutineState copyWith({ - List>? ifItems, - List>? thenItems, + List>? ifItems, + List>? thenItems, List? scenes, List? automations, bool? isLoading, diff --git a/lib/pages/routiens/enum/ac_values_enum.dart b/lib/pages/routiens/enum/ac_values_enum.dart new file mode 100644 index 00000000..e7d0f865 --- /dev/null +++ b/lib/pages/routiens/enum/ac_values_enum.dart @@ -0,0 +1,9 @@ +enum AcValuesEnums { + Cooling, + Heating, + Ventilation, +} + +enum TempModes { hot, cold, wind } + +enum FanSpeeds { auto, low, middle, high } diff --git a/lib/pages/routiens/helper/ac_helper.dart b/lib/pages/routiens/helper/ac_helper.dart new file mode 100644 index 00000000..6cdb415d --- /dev/null +++ b/lib/pages/routiens/helper/ac_helper.dart @@ -0,0 +1,190 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart'; +import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +mixin ACHelper { + Future?> showACFunctionsDialog(BuildContext context, List> functions) { + List acFunctions = functions.whereType().toList(); + String? selectedFunction; + dynamic selectedValue; + + return showDialog( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: Container( + width: selectedFunction != null ? 600 : 360, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'AC Functions', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontWeight.bold, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 50), + child: Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + ), + Flexible( + child: Row( + children: [ + Expanded( + child: ListView.separated( + shrinkWrap: true, + itemCount: acFunctions.length, + separatorBuilder: (context, index) => Divider(), + itemBuilder: (context, index) { + final function = acFunctions[index]; + return ListTile( + leading: Image.asset(function.icon, width: 24, height: 24), + title: Text(function.operationName), + trailing: Icon(Icons.arrow_forward_ios), + onTap: () { + setState(() { + selectedFunction = function.code; + selectedValue = null; + }); + }, + ); + }, + ), + ), + if (selectedFunction != null) + Container( + width: 1, + color: ColorsManager.greyColor, + ), + if (selectedFunction != null) + Expanded( + child: ListView.separated( + shrinkWrap: true, + itemCount: acFunctions + .firstWhere((f) => f.code == selectedFunction) + .getOperationalValues() + .length, + separatorBuilder: (context, index) => Divider(), + itemBuilder: (context, index) { + final operationalValue = acFunctions.firstWhere((f) => f.code == selectedFunction) + ..getOperationalValues()[index]; + return ListTile( + leading: Image.asset(operationalValue.icon, width: 24, height: 24), + title: Text(operationalValue.getOperationalValues()[index].description), + trailing: Radio( + value: operationalValue.getOperationalValues()[index].value, + groupValue: selectedValue, + onChanged: (value) { + setState(() { + selectedValue = value; + }); + }, + ), + ); + }, + ), + ), + ], + ), + ), + Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Center( + child: Text( + 'Cancel', + style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: ColorsManager.greyColor), + ), + ), + ), + Container( + height: 50, + width: 1, + color: ColorsManager.greyColor, + ), + GestureDetector( + onTap: () { + // Handle the confirmation action here + Navigator.pop(context, { + 'function': selectedFunction, + 'value': selectedValue, + }); + }, + child: Center( + child: Text( + 'Confirm', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), + ), + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + }, + ); + } + + void handleACDeviceDrop(BuildContext context, Map data) { + final device = data['device'] as AllDevicesModel; + final acFunctions = device.deviceFunctions; + + showACFunctionsDialog(context, acFunctions).then((result) { + if (result != null) { + _addACDeviceToRoutine(context, data, result); + } + }); + } + + void handleNonACDeviceDrop(BuildContext context, Map data) { + context.read().add(AddToThenContainer(data)); + } + + void _addACDeviceToRoutine(BuildContext context, Map deviceData, Map functionData) { + final updatedData = { + ...deviceData, + 'function': functionData['function'], + 'value': functionData['value'], + }; + + context.read().add(AddToThenContainer(updatedData)); + + _logACFunctionSelection(functionData); + } + + void _logACFunctionSelection(Map functionData) { + print('Selected AC function: ${functionData['function']}, Value: ${functionData['value']}'); + } +} diff --git a/lib/pages/routiens/models/ac/ac_function.dart b/lib/pages/routiens/models/ac/ac_function.dart new file mode 100644 index 00000000..3afe74c6 --- /dev/null +++ b/lib/pages/routiens/models/ac/ac_function.dart @@ -0,0 +1,215 @@ +import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; +import 'package:syncrow_web/pages/routiens/models/ac/ac_operational_value.dart'; +import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +abstract class ACFunction extends DeviceFunction { + ACFunction({ + required String deviceId, + required String deviceName, + required String code, + required String operationName, + required String icon, + }) : super( + deviceId: deviceId, + deviceName: deviceName, + code: code, + operationName: operationName, + icon: icon, + ); + + @override + AcStatusModel execute(AcStatusModel currentStatus, dynamic newValue); + + List getOperationalValues(); +} + +class SwitchFunction extends ACFunction { + SwitchFunction({required super.deviceId, required super.deviceName}) + : super( + code: 'switch', + operationName: 'Power', + icon: Assets.assetsAcPower, + ); + + @override + AcStatusModel execute(AcStatusModel currentStatus, dynamic newValue) { + return currentStatus.copyWith(acSwitch: newValue as bool); + } + + @override + List getOperationalValues() => [ + ACOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + ACOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; +} + +class ModeFunction extends ACFunction { + ModeFunction({required super.deviceId, required super.deviceName}) + : super( + code: 'mode', + operationName: 'Mode', + icon: Assets.assetsFreezing, + ); + + @override + AcStatusModel execute(AcStatusModel currentStatus, dynamic newValue) { + return currentStatus.copyWith(modeString: (newValue as TempModes).toString().split('.').last); + } + + @override + List getOperationalValues() => [ + ACOperationalValue( + icon: Assets.assetsAcCooling, + description: "Cooling", + value: TempModes.cold, + ), + ACOperationalValue( + icon: Assets.assetsAcHeating, + description: "Heating", + value: TempModes.hot, + ), + ACOperationalValue( + icon: Assets.assetsFanSpeed, + description: "Ventilation", + value: TempModes.wind, + ), + ]; +} + +class TempSetFunction extends ACFunction { + final int min; + final int max; + final int step; + + TempSetFunction({required super.deviceId, required super.deviceName}) + : min = 200, + max = 300, + step = 5, + super( + code: 'temp_set', + operationName: 'Set Temperature', + icon: Assets.assetsTempreture, + ); + + @override + AcStatusModel execute(AcStatusModel currentStatus, dynamic newValue) { + return currentStatus.copyWith(tempSet: newValue as int); + } + + @override + List getOperationalValues() { + List values = []; + for (int temp = min; temp <= max; temp += step) { + values.add(ACOperationalValue( + icon: Assets.assetsTempreture, + description: "${temp / 10}°C", + value: temp, + )); + } + return values; + } +} + +class LevelFunction extends ACFunction { + LevelFunction({required super.deviceId, required super.deviceName}) + : super( + code: 'level', + operationName: 'Fan Speed', + icon: Assets.assetsFanSpeed, + ); + + @override + AcStatusModel execute(AcStatusModel currentStatus, dynamic newValue) { + return currentStatus.copyWith(fanSpeedsString: (newValue as FanSpeeds).toString().split('.').last); + } + + @override + List getOperationalValues() => [ + ACOperationalValue( + icon: Assets.assetsAcFanLow, + description: "LOW", + value: FanSpeeds.low, + ), + ACOperationalValue( + icon: Assets.assetsAcFanMiddle, + description: "MIDDLE", + value: FanSpeeds.middle, + ), + ACOperationalValue( + icon: Assets.assetsAcFanHigh, + description: "HIGH", + value: FanSpeeds.high, + ), + ACOperationalValue( + icon: Assets.assetsAcFanAuto, + description: "AUTO", + value: FanSpeeds.auto, + ), + ]; +} + +class ChildLockFunction extends ACFunction { + ChildLockFunction({required super.deviceId, required super.deviceName}) + : super( + code: 'child_lock', + operationName: 'Child Lock', + icon: Assets.assetsChildLock, + ); + + @override + AcStatusModel execute(AcStatusModel currentStatus, dynamic newValue) { + return currentStatus.copyWith(childLock: newValue as bool); + } + + @override + List getOperationalValues() => [ + ACOperationalValue( + icon: Assets.assetsSceneChildLock, + description: "Lock", + value: true, + ), + ACOperationalValue( + icon: Assets.assetsSceneChildUnlock, + description: "Unlock", + value: false, + ), + ]; +} + +/* + + final deviceId = 'AC001'; + final deviceName = 'Living Room AC'; + + // Initial AC status + var acStatus = AcStatusModel( + uuid: deviceId, + acSwitch: false, + modeString: 'cold', + tempSet: 220, + currentTemp: 250, + fanSpeedsString: 'auto', + childLock: false, + ); + + // Get all AC functions + final acFunctions = getACFunctions(deviceId, deviceName); + + // Example: Turn on the AC + final switchFunction = acFunctions.firstWhere((f) => f is SwitchFunction) as SwitchFunction; + acStatus = switchFunction.execute(acStatus, true); + + // Example: Change mode to heat + final modeFunction = acFunctions.firstWhere((f) => f is ModeFunction) as ModeFunction; + acStatus = modeFunction.execute(acStatus, TempModes.hot); + + */ diff --git a/lib/pages/routiens/models/ac/ac_operational_value.dart b/lib/pages/routiens/models/ac/ac_operational_value.dart new file mode 100644 index 00000000..4ca45d10 --- /dev/null +++ b/lib/pages/routiens/models/ac/ac_operational_value.dart @@ -0,0 +1,11 @@ +class ACOperationalValue { + final String icon; + final String description; + final dynamic value; + + ACOperationalValue({ + required this.icon, + required this.description, + required this.value, + }); +} diff --git a/lib/pages/routiens/models/device_functions.dart b/lib/pages/routiens/models/device_functions.dart new file mode 100644 index 00000000..fa6ac673 --- /dev/null +++ b/lib/pages/routiens/models/device_functions.dart @@ -0,0 +1,17 @@ +abstract class DeviceFunction { + final String deviceId; + final String deviceName; + final String code; + final String operationName; + final String icon; + + DeviceFunction({ + required this.deviceId, + required this.deviceName, + required this.code, + required this.operationName, + required this.icon, + }); + + T execute(T currentStatus, dynamic newValue); +} diff --git a/lib/pages/routiens/models/routine_item.dart b/lib/pages/routiens/models/routine_item.dart new file mode 100644 index 00000000..33920634 --- /dev/null +++ b/lib/pages/routiens/models/routine_item.dart @@ -0,0 +1,29 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; + +class RoutineItem { + final AllDevicesModel device; + final String? function; + final dynamic value; + + RoutineItem({ + required this.device, + this.function, + this.value, + }); + + Map toMap() { + return { + 'device': device, + 'function': function, + 'value': value, + }; + } + + factory RoutineItem.fromMap(Map map) { + return RoutineItem( + device: map['device'] as AllDevicesModel, + function: map['function'], + value: map['value'], + ); + } +} diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index 7e98da28..2e7d1a0e 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -10,24 +10,32 @@ class DraggableCard extends StatelessWidget { required this.title, this.titleColor, this.isDragged = false, + this.isDisabled = false, }); final String imagePath; final String title; final Color? titleColor; final bool isDragged; + final bool isDisabled; @override Widget build(BuildContext context) { - return Draggable>( + Widget card = Draggable>( data: {'key': UniqueKey().toString(), 'imagePath': imagePath, 'title': title}, feedback: Transform.rotate( angle: -0.1, child: _buildCardContent(context), ), childWhenDragging: _buildGreyContainer(), - child: isDragged ? _buildGreyContainer() : _buildCardContent(context), + child: _buildCardContent(context), ); + + if (isDisabled) { + card = AbsorbPointer(child: card); + } + + return card; } Widget _buildCardContent(BuildContext context) { diff --git a/lib/pages/routiens/widgets/routine_devices.dart b/lib/pages/routiens/widgets/routine_devices.dart index 9fba225c..12d8492e 100644 --- a/lib/pages/routiens/widgets/routine_devices.dart +++ b/lib/pages/routiens/widgets/routine_devices.dart @@ -28,12 +28,14 @@ class RoutineDevices extends StatelessWidget { return Wrap( spacing: 10, runSpacing: 10, - children: deviceList - .map((device) => DraggableCard( - imagePath: device.getDefaultIcon(device.productType), - title: device.name ?? '', - )) - .toList(), + children: deviceList.asMap().entries.map((entry) { + final index = entry.key; + final device = entry.value; + return DraggableCard( + imagePath: device.getDefaultIcon(device.productType), + title: device.name ?? '', + ); + }).toList(), ); } return const Center(child: CircularProgressIndicator()); diff --git a/lib/pages/routiens/widgets/scenes_and_automations.dart b/lib/pages/routiens/widgets/scenes_and_automations.dart index 9819f0fe..7c90305b 100644 --- a/lib/pages/routiens/widgets/scenes_and_automations.dart +++ b/lib/pages/routiens/widgets/scenes_and_automations.dart @@ -26,12 +26,14 @@ class ScenesAndAutomations extends StatelessWidget { return Wrap( spacing: 10, runSpacing: 10, - children: scenes - .map((scene) => DraggableCard( - imagePath: Assets.logo, - title: scene.name ?? '', - )) - .toList(), + children: scenes.asMap().entries.map((entry) { + final index = entry.key; + final scene = entry.value; + return DraggableCard( + imagePath: Assets.logo, + title: scene.name ?? '', + ); + }).toList(), ); } return const Center(child: CircularProgressIndicator()); diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 2c0c526f..6c542017 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -210,4 +210,59 @@ class Assets { //assets/icons/routine/delay.svg static const String delay = 'assets/icons/routine/delay.svg'; + + // Assets for functions_icons + static const String assetsSensitivityFunction = "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 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 assetsAcCooling = "assets/icons/functions_icons/ac_cooling.svg"; + static const String assetsAcHeating = "assets/icons/functions_icons/ac_heating.svg"; + static const String assetsCelsiusDegrees = "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 assetsAcFanMiddle = "assets/icons/functions_icons/ac_fan_middle.svg"; + static const String assetsAcFanHigh = "assets/icons/functions_icons/ac_fan_high.svg"; + static const String assetsAcFanAuto = "assets/icons/functions_icons/ac_fan_auto.svg"; + static const String assetsSceneChildLock = "assets/icons/functions_icons/scene_child_lock.svg"; + static const String assetsSceneChildUnlock = "assets/icons/functions_icons/scene_child_unlock.svg"; + static const String assetsSceneRefresh = "assets/icons/functions_icons/scene_refresh.svg"; + static const String assetsLightCountdown = "assets/icons/functions_icons/light_countdown.svg"; + static const String assetsFarDetection = "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 assetsMotionDetection = "assets/icons/functions_icons/motion_detection.svg"; + static const String assetsMotionlessDetection = "assets/icons/functions_icons/motionless_detection.svg"; + static const String assetsNobodyTime = "assets/icons/functions_icons/nobody_time.svg"; + static const String assetsFactoryReset = "assets/icons/functions_icons/factory_reset.svg"; + static const String assetsMasterState = "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"; + +// Assets for automation_functions + static const String assetsCardUnlock = "assets/icons/functions_icons/automation_functions/card_unlock.svg"; + static const String assetsDoorbell = "assets/icons/functions_icons/automation_functions/doorbell.svg"; + static const String assetsDoorlockNormalOpen = + "assets/icons/functions_icons/automation_functions/doorlock_normal_open.svg"; + static const String assetsDoubleLock = "assets/icons/functions_icons/automation_functions/double_lock.svg"; + static const String assetsFingerprintUnlock = + "assets/icons/functions_icons/automation_functions/fingerprint_unlock.svg"; + static const String assetsHijackAlarm = "assets/icons/functions_icons/automation_functions/hijack_alarm.svg"; + static const String assetsLockAlarm = "assets/icons/functions_icons/automation_functions/lock_alarm.svg"; + static const String assetsPasswordUnlock = "assets/icons/functions_icons/automation_functions/password_unlock.svg"; + static const String assetsRemoteUnlockReq = "assets/icons/functions_icons/automation_functions/remote_unlock_req.svg"; + static const String assetsRemoteUnlockViaApp = + "assets/icons/functions_icons/automation_functions/remote_unlock_via_app.svg"; + static const String assetsResidualElectricity = + "assets/icons/functions_icons/automation_functions/residual_electricity.svg"; + static const String assetsTempPasswordUnlock = + "assets/icons/functions_icons/automation_functions/temp_password_unlock.svg"; + static const String assetsSelfTestResult = "assets/icons/functions_icons/automation_functions/self_test_result.svg"; + static const String assetsPresence = "assets/icons/functions_icons/automation_functions/presence.svg"; + static const String assetsMotion = "assets/icons/functions_icons/automation_functions/motion.svg"; + static const String assetsCurrentTemp = "assets/icons/functions_icons/automation_functions/current_temp.svg"; + static const String assetsPresenceState = "assets/icons/functions_icons/automation_functions/presence_state.svg"; } From 4441878bddd1f6d96cf1cd26c714d283ea7fde47 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Thu, 21 Nov 2024 00:50:06 +0300 Subject: [PATCH 06/40] push ac functions and gang switches functions --- .vscode/settings.json | 5 + .../all_devices/models/devices_model.dart | 58 +- lib/pages/routiens/helper/ac_helper.dart | 541 ++++++++++++------ .../dialog_helper/device_dialog_helper.dart | 53 ++ .../helper/one_gang_switch_helper.dart | 206 +++++++ .../helper/three_gang_switch_helper.dart | 211 +++++++ .../helper/two_gang_switch_helper.dart | 209 +++++++ .../gang_switches/base_switch_function.dart | 17 + .../one_gang_switch/one_gang_switch.dart | 57 ++ .../switch_operational_value.dart | 17 + .../three_gang_switch/three_gang_switch.dart | 168 ++++++ .../two_gang_switch/two_gang_switch.dart | 115 ++++ lib/pages/routiens/widgets/dragable_card.dart | 11 +- lib/pages/routiens/widgets/if_container.dart | 23 +- .../routiens/widgets/routine_devices.dart | 9 +- .../widgets/scenes_and_automations.dart | 7 +- .../routiens/widgets/then_container.dart | 21 +- .../Runner/Logs/Build/.dat.nosync1585.W9c579 | 95 +++ ...-9521-4D0C-959C-43A07F62CC12.xcactivitylog | Bin 0 -> 217 bytes ...-C79D-4D4B-891A-12C476DFCB10.xcactivitylog | Bin 0 -> 209 bytes .../Runner/Logs/Build/LogStoreManifest.plist | 53 ++ .../Runner/Logs/Launch/LogStoreManifest.plist | 10 + .../Logs/Localization/LogStoreManifest.plist | 10 + .../Logs/Package/LogStoreManifest.plist | 10 + .../Runner/Logs/Test/LogStoreManifest.plist | 10 + macos/DerivedData/Runner/info.plist | 10 + pubspec.yaml | 1 + 27 files changed, 1739 insertions(+), 188 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart create mode 100644 lib/pages/routiens/helper/one_gang_switch_helper.dart create mode 100644 lib/pages/routiens/helper/three_gang_switch_helper.dart create mode 100644 lib/pages/routiens/helper/two_gang_switch_helper.dart create mode 100644 lib/pages/routiens/models/gang_switches/base_switch_function.dart create mode 100644 lib/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart create mode 100644 lib/pages/routiens/models/gang_switches/switch_operational_value.dart create mode 100644 lib/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart create mode 100644 lib/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart create mode 100644 macos/DerivedData/Runner/Logs/Build/.dat.nosync1585.W9c579 create mode 100644 macos/DerivedData/Runner/Logs/Build/DEC061F9-9521-4D0C-959C-43A07F62CC12.xcactivitylog create mode 100644 macos/DerivedData/Runner/Logs/Build/FB42CDDD-C79D-4D4B-891A-12C476DFCB10.xcactivitylog create mode 100644 macos/DerivedData/Runner/Logs/Build/LogStoreManifest.plist create mode 100644 macos/DerivedData/Runner/Logs/Launch/LogStoreManifest.plist create mode 100644 macos/DerivedData/Runner/Logs/Localization/LogStoreManifest.plist create mode 100644 macos/DerivedData/Runner/Logs/Package/LogStoreManifest.plist create mode 100644 macos/DerivedData/Runner/Logs/Test/LogStoreManifest.plist create mode 100644 macos/DerivedData/Runner/info.plist diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..b87628bd --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "automations" + ] +} \ No newline at end of file diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index b37ff36c..367a4fe8 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -4,6 +4,9 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart' import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart'; import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/enum/device_types.dart'; @@ -71,7 +74,6 @@ class AllDevicesModel { int? batteryLevel; String? productName; List? spaces; - List? _deviceFunctions; AllDevicesModel({ this.room, @@ -104,8 +106,12 @@ class AllDevicesModel { this.spaces, }); AllDevicesModel.fromJson(Map json) { - room = (json['room'] != null && (json['room'] is Map)) ? DevicesModelRoom.fromJson(json['room']) : null; - unit = (json['unit'] != null && (json['unit'] is Map)) ? DevicesModelUnit.fromJson(json['unit']) : null; + room = (json['room'] != null && (json['room'] is Map)) + ? DevicesModelRoom.fromJson(json['room']) + : null; + unit = (json['unit'] != null && (json['unit'] is Map)) + ? DevicesModelUnit.fromJson(json['unit']) + : null; community = (json['community'] != null && (json['community'] is Map)) ? DeviceCommunityModel.fromJson(json['community']) : null; @@ -134,7 +140,9 @@ class AllDevicesModel { batteryLevel = int.tryParse(json['battery']?.toString() ?? ''); productName = json['productName']?.toString(); if (json['spaces'] != null && json['spaces'] is List) { - spaces = (json['spaces'] as List).map((space) => DeviceSpaceModel.fromJson(space)).toList(); + spaces = (json['spaces'] as List) + .map((space) => DeviceSpaceModel.fromJson(space)) + .toList(); } } @@ -182,7 +190,8 @@ SOS String tempIcon = ''; if (type == DeviceType.LightBulb) { tempIcon = Assets.lightBulb; - } else if (type == DeviceType.CeilingSensor || type == DeviceType.WallSensor) { + } else if (type == DeviceType.CeilingSensor || + type == DeviceType.WallSensor) { tempIcon = Assets.sensors; } else if (type == DeviceType.AC) { tempIcon = Assets.ac; @@ -218,11 +227,11 @@ SOS return tempIcon; } - List get deviceFunctions { - _deviceFunctions ??= _getDeviceFunctions(); - return _deviceFunctions!; + List get functions { + return _getDeviceFunctions(); } + //! Functions for Devices Types List _getDeviceFunctions() { switch (productType) { case 'AC': @@ -234,7 +243,38 @@ SOS ChildLockFunction(deviceId: uuid ?? '', deviceName: name ?? ''), ]; - // other product types + case '1G': + return [ + OneGangSwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''), + OneGangCountdownFunction( + deviceId: uuid ?? '', deviceName: name ?? ''), + ]; + + case '2G': + return [ + TwoGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''), + TwoGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''), + TwoGangCountdown1Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + TwoGangCountdown2Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + ]; + + case '3G': + return [ + ThreeGangSwitch1Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangSwitch2Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangSwitch3Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangCountdown1Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangCountdown2Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangCountdown3Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + ]; default: return []; } diff --git a/lib/pages/routiens/helper/ac_helper.dart b/lib/pages/routiens/helper/ac_helper.dart index 6cdb415d..4c216a89 100644 --- a/lib/pages/routiens/helper/ac_helper.dart +++ b/lib/pages/routiens/helper/ac_helper.dart @@ -1,154 +1,39 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; -mixin ACHelper { - Future?> showACFunctionsDialog(BuildContext context, List> functions) { +class ACHelper { + static Future?> showACFunctionsDialog( + BuildContext context, + List> functions, + ) async { List acFunctions = functions.whereType().toList(); String? selectedFunction; - dynamic selectedValue; + dynamic selectedValue = 20; + String? selectedCondition = "=="; + List _selectedConditions = [false, true, false]; - return showDialog( + return showDialog?>( context: context, builder: (BuildContext context) { return StatefulBuilder( builder: (context, setState) { return AlertDialog( contentPadding: EdgeInsets.zero, - content: Container( - width: selectedFunction != null ? 600 : 360, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - ), - padding: const EdgeInsets.only(top: 20), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'AC Functions', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - fontWeight: FontWeight.bold, - ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 50), - child: Container( - height: 1, - width: double.infinity, - color: ColorsManager.greyColor, - ), - ), - Flexible( - child: Row( - children: [ - Expanded( - child: ListView.separated( - shrinkWrap: true, - itemCount: acFunctions.length, - separatorBuilder: (context, index) => Divider(), - itemBuilder: (context, index) { - final function = acFunctions[index]; - return ListTile( - leading: Image.asset(function.icon, width: 24, height: 24), - title: Text(function.operationName), - trailing: Icon(Icons.arrow_forward_ios), - onTap: () { - setState(() { - selectedFunction = function.code; - selectedValue = null; - }); - }, - ); - }, - ), - ), - if (selectedFunction != null) - Container( - width: 1, - color: ColorsManager.greyColor, - ), - if (selectedFunction != null) - Expanded( - child: ListView.separated( - shrinkWrap: true, - itemCount: acFunctions - .firstWhere((f) => f.code == selectedFunction) - .getOperationalValues() - .length, - separatorBuilder: (context, index) => Divider(), - itemBuilder: (context, index) { - final operationalValue = acFunctions.firstWhere((f) => f.code == selectedFunction) - ..getOperationalValues()[index]; - return ListTile( - leading: Image.asset(operationalValue.icon, width: 24, height: 24), - title: Text(operationalValue.getOperationalValues()[index].description), - trailing: Radio( - value: operationalValue.getOperationalValues()[index].value, - groupValue: selectedValue, - onChanged: (value) { - setState(() { - selectedValue = value; - }); - }, - ), - ); - }, - ), - ), - ], - ), - ), - Container( - height: 1, - width: double.infinity, - color: ColorsManager.greyColor, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - GestureDetector( - onTap: () { - Navigator.pop(context); - }, - child: Center( - child: Text( - 'Cancel', - style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: ColorsManager.greyColor), - ), - ), - ), - Container( - height: 50, - width: 1, - color: ColorsManager.greyColor, - ), - GestureDetector( - onTap: () { - // Handle the confirmation action here - Navigator.pop(context, { - 'function': selectedFunction, - 'value': selectedValue, - }); - }, - child: Center( - child: Text( - 'Confirm', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), - ), - ), - ), - ], - ), - ], - ), + content: _buildDialogContent( + context, + setState, + acFunctions, + selectedFunction, + selectedValue, + selectedCondition, + _selectedConditions, + (fn) => selectedFunction = fn, + (val) => selectedValue = val, + (cond) => selectedCondition = cond, ), ); }, @@ -157,34 +42,370 @@ mixin ACHelper { ); } - void handleACDeviceDrop(BuildContext context, Map data) { - final device = data['device'] as AllDevicesModel; - final acFunctions = device.deviceFunctions; - - showACFunctionsDialog(context, acFunctions).then((result) { - if (result != null) { - _addACDeviceToRoutine(context, data, result); - } - }); + /// Build dialog content for AC functions dialog + static Widget _buildDialogContent( + BuildContext context, + StateSetter setState, + List acFunctions, + String? selectedFunction, + dynamic selectedValue, + String? selectedCondition, + List selectedConditions, + Function(String?) onFunctionSelected, + Function(dynamic) onValueSelected, + Function(String?) onConditionSelected, + ) { + return Container( + width: selectedFunction != null ? 600 : 360, + height: 450, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildDialogHeader(context), + Flexible( + child: Row( + children: [ + _buildFunctionsList( + context, + setState, + acFunctions, + selectedFunction, + onFunctionSelected, + ), + if (selectedFunction != null) + _buildValueSelector( + context, + setState, + selectedFunction, + selectedValue, + selectedCondition, + selectedConditions, + onValueSelected, + onConditionSelected, + acFunctions, + ), + ], + ), + ), + _buildDialogFooter( + context, + selectedFunction, + selectedValue, + selectedCondition, + ), + ], + ), + ); } - void handleNonACDeviceDrop(BuildContext context, Map data) { - context.read().add(AddToThenContainer(data)); + /// Build header for AC functions dialog + static Widget _buildDialogHeader(BuildContext context) { + return Column( + children: [ + Text( + 'AC Condition', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontWeight.bold, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 50), + child: Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + ), + ], + ); } - void _addACDeviceToRoutine(BuildContext context, Map deviceData, Map functionData) { - final updatedData = { - ...deviceData, - 'function': functionData['function'], - 'value': functionData['value'], - }; - - context.read().add(AddToThenContainer(updatedData)); - - _logACFunctionSelection(functionData); + /// Build functions list for AC functions dialog + static Widget _buildFunctionsList( + BuildContext context, + StateSetter setState, + List acFunctions, + String? selectedFunction, + Function(String?) onFunctionSelected, + ) { + return Expanded( + child: ListView.separated( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: acFunctions.length, + separatorBuilder: (context, index) => const Divider( + color: ColorsManager.dividerColor, + ), + itemBuilder: (context, index) { + final function = acFunctions[index]; + return ListTile( + leading: SvgPicture.asset( + function.icon, + width: 24, + height: 24, + ), + title: Text( + function.operationName, + style: context.textTheme.bodyMedium, + ), + trailing: const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, + ), + onTap: () => setState(() => onFunctionSelected(function.code)), + ); + }, + ), + ); } - void _logACFunctionSelection(Map functionData) { - print('Selected AC function: ${functionData['function']}, Value: ${functionData['value']}'); + /// Build value selector for AC functions dialog + static Widget _buildValueSelector( + BuildContext context, + StateSetter setState, + String selectedFunction, + dynamic selectedValue, + String? selectedCondition, + List selectedConditions, + Function(dynamic) onValueSelected, + Function(String?) onConditionSelected, + List acFunctions, + ) { + if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') { + return Expanded( + child: _buildTemperatureSelector( + context, + setState, + selectedValue, + selectedCondition, + selectedConditions, + onValueSelected, + onConditionSelected, + ), + ); + } + + final selectedFn = + acFunctions.firstWhere((f) => f.code == selectedFunction); + final values = selectedFn.getOperationalValues(); + return Expanded( + child: _buildOperationalValuesList( + context, + setState, + values, + selectedValue, + onValueSelected, + ), + ); + } + + /// Build temperature selector for AC functions dialog + static Widget _buildTemperatureSelector( + BuildContext context, + StateSetter setState, + dynamic selectedValue, + String? selectedCondition, + List selectedConditions, + Function(dynamic) onValueSelected, + Function(String?) onConditionSelected, + ) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildConditionToggle( + context, + setState, + selectedConditions, + onConditionSelected, + ), + const SizedBox(height: 20), + _buildTemperatureDisplay(context, selectedValue), + const SizedBox(height: 20), + _buildTemperatureSlider( + context, + setState, + selectedValue, + onValueSelected, + ), + ], + ); + } + + /// Build condition toggle for AC functions dialog + static Widget _buildConditionToggle( + BuildContext context, + StateSetter setState, + List selectedConditions, + Function(String?) onConditionSelected, + ) { + return ToggleButtons( + onPressed: (int index) { + setState(() { + for (int i = 0; i < selectedConditions.length; i++) { + selectedConditions[i] = i == index; + } + onConditionSelected(index == 0 + ? "<" + : index == 1 + ? "==" + : ">"); + }); + }, + borderRadius: const BorderRadius.all(Radius.circular(8)), + selectedBorderColor: ColorsManager.primaryColorWithOpacity, + selectedColor: Colors.white, + fillColor: ColorsManager.primaryColorWithOpacity, + color: ColorsManager.primaryColorWithOpacity, + constraints: const BoxConstraints( + minHeight: 40.0, + minWidth: 40.0, + ), + isSelected: selectedConditions, + children: const [Text("<"), Text("="), Text(">")], + ); + } + + /// Build temperature display for AC functions dialog + static Widget _buildTemperatureDisplay( + BuildContext context, dynamic selectedValue) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + '${selectedValue ?? 20}°C', + style: context.textTheme.headlineMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), + ), + ); + } + + static Widget _buildTemperatureSlider( + BuildContext context, + StateSetter setState, + dynamic selectedValue, + Function(dynamic) onValueSelected, + ) { + final currentValue = selectedValue is int ? selectedValue.toDouble() : 20.0; + return Slider( + value: currentValue, + min: 16, + max: 30, + divisions: 14, + label: '${currentValue.toInt()}°C', + onChanged: (value) { + setState(() => onValueSelected(value.toInt())); + }, + ); + } + + static Widget _buildOperationalValuesList( + BuildContext context, + StateSetter setState, + List values, + dynamic selectedValue, + Function(dynamic) onValueSelected, + ) { + return ListView.builder( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Radio( + value: value.value, + groupValue: selectedValue, + onChanged: (newValue) { + setState(() => onValueSelected(newValue)); + }, + ), + ); + }, + ); + } + + static Widget _buildDialogFooter( + BuildContext context, + String? selectedFunction, + dynamic selectedValue, + String? selectedCondition, + ) { + return Container( + decoration: const BoxDecoration( + border: Border( + top: BorderSide( + color: ColorsManager.greyColor, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _buildFooterButton( + context, + 'Cancel', + selectedFunction != null ? 299 : 179, + () => Navigator.pop(context), + ), + _buildFooterButton( + context, + 'Confirm', + selectedFunction != null ? 299 : 179, + selectedFunction != null && selectedValue != null + ? () => Navigator.pop(context, { + 'function': selectedFunction, + 'value': selectedValue, + 'condition': selectedCondition ?? "==", + }) + : null, + ), + ], + ), + ); + } + + static Widget _buildFooterButton( + BuildContext context, + String text, + double width, + VoidCallback? onTap, + ) { + return GestureDetector( + onTap: onTap, + child: SizedBox( + height: 50, + width: width, + child: Center( + child: Text( + text, + style: context.textTheme.bodyMedium!.copyWith( + color: onTap != null + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.textGray, + ), + ), + ), + ), + ); } } diff --git a/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart new file mode 100644 index 00000000..0046db8a --- /dev/null +++ b/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/routiens/helper/ac_helper.dart'; +import 'package:syncrow_web/pages/routiens/helper/one_gang_switch_helper.dart'; +import 'package:syncrow_web/pages/routiens/helper/three_gang_switch_helper.dart'; +import 'package:syncrow_web/pages/routiens/helper/two_gang_switch_helper.dart'; +import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; + +class DeviceDialogHelper { + static Future?> showDeviceDialog( + BuildContext context, + Map data, + ) async { + final functions = data['functions'] as List; + + try { + final result = await _getDialogForDeviceType( + context, + data['productType'], + functions, + ); + + if (result != null) { + return {...data, ...result}; + } + } catch (e) { + debugPrint('Error: $e'); + } + + return null; + } + + static Future?> _getDialogForDeviceType( + BuildContext context, + String productType, + List functions, + ) async { + switch (productType) { + case 'AC': + return ACHelper.showACFunctionsDialog(context, functions); + case '1G': + return OneGangSwitchHelper.showSwitchFunctionsDialog( + context, functions); + case '2G': + return TwoGangSwitchHelper.showSwitchFunctionsDialog( + context, functions); + case '3G': + return ThreeGangSwitchHelper.showSwitchFunctionsDialog( + context, functions); + default: + return null; + } + } +} diff --git a/lib/pages/routiens/helper/one_gang_switch_helper.dart b/lib/pages/routiens/helper/one_gang_switch_helper.dart new file mode 100644 index 00000000..30f25a96 --- /dev/null +++ b/lib/pages/routiens/helper/one_gang_switch_helper.dart @@ -0,0 +1,206 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class OneGangSwitchHelper { + static Future?> showSwitchFunctionsDialog( + BuildContext context, List> functions) async { + List> switchFunctions = functions + .where( + (f) => f is OneGangSwitchFunction || f is OneGangCountdownFunction) + .toList(); + String? selectedFunction; + dynamic selectedValue; + + return showDialog?>( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: Container( + width: selectedFunction != null ? 600 : 360, + height: 450, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '1 Gang Light Switch Condition', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontWeight.bold, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 15, horizontal: 50), + child: Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + ), + Flexible( + child: Row( + children: [ + Expanded( + child: ListView.separated( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: switchFunctions.length, + separatorBuilder: (context, index) => + const Divider( + color: ColorsManager.dividerColor, + ), + itemBuilder: (context, index) { + final function = switchFunctions[index]; + return ListTile( + leading: SvgPicture.asset( + function.icon, + width: 24, + height: 24, + ), + title: Text( + function.operationName, + style: context.textTheme.bodyMedium, + ), + trailing: const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, + ), + onTap: () { + setState(() { + selectedFunction = function.code; + selectedValue = null; + }); + }, + ); + }, + ), + ), + if (selectedFunction != null) + Expanded( + child: Builder( + builder: (context) { + final selectedFn = switchFunctions.firstWhere( + (f) => f.code == selectedFunction) + as BaseSwitchFunction; + final values = + selectedFn.getOperationalValues(); + return ListView.builder( + shrinkWrap: false, + physics: + const AlwaysScrollableScrollPhysics(), + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Radio( + value: value.value, + groupValue: selectedValue, + onChanged: (newValue) { + setState(() { + selectedValue = newValue; + }); + }, + ), + ); + }, + ); + }, + ), + ), + ], + ), + ), + Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Container( + height: 50, + width: selectedFunction != null ? 299 : 179, + decoration: const BoxDecoration( + border: Border( + right: + BorderSide(color: ColorsManager.greyColor), + ), + ), + child: Center( + child: Text( + 'Cancel', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: ColorsManager.greyColor), + ), + ), + ), + ), + GestureDetector( + onTap: () { + if (selectedFunction != null && + selectedValue != null) { + Navigator.pop(context, { + 'function': selectedFunction, + 'value': selectedValue, + }); + } + }, + child: SizedBox( + height: 50, + width: selectedFunction != null ? 299 : 179, + child: Center( + child: Text( + 'Confirm', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: + ColorsManager.primaryColorWithOpacity, + ), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/pages/routiens/helper/three_gang_switch_helper.dart b/lib/pages/routiens/helper/three_gang_switch_helper.dart new file mode 100644 index 00000000..89aa2d95 --- /dev/null +++ b/lib/pages/routiens/helper/three_gang_switch_helper.dart @@ -0,0 +1,211 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class ThreeGangSwitchHelper { + static Future?> showSwitchFunctionsDialog( + BuildContext context, List> functions) async { + List> switchFunctions = functions + .where((f) => + f is ThreeGangSwitch1Function || + f is ThreeGangSwitch2Function || + f is ThreeGangSwitch3Function || + f is ThreeGangCountdown1Function || + f is ThreeGangCountdown2Function || + f is ThreeGangCountdown3Function) + .toList(); + String? selectedFunction; + dynamic selectedValue; + + return showDialog?>( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: Container( + width: selectedFunction != null ? 600 : 360, + height: 450, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '3 Gangs Light Switch Condition', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontWeight.bold, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 15, horizontal: 50), + child: Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + ), + Flexible( + child: Row( + children: [ + Expanded( + child: ListView.separated( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: switchFunctions.length, + separatorBuilder: (context, index) => + const Divider( + color: ColorsManager.dividerColor, + ), + itemBuilder: (context, index) { + final function = switchFunctions[index]; + return ListTile( + leading: SvgPicture.asset( + function.icon, + width: 24, + height: 24, + ), + title: Text( + function.operationName, + style: context.textTheme.bodyMedium, + ), + trailing: const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, + ), + onTap: () { + setState(() { + selectedFunction = function.code; + selectedValue = null; + }); + }, + ); + }, + ), + ), + if (selectedFunction != null) + Expanded( + child: Builder( + builder: (context) { + final selectedFn = switchFunctions.firstWhere( + (f) => f.code == selectedFunction) + as BaseSwitchFunction; + final values = + selectedFn.getOperationalValues(); + return ListView.builder( + shrinkWrap: false, + physics: + const AlwaysScrollableScrollPhysics(), + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Radio( + value: value.value, + groupValue: selectedValue, + onChanged: (newValue) { + setState(() { + selectedValue = newValue; + }); + }, + ), + ); + }, + ); + }, + ), + ), + ], + ), + ), + Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Container( + height: 50, + width: selectedFunction != null ? 299 : 179, + decoration: const BoxDecoration( + border: Border( + right: + BorderSide(color: ColorsManager.greyColor), + ), + ), + child: Center( + child: Text( + 'Cancel', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: ColorsManager.greyColor), + ), + ), + ), + ), + GestureDetector( + onTap: () { + if (selectedFunction != null && + selectedValue != null) { + Navigator.pop(context, { + 'function': selectedFunction, + 'value': selectedValue, + }); + } + }, + child: SizedBox( + height: 50, + width: selectedFunction != null ? 299 : 179, + child: Center( + child: Text( + 'Confirm', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: + ColorsManager.primaryColorWithOpacity, + ), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/pages/routiens/helper/two_gang_switch_helper.dart b/lib/pages/routiens/helper/two_gang_switch_helper.dart new file mode 100644 index 00000000..1d271ac7 --- /dev/null +++ b/lib/pages/routiens/helper/two_gang_switch_helper.dart @@ -0,0 +1,209 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class TwoGangSwitchHelper { + static Future?> showSwitchFunctionsDialog( + BuildContext context, List> functions) async { + List> switchFunctions = functions + .where((f) => + f is TwoGangSwitch1Function || + f is TwoGangSwitch2Function || + f is TwoGangCountdown1Function || + f is TwoGangCountdown2Function) + .toList(); + String? selectedFunction; + dynamic selectedValue; + + return showDialog?>( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: Container( + width: selectedFunction != null ? 600 : 360, + height: 450, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '2 Gangs Light Switch Condition', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontWeight.bold, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 15, horizontal: 50), + child: Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + ), + Flexible( + child: Row( + children: [ + Expanded( + child: ListView.separated( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: switchFunctions.length, + separatorBuilder: (context, index) => + const Divider( + color: ColorsManager.dividerColor, + ), + itemBuilder: (context, index) { + final function = switchFunctions[index]; + return ListTile( + leading: SvgPicture.asset( + function.icon, + width: 24, + height: 24, + ), + title: Text( + function.operationName, + style: context.textTheme.bodyMedium, + ), + trailing: const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, + ), + onTap: () { + setState(() { + selectedFunction = function.code; + selectedValue = null; + }); + }, + ); + }, + ), + ), + if (selectedFunction != null) + Expanded( + child: Builder( + builder: (context) { + final selectedFn = switchFunctions.firstWhere( + (f) => f.code == selectedFunction) + as BaseSwitchFunction; + final values = + selectedFn.getOperationalValues(); + return ListView.builder( + shrinkWrap: false, + physics: + const AlwaysScrollableScrollPhysics(), + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Radio( + value: value.value, + groupValue: selectedValue, + onChanged: (newValue) { + setState(() { + selectedValue = newValue; + }); + }, + ), + ); + }, + ); + }, + ), + ), + ], + ), + ), + Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Container( + height: 50, + width: selectedFunction != null ? 299 : 179, + decoration: const BoxDecoration( + border: Border( + right: + BorderSide(color: ColorsManager.greyColor), + ), + ), + child: Center( + child: Text( + 'Cancel', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: ColorsManager.greyColor), + ), + ), + ), + ), + GestureDetector( + onTap: () { + if (selectedFunction != null && + selectedValue != null) { + Navigator.pop(context, { + 'function': selectedFunction, + 'value': selectedValue, + }); + } + }, + child: SizedBox( + height: 50, + width: selectedFunction != null ? 299 : 179, + child: Center( + child: Text( + 'Confirm', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: + ColorsManager.primaryColorWithOpacity, + ), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/pages/routiens/models/gang_switches/base_switch_function.dart b/lib/pages/routiens/models/gang_switches/base_switch_function.dart new file mode 100644 index 00000000..3c89bebd --- /dev/null +++ b/lib/pages/routiens/models/gang_switches/base_switch_function.dart @@ -0,0 +1,17 @@ +import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; + +abstract class BaseSwitchFunction extends DeviceFunction { + BaseSwitchFunction({ + required super.deviceId, + required super.deviceName, + required super.code, + required super.operationName, + required super.icon, + }); + + @override + bool execute(bool currentStatus, dynamic newValue); + + List getOperationalValues(); +} diff --git a/lib/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart b/lib/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart new file mode 100644 index 00000000..32bc436e --- /dev/null +++ b/lib/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart @@ -0,0 +1,57 @@ +import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class OneGangSwitchFunction extends BaseSwitchFunction { + OneGangSwitchFunction({required super.deviceId, required super.deviceName}) + : super( + code: 'switch_1', + operationName: 'Light Switch', + icon: Assets.assetsAcPower, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SwitchOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; +} + +class OneGangCountdownFunction extends BaseSwitchFunction { + OneGangCountdownFunction({required super.deviceId, required super.deviceName}) + : super( + code: 'countdown_1', + operationName: 'Light Countdown', + icon: Assets.assetsLightCountdown, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: '', + description: "sec", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ), + ]; +} diff --git a/lib/pages/routiens/models/gang_switches/switch_operational_value.dart b/lib/pages/routiens/models/gang_switches/switch_operational_value.dart new file mode 100644 index 00000000..eabd4a35 --- /dev/null +++ b/lib/pages/routiens/models/gang_switches/switch_operational_value.dart @@ -0,0 +1,17 @@ +class SwitchOperationalValue { + final String icon; + final String description; + final dynamic value; + final double? minValue; + final double? maxValue; + final double? stepValue; + + SwitchOperationalValue({ + required this.icon, + required this.value, + this.description = '', + this.minValue, + this.maxValue, + this.stepValue, + }); +} diff --git a/lib/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart b/lib/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart new file mode 100644 index 00000000..8ff186ef --- /dev/null +++ b/lib/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart @@ -0,0 +1,168 @@ +import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class ThreeGangSwitch1Function extends BaseSwitchFunction { + ThreeGangSwitch1Function({required super.deviceId, required super.deviceName}) + : super( + code: 'switch_1', + operationName: 'Light 1 Switch', + icon: Assets.assetsAcPower, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SwitchOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; +} + +class ThreeGangCountdown1Function extends BaseSwitchFunction { + ThreeGangCountdown1Function( + {required super.deviceId, required super.deviceName}) + : super( + code: 'countdown_1', + operationName: 'Light 1 Countdown', + icon: Assets.assetsLightCountdown, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: '', + description: "sec", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ), + ]; +} + +class ThreeGangSwitch2Function extends BaseSwitchFunction { + ThreeGangSwitch2Function({required super.deviceId, required super.deviceName}) + : super( + code: 'switch_2', + operationName: 'Light 2 Switch', + icon: Assets.assetsAcPower, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SwitchOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; +} + +class ThreeGangCountdown2Function extends BaseSwitchFunction { + ThreeGangCountdown2Function( + {required super.deviceId, required super.deviceName}) + : super( + code: 'countdown_2', + operationName: 'Light 2 Countdown', + icon: Assets.assetsLightCountdown, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: '', + description: "sec", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ), + ]; +} + +class ThreeGangSwitch3Function extends BaseSwitchFunction { + ThreeGangSwitch3Function({required super.deviceId, required super.deviceName}) + : super( + code: 'switch_3', + operationName: 'Light 3 Switch', + icon: Assets.assetsAcPower, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SwitchOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; +} + +class ThreeGangCountdown3Function extends BaseSwitchFunction { + ThreeGangCountdown3Function( + {required super.deviceId, required super.deviceName}) + : super( + code: 'countdown_3', + operationName: 'Light 3 Countdown', + icon: Assets.assetsLightCountdown, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: '', + description: "sec", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ), + ]; +} diff --git a/lib/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart b/lib/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart new file mode 100644 index 00000000..7408d9d0 --- /dev/null +++ b/lib/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart @@ -0,0 +1,115 @@ +import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class TwoGangSwitch1Function extends BaseSwitchFunction { + TwoGangSwitch1Function({required super.deviceId, required super.deviceName}) + : super( + code: 'switch_1', + operationName: 'Light 1 Switch', + icon: Assets.assetsAcPower, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SwitchOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; +} + +class TwoGangSwitch2Function extends BaseSwitchFunction { + TwoGangSwitch2Function({required String deviceId, required String deviceName}) + : super( + deviceId: deviceId, + deviceName: deviceName, + code: 'switch_2', + operationName: 'Light 2 Switch', + icon: Assets.assetsAcPower, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SwitchOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; +} + +class TwoGangCountdown1Function extends BaseSwitchFunction { + TwoGangCountdown1Function( + {required super.deviceId, required super.deviceName}) + : super( + code: 'countdown_1', + operationName: 'Light 1 Countdown', + icon: Assets.assetsLightCountdown, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: '', + description: "sec", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ), + ]; +} + +class TwoGangCountdown2Function extends BaseSwitchFunction { + TwoGangCountdown2Function( + {required super.deviceId, required super.deviceName}) + : super( + code: 'countdown_2', + operationName: 'Light 2 Countdown', + icon: Assets.assetsLightCountdown, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: '', + description: "sec", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ), + ]; +} diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index 2e7d1a0e..8ed4afff 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -11,6 +11,7 @@ class DraggableCard extends StatelessWidget { this.titleColor, this.isDragged = false, this.isDisabled = false, + this.deviceData, }); final String imagePath; @@ -18,11 +19,17 @@ class DraggableCard extends StatelessWidget { final Color? titleColor; final bool isDragged; final bool isDisabled; + final Map? deviceData; @override Widget build(BuildContext context) { - Widget card = Draggable>( - data: {'key': UniqueKey().toString(), 'imagePath': imagePath, 'title': title}, + Widget card = Draggable>( + data: deviceData ?? + { + 'key': UniqueKey().toString(), + 'imagePath': imagePath, + 'title': title, + }, feedback: Transform.rotate( angle: -0.1, child: _buildCardContent(context), diff --git a/lib/pages/routiens/widgets/if_container.dart b/lib/pages/routiens/widgets/if_container.dart index 069f77b4..ba0d67ab 100644 --- a/lib/pages/routiens/widgets/if_container.dart +++ b/lib/pages/routiens/widgets/if_container.dart @@ -1,18 +1,17 @@ -// lib/pages/routiens/widgets/if_container.dart - import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; class IfContainer extends StatelessWidget { - const IfContainer({Key? key}) : super(key: key); + const IfContainer({super.key}); @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - return DragTarget>( + return DragTarget>( builder: (context, candidateData, rejectedData) { return Container( width: double.infinity, @@ -20,7 +19,9 @@ class IfContainer extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('IF', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const Text('IF', + style: + TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 16), Wrap( spacing: 8, @@ -37,8 +38,16 @@ class IfContainer extends StatelessWidget { ), ); }, - onAccept: (data) { - context.read().add(AddToIfContainer(data)); + onWillAccept: (data) => data != null, + onAccept: (data) async { + final result = + await DeviceDialogHelper.showDeviceDialog(context, data); + if (result != null) { + context.read().add(AddToIfContainer(result)); + } else if (!['AC', '1G', '2G', '3G'] + .contains(data['productType'])) { + context.read().add(AddToIfContainer(data)); + } }, ); }, diff --git a/lib/pages/routiens/widgets/routine_devices.dart b/lib/pages/routiens/widgets/routine_devices.dart index 12d8492e..c845af19 100644 --- a/lib/pages/routiens/widgets/routine_devices.dart +++ b/lib/pages/routiens/widgets/routine_devices.dart @@ -29,11 +29,18 @@ class RoutineDevices extends StatelessWidget { spacing: 10, runSpacing: 10, children: deviceList.asMap().entries.map((entry) { - final index = entry.key; final device = entry.value; return DraggableCard( imagePath: device.getDefaultIcon(device.productType), title: device.name ?? '', + deviceData: { + 'key': UniqueKey().toString(), + 'imagePath': device.getDefaultIcon(device.productType), + 'title': device.name ?? '', + 'deviceId': device.uuid, + 'productType': device.productType, + 'functions': device.functions, + }, ); }).toList(), ); diff --git a/lib/pages/routiens/widgets/scenes_and_automations.dart b/lib/pages/routiens/widgets/scenes_and_automations.dart index 7c90305b..b455b2f5 100644 --- a/lib/pages/routiens/widgets/scenes_and_automations.dart +++ b/lib/pages/routiens/widgets/scenes_and_automations.dart @@ -14,10 +14,10 @@ class ScenesAndAutomations extends StatelessWidget { return BlocProvider( create: (context) => RoutineBloc() ..add( - LoadScenes(spaceId), + const LoadScenes(spaceId), ) ..add( - LoadAutomation(spaceId), + const LoadAutomation(spaceId), ), child: BlocBuilder( builder: (context, state) { @@ -27,11 +27,10 @@ class ScenesAndAutomations extends StatelessWidget { spacing: 10, runSpacing: 10, children: scenes.asMap().entries.map((entry) { - final index = entry.key; final scene = entry.value; return DraggableCard( imagePath: Assets.logo, - title: scene.name ?? '', + title: scene.name, ); }).toList(), ); diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index bc72bd9f..9bd7fd00 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -3,16 +3,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; class ThenContainer extends StatelessWidget { - const ThenContainer({Key? key}) : super(key: key); + const ThenContainer({super.key}); @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - return DragTarget>( + return DragTarget>( builder: (context, candidateData, rejectedData) { return Container( padding: const EdgeInsets.all(16), @@ -20,7 +21,9 @@ class ThenContainer extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('THEN', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const Text('THEN', + style: + TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 16), Wrap( spacing: 8, @@ -37,8 +40,16 @@ class ThenContainer extends StatelessWidget { ), ); }, - onAccept: (data) { - context.read().add(AddToThenContainer(data)); + onWillAccept: (data) => data != null, + onAccept: (data) async { + final result = + await DeviceDialogHelper.showDeviceDialog(context, data); + if (result != null) { + context.read().add(AddToThenContainer(result)); + } else if (!['AC', '1G', '2G', '3G'] + .contains(data['productType'])) { + context.read().add(AddToThenContainer(data)); + } }, ); }, diff --git a/macos/DerivedData/Runner/Logs/Build/.dat.nosync1585.W9c579 b/macos/DerivedData/Runner/Logs/Build/.dat.nosync1585.W9c579 new file mode 100644 index 00000000..309af193 --- /dev/null +++ b/macos/DerivedData/Runner/Logs/Build/.dat.nosync1585.W9c579 @@ -0,0 +1,95 @@ + + + + + logFormatVersion + 11 + logs + + DEC061F9-9521-4D0C-959C-43A07F62CC12 + + className + IDECommandLineBuildLog + documentTypeString + <nil> + domainType + Xcode.IDEActivityLogDomainType.BuildLog + fileName + DEC061F9-9521-4D0C-959C-43A07F62CC12.xcactivitylog + hasPrimaryLog + + primaryObservable + + highLevelStatus + S + totalNumberOfAnalyzerIssues + 0 + totalNumberOfErrors + 0 + totalNumberOfTestFailures + 0 + totalNumberOfWarnings + 0 + + schemeIdentifier-containerName + Runner project + schemeIdentifier-schemeName + Flutter Assemble + schemeIdentifier-sharedScheme + 1 + signature + Cleaning workspace Runner with scheme Flutter Assemble + timeStartedRecording + 752000674.27645695 + timeStoppedRecording + 752000674.42918503 + title + Cleaning workspace Runner with scheme Flutter Assemble + uniqueIdentifier + DEC061F9-9521-4D0C-959C-43A07F62CC12 + + FB42CDDD-C79D-4D4B-891A-12C476DFCB10 + + className + IDECommandLineBuildLog + documentTypeString + <nil> + domainType + Xcode.IDEActivityLogDomainType.BuildLog + fileName + FB42CDDD-C79D-4D4B-891A-12C476DFCB10.xcactivitylog + hasPrimaryLog + + primaryObservable + + highLevelStatus + S + totalNumberOfAnalyzerIssues + 0 + totalNumberOfErrors + 0 + totalNumberOfTestFailures + 0 + totalNumberOfWarnings + 0 + + schemeIdentifier-containerName + Runner project + schemeIdentifier-schemeName + Runner + schemeIdentifier-sharedScheme + 1 + signature + Cleaning workspace Runner with scheme Runner + timeStartedRecording + 752000674.90370798 + timeStoppedRecording + 752000675.05962098 + title + Cleaning workspace Runner with scheme Runner + uniqueIdentifier + FB42CDDD-C79D-4D4B-891A-12C476DFCB10 + + + + diff --git a/macos/DerivedData/Runner/Logs/Build/DEC061F9-9521-4D0C-959C-43A07F62CC12.xcactivitylog b/macos/DerivedData/Runner/Logs/Build/DEC061F9-9521-4D0C-959C-43A07F62CC12.xcactivitylog new file mode 100644 index 0000000000000000000000000000000000000000..c811e6cbcebca9a7a43e017168389b0481562c19 GIT binary patch literal 217 zcmV;~04Dz*iwFP!000006P-`ZO2jZ2e3j55c*&A4Nu#-0Qwu^*>cNvxldrqMCRsMA z%f7u45j@LWhWUY+=V@dBE$3H{!-Gj7%+XCg;{E1VH>Ew?u~z8j)h36#8tHBEpT1nG zHKZ{6_;NYo?l;}Z#;iC;ANS;(P9OO=TO?0gjCdkvUk)VO0|q4Hx|LG3K4%Q?EB?E< zfZ|+qAQz>rlJIt`aS`f|Kq+PIH!2aTsV|v=QB&P + + + + logFormatVersion + 11 + logs + + DEC061F9-9521-4D0C-959C-43A07F62CC12 + + className + IDECommandLineBuildLog + documentTypeString + <nil> + domainType + Xcode.IDEActivityLogDomainType.BuildLog + fileName + DEC061F9-9521-4D0C-959C-43A07F62CC12.xcactivitylog + hasPrimaryLog + + primaryObservable + + highLevelStatus + S + totalNumberOfAnalyzerIssues + 0 + totalNumberOfErrors + 0 + totalNumberOfTestFailures + 0 + totalNumberOfWarnings + 0 + + schemeIdentifier-containerName + Runner project + schemeIdentifier-schemeName + Flutter Assemble + schemeIdentifier-sharedScheme + 1 + signature + Cleaning workspace Runner with scheme Flutter Assemble + timeStartedRecording + 752000674.27645695 + timeStoppedRecording + 752000674.42918503 + title + Cleaning workspace Runner with scheme Flutter Assemble + uniqueIdentifier + DEC061F9-9521-4D0C-959C-43A07F62CC12 + + + + diff --git a/macos/DerivedData/Runner/Logs/Launch/LogStoreManifest.plist b/macos/DerivedData/Runner/Logs/Launch/LogStoreManifest.plist new file mode 100644 index 00000000..f38de442 --- /dev/null +++ b/macos/DerivedData/Runner/Logs/Launch/LogStoreManifest.plist @@ -0,0 +1,10 @@ + + + + + logFormatVersion + 11 + logs + + + diff --git a/macos/DerivedData/Runner/Logs/Localization/LogStoreManifest.plist b/macos/DerivedData/Runner/Logs/Localization/LogStoreManifest.plist new file mode 100644 index 00000000..f38de442 --- /dev/null +++ b/macos/DerivedData/Runner/Logs/Localization/LogStoreManifest.plist @@ -0,0 +1,10 @@ + + + + + logFormatVersion + 11 + logs + + + diff --git a/macos/DerivedData/Runner/Logs/Package/LogStoreManifest.plist b/macos/DerivedData/Runner/Logs/Package/LogStoreManifest.plist new file mode 100644 index 00000000..f38de442 --- /dev/null +++ b/macos/DerivedData/Runner/Logs/Package/LogStoreManifest.plist @@ -0,0 +1,10 @@ + + + + + logFormatVersion + 11 + logs + + + diff --git a/macos/DerivedData/Runner/Logs/Test/LogStoreManifest.plist b/macos/DerivedData/Runner/Logs/Test/LogStoreManifest.plist new file mode 100644 index 00000000..f38de442 --- /dev/null +++ b/macos/DerivedData/Runner/Logs/Test/LogStoreManifest.plist @@ -0,0 +1,10 @@ + + + + + logFormatVersion + 11 + logs + + + diff --git a/macos/DerivedData/Runner/info.plist b/macos/DerivedData/Runner/info.plist new file mode 100644 index 00000000..3594c152 --- /dev/null +++ b/macos/DerivedData/Runner/info.plist @@ -0,0 +1,10 @@ + + + + + LastAccessedDate + 2024-10-30T17:04:35Z + WorkspacePath + /Users/akmz/Developer/web/syncrow-web/web/macos/Runner.xcworkspace + + diff --git a/pubspec.yaml b/pubspec.yaml index 9e35e58f..8dc6b6bc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -76,6 +76,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - assets/icons/automation_functions/ + - assets/icons/functions_icons/ - assets/icons/routine/ - assets/icons/ - assets/images/ From 1d6673b5b0c11b0b870d94b0e62a8e676df961eb Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Fri, 22 Nov 2024 16:55:30 +0300 Subject: [PATCH 07/40] adding fucntions and values to routine bloc --- lib/pages/routiens/bloc/routine_bloc.dart | 44 +- lib/pages/routiens/bloc/routine_event.dart | 16 + lib/pages/routiens/bloc/routine_state.dart | 5 + lib/pages/routiens/helper/ac_helper.dart | 562 ++++++------------ .../dialog_helper/device_dialog_helper.dart | 24 +- .../helper/one_gang_switch_helper.dart | 333 +++++++---- .../helper/three_gang_switch_helper.dart | 335 +++++++---- .../helper/two_gang_switch_helper.dart | 334 +++++++---- .../routiens/models/device_functions.dart | 44 ++ lib/pages/routiens/widgets/dragable_card.dart | 147 +++-- 10 files changed, 1094 insertions(+), 750 deletions(-) diff --git a/lib/pages/routiens/bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc.dart index dfbb824c..d69df552 100644 --- a/lib/pages/routiens/bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc.dart @@ -1,5 +1,6 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/routine_model.dart'; import 'package:syncrow_web/services/routines_api.dart'; @@ -14,27 +15,55 @@ class RoutineBloc extends Bloc { on(_onAddToThenContainer); on(_onLoadScenes); on(_onLoadAutomation); + on(_onAddFunction); + on(_onRemoveFunction); + on(_onClearFunctions); } void _onAddToIfContainer(AddToIfContainer event, Emitter emit) { if (!_isDuplicate(state.ifItems, event.item)) { - final updatedIfItems = List>.from(state.ifItems)..add(event.item); + final updatedIfItems = List>.from(state.ifItems) + ..add(event.item); emit(state.copyWith(ifItems: updatedIfItems)); } } - void _onAddToThenContainer(AddToThenContainer event, Emitter emit) { + void _onAddToThenContainer( + AddToThenContainer event, Emitter emit) { if (!_isDuplicate(state.thenItems, event.item)) { - final updatedThenItems = List>.from(state.thenItems)..add(event.item); + final updatedThenItems = List>.from(state.thenItems) + ..add(event.item); emit(state.copyWith(thenItems: updatedThenItems)); } } - bool _isDuplicate(List> items, Map newItem) { - return items.any((item) => item['imagePath'] == newItem['imagePath'] && item['title'] == newItem['title']); + void _onAddFunction(AddFunction event, Emitter emit) { + final functions = List.from(state.selectedFunctions); + functions.add(event.function); + emit(state.copyWith(selectedFunctions: functions)); } - Future _onLoadScenes(LoadScenes event, Emitter emit) async { + void _onRemoveFunction(RemoveFunction event, Emitter emit) { + final functions = List.from(state.selectedFunctions) + ..removeWhere((f) => + f.function == event.function.function && + f.value == event.function.value); + emit(state.copyWith(selectedFunctions: functions)); + } + + void _onClearFunctions(ClearFunctions event, Emitter emit) { + emit(state.copyWith(selectedFunctions: [])); + } + + bool _isDuplicate( + List> items, Map newItem) { + return items.any((item) => + item['imagePath'] == newItem['imagePath'] && + item['title'] == newItem['title']); + } + + Future _onLoadScenes( + LoadScenes event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { @@ -51,7 +80,8 @@ class RoutineBloc extends Bloc { } } - Future _onLoadAutomation(LoadAutomation event, Emitter emit) async { + Future _onLoadAutomation( + LoadAutomation event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { diff --git a/lib/pages/routiens/bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_event.dart index aaee73c7..d3af8134 100644 --- a/lib/pages/routiens/bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_event.dart @@ -42,3 +42,19 @@ class LoadAutomation extends RoutineEvent { @override List get props => [unitId]; } + +class AddFunction extends RoutineEvent { + final DeviceFunctionData function; + const AddFunction(this.function); + @override + List get props => [function]; +} + +class RemoveFunction extends RoutineEvent { + final DeviceFunctionData function; + const RemoveFunction(this.function); + @override + List get props => [function]; +} + +class ClearFunctions extends RoutineEvent {} diff --git a/lib/pages/routiens/bloc/routine_state.dart b/lib/pages/routiens/bloc/routine_state.dart index 63d2e6f8..95aab557 100644 --- a/lib/pages/routiens/bloc/routine_state.dart +++ b/lib/pages/routiens/bloc/routine_state.dart @@ -6,6 +6,7 @@ class RoutineState extends Equatable { final List> availableCards; final List scenes; final List automations; + final List selectedFunctions; final bool isLoading; final String? errorMessage; @@ -15,6 +16,7 @@ class RoutineState extends Equatable { this.availableCards = const [], this.scenes = const [], this.automations = const [], + this.selectedFunctions = const [], this.isLoading = false, this.errorMessage, }); @@ -24,6 +26,7 @@ class RoutineState extends Equatable { List>? thenItems, List? scenes, List? automations, + List? selectedFunctions, bool? isLoading, String? errorMessage, }) { @@ -32,6 +35,7 @@ class RoutineState extends Equatable { thenItems: thenItems ?? this.thenItems, scenes: scenes ?? this.scenes, automations: automations ?? this.automations, + selectedFunctions: selectedFunctions ?? this.selectedFunctions, isLoading: isLoading ?? this.isLoading, errorMessage: errorMessage ?? this.errorMessage, ); @@ -43,6 +47,7 @@ class RoutineState extends Equatable { thenItems, scenes, automations, + selectedFunctions, isLoading, errorMessage, ]; diff --git a/lib/pages/routiens/helper/ac_helper.dart b/lib/pages/routiens/helper/ac_helper.dart index 4c216a89..b14acd3a 100644 --- a/lib/pages/routiens/helper/ac_helper.dart +++ b/lib/pages/routiens/helper/ac_helper.dart @@ -1,411 +1,219 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class ACHelper { - static Future?> showACFunctionsDialog( + static Future showACFunctionsDialog( BuildContext context, List> functions, ) async { List acFunctions = functions.whereType().toList(); - String? selectedFunction; - dynamic selectedValue = 20; - String? selectedCondition = "=="; - List _selectedConditions = [false, true, false]; + // Track multiple selections using a map + Map selectedValues = {}; + List selectedFunctions = []; - return showDialog?>( + await showDialog( context: context, builder: (BuildContext context) { - return StatefulBuilder( - builder: (context, setState) { - return AlertDialog( - contentPadding: EdgeInsets.zero, - content: _buildDialogContent( - context, - setState, - acFunctions, - selectedFunction, - selectedValue, - selectedCondition, - _selectedConditions, - (fn) => selectedFunction = fn, - (val) => selectedValue = val, - (cond) => selectedCondition = cond, - ), - ); - }, + return Dialog( + child: Container( + width: 600, + height: 450, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'AC Functions', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontWeight.bold, + ), + ), + Padding( + padding: + const EdgeInsets.symmetric(vertical: 15, horizontal: 50), + child: Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + ), + Expanded( + child: Row( + children: [ + Expanded( + child: ListView.separated( + itemCount: acFunctions.length, + separatorBuilder: (_, __) => const Divider( + color: ColorsManager.dividerColor, + ), + itemBuilder: (context, index) { + final function = acFunctions[index]; + final isSelected = + selectedValues.containsKey(function.code); + return ListTile( + tileColor: + isSelected ? Colors.grey.shade100 : null, + leading: SvgPicture.asset( + function.icon, + width: 24, + height: 24, + ), + title: Text( + function.operationName, + style: context.textTheme.bodyMedium, + ), + trailing: isSelected + ? Icon( + Icons.check_circle, + color: + ColorsManager.primaryColorWithOpacity, + size: 20, + ) + : const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, + ), + onTap: () { + if (isSelected) { + selectedValues.remove(function.code); + selectedFunctions.removeWhere( + (f) => f.function == function.code); + } + (context as Element).markNeedsBuild(); + }, + ); + }, + ), + ), + Expanded( + child: Builder( + builder: (context) { + final selectedFunction = acFunctions.firstWhere( + (f) => selectedValues.containsKey(f.code), + orElse: () => acFunctions.first, + ); + return _buildValueSelector( + context, + selectedFunction, + selectedValues[selectedFunction.code], + (value) { + selectedValues[selectedFunction.code] = value; + // Update or add the function data + final functionData = DeviceFunctionData( + entityId: selectedFunction.deviceId, + function: selectedFunction.code, + operationName: selectedFunction.operationName, + value: value, + valueDescription: _getValueDescription( + selectedFunction, value), + ); + + final existingIndex = + selectedFunctions.indexWhere((f) => + f.function == selectedFunction.code); + if (existingIndex != -1) { + selectedFunctions[existingIndex] = + functionData; + } else { + selectedFunctions.add(functionData); + } + + (context as Element).markNeedsBuild(); + }, + ); + }, + ), + ), + ], + ), + ), + Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text( + 'Cancel', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: ColorsManager.greyColor), + ), + ), + TextButton( + onPressed: selectedFunctions.isNotEmpty + ? () { + // Add all selected functions to the bloc + for (final function in selectedFunctions) { + context + .read() + .add(AddFunction(function)); + } + Navigator.pop(context, true); + } + : null, + child: Text( + 'Confirm', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), + ), + ), + ], + ), + ], + ), + ), ); }, ); } - /// Build dialog content for AC functions dialog - static Widget _buildDialogContent( + static Widget _buildValueSelector( BuildContext context, - StateSetter setState, - List acFunctions, - String? selectedFunction, + ACFunction function, dynamic selectedValue, - String? selectedCondition, - List selectedConditions, - Function(String?) onFunctionSelected, Function(dynamic) onValueSelected, - Function(String?) onConditionSelected, ) { + final values = function.getOperationalValues(); return Container( - width: selectedFunction != null ? 600 : 360, - height: 450, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - ), - padding: const EdgeInsets.only(top: 20), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - _buildDialogHeader(context), - Flexible( - child: Row( - children: [ - _buildFunctionsList( - context, - setState, - acFunctions, - selectedFunction, - onFunctionSelected, - ), - if (selectedFunction != null) - _buildValueSelector( - context, - setState, - selectedFunction, - selectedValue, - selectedCondition, - selectedConditions, - onValueSelected, - onConditionSelected, - acFunctions, - ), - ], - ), - ), - _buildDialogFooter( - context, - selectedFunction, - selectedValue, - selectedCondition, - ), - ], - ), - ); - } - - /// Build header for AC functions dialog - static Widget _buildDialogHeader(BuildContext context) { - return Column( - children: [ - Text( - 'AC Condition', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - fontWeight: FontWeight.bold, - ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 50), - child: Container( - height: 1, - width: double.infinity, - color: ColorsManager.greyColor, - ), - ), - ], - ); - } - - /// Build functions list for AC functions dialog - static Widget _buildFunctionsList( - BuildContext context, - StateSetter setState, - List acFunctions, - String? selectedFunction, - Function(String?) onFunctionSelected, - ) { - return Expanded( - child: ListView.separated( - shrinkWrap: false, - physics: const AlwaysScrollableScrollPhysics(), - itemCount: acFunctions.length, - separatorBuilder: (context, index) => const Divider( - color: ColorsManager.dividerColor, - ), + height: 200, + padding: const EdgeInsets.symmetric(horizontal: 20), + child: ListView.builder( + itemCount: values.length, itemBuilder: (context, index) { - final function = acFunctions[index]; - return ListTile( - leading: SvgPicture.asset( - function.icon, - width: 24, - height: 24, - ), - title: Text( - function.operationName, - style: context.textTheme.bodyMedium, - ), - trailing: const Icon( - Icons.arrow_forward_ios, - size: 16, - color: ColorsManager.textGray, - ), - onTap: () => setState(() => onFunctionSelected(function.code)), + final value = values[index]; + return RadioListTile( + value: value.value, + groupValue: selectedValue, + onChanged: onValueSelected, + title: Text(value.description), ); }, ), ); } - /// Build value selector for AC functions dialog - static Widget _buildValueSelector( - BuildContext context, - StateSetter setState, - String selectedFunction, - dynamic selectedValue, - String? selectedCondition, - List selectedConditions, - Function(dynamic) onValueSelected, - Function(String?) onConditionSelected, - List acFunctions, - ) { - if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') { - return Expanded( - child: _buildTemperatureSelector( - context, - setState, - selectedValue, - selectedCondition, - selectedConditions, - onValueSelected, - onConditionSelected, - ), - ); - } - - final selectedFn = - acFunctions.firstWhere((f) => f.code == selectedFunction); - final values = selectedFn.getOperationalValues(); - return Expanded( - child: _buildOperationalValuesList( - context, - setState, - values, - selectedValue, - onValueSelected, - ), - ); - } - - /// Build temperature selector for AC functions dialog - static Widget _buildTemperatureSelector( - BuildContext context, - StateSetter setState, - dynamic selectedValue, - String? selectedCondition, - List selectedConditions, - Function(dynamic) onValueSelected, - Function(String?) onConditionSelected, - ) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _buildConditionToggle( - context, - setState, - selectedConditions, - onConditionSelected, - ), - const SizedBox(height: 20), - _buildTemperatureDisplay(context, selectedValue), - const SizedBox(height: 20), - _buildTemperatureSlider( - context, - setState, - selectedValue, - onValueSelected, - ), - ], - ); - } - - /// Build condition toggle for AC functions dialog - static Widget _buildConditionToggle( - BuildContext context, - StateSetter setState, - List selectedConditions, - Function(String?) onConditionSelected, - ) { - return ToggleButtons( - onPressed: (int index) { - setState(() { - for (int i = 0; i < selectedConditions.length; i++) { - selectedConditions[i] = i == index; - } - onConditionSelected(index == 0 - ? "<" - : index == 1 - ? "==" - : ">"); - }); - }, - borderRadius: const BorderRadius.all(Radius.circular(8)), - selectedBorderColor: ColorsManager.primaryColorWithOpacity, - selectedColor: Colors.white, - fillColor: ColorsManager.primaryColorWithOpacity, - color: ColorsManager.primaryColorWithOpacity, - constraints: const BoxConstraints( - minHeight: 40.0, - minWidth: 40.0, - ), - isSelected: selectedConditions, - children: const [Text("<"), Text("="), Text(">")], - ); - } - - /// Build temperature display for AC functions dialog - static Widget _buildTemperatureDisplay( - BuildContext context, dynamic selectedValue) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - decoration: BoxDecoration( - color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Text( - '${selectedValue ?? 20}°C', - style: context.textTheme.headlineMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), - ), - ); - } - - static Widget _buildTemperatureSlider( - BuildContext context, - StateSetter setState, - dynamic selectedValue, - Function(dynamic) onValueSelected, - ) { - final currentValue = selectedValue is int ? selectedValue.toDouble() : 20.0; - return Slider( - value: currentValue, - min: 16, - max: 30, - divisions: 14, - label: '${currentValue.toInt()}°C', - onChanged: (value) { - setState(() => onValueSelected(value.toInt())); - }, - ); - } - - static Widget _buildOperationalValuesList( - BuildContext context, - StateSetter setState, - List values, - dynamic selectedValue, - Function(dynamic) onValueSelected, - ) { - return ListView.builder( - shrinkWrap: false, - physics: const AlwaysScrollableScrollPhysics(), - itemCount: values.length, - itemBuilder: (context, index) { - final value = values[index]; - return ListTile( - leading: SvgPicture.asset( - value.icon, - width: 24, - height: 24, - ), - title: Text( - value.description, - style: context.textTheme.bodyMedium, - ), - trailing: Radio( - value: value.value, - groupValue: selectedValue, - onChanged: (newValue) { - setState(() => onValueSelected(newValue)); - }, - ), - ); - }, - ); - } - - static Widget _buildDialogFooter( - BuildContext context, - String? selectedFunction, - dynamic selectedValue, - String? selectedCondition, - ) { - return Container( - decoration: const BoxDecoration( - border: Border( - top: BorderSide( - color: ColorsManager.greyColor, - ), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - _buildFooterButton( - context, - 'Cancel', - selectedFunction != null ? 299 : 179, - () => Navigator.pop(context), - ), - _buildFooterButton( - context, - 'Confirm', - selectedFunction != null ? 299 : 179, - selectedFunction != null && selectedValue != null - ? () => Navigator.pop(context, { - 'function': selectedFunction, - 'value': selectedValue, - 'condition': selectedCondition ?? "==", - }) - : null, - ), - ], - ), - ); - } - - static Widget _buildFooterButton( - BuildContext context, - String text, - double width, - VoidCallback? onTap, - ) { - return GestureDetector( - onTap: onTap, - child: SizedBox( - height: 50, - width: width, - child: Center( - child: Text( - text, - style: context.textTheme.bodyMedium!.copyWith( - color: onTap != null - ? ColorsManager.primaryColorWithOpacity - : ColorsManager.textGray, - ), - ), - ), - ), - ); + static String _getValueDescription(ACFunction function, dynamic value) { + final values = function.getOperationalValues(); + final selectedValue = values.firstWhere((v) => v.value == value); + return selectedValue.description; } } diff --git a/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart index 0046db8a..ea4743b7 100644 --- a/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart @@ -13,15 +13,11 @@ class DeviceDialogHelper { final functions = data['functions'] as List; try { - final result = await _getDialogForDeviceType( + await _getDialogForDeviceType( context, data['productType'], functions, ); - - if (result != null) { - return {...data, ...result}; - } } catch (e) { debugPrint('Error: $e'); } @@ -29,25 +25,25 @@ class DeviceDialogHelper { return null; } - static Future?> _getDialogForDeviceType( + static Future _getDialogForDeviceType( BuildContext context, String productType, List functions, ) async { switch (productType) { case 'AC': - return ACHelper.showACFunctionsDialog(context, functions); + await ACHelper.showACFunctionsDialog(context, functions); + break; case '1G': - return OneGangSwitchHelper.showSwitchFunctionsDialog( - context, functions); + await OneGangSwitchHelper.showSwitchFunctionsDialog(context, functions); + break; case '2G': - return TwoGangSwitchHelper.showSwitchFunctionsDialog( - context, functions); + await TwoGangSwitchHelper.showSwitchFunctionsDialog(context, functions); + break; case '3G': - return ThreeGangSwitchHelper.showSwitchFunctionsDialog( + await ThreeGangSwitchHelper.showSwitchFunctionsDialog( context, functions); - default: - return null; + break; } } } diff --git a/lib/pages/routiens/helper/one_gang_switch_helper.dart b/lib/pages/routiens/helper/one_gang_switch_helper.dart index 30f25a96..c9a79377 100644 --- a/lib/pages/routiens/helper/one_gang_switch_helper.dart +++ b/lib/pages/routiens/helper/one_gang_switch_helper.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart'; @@ -7,16 +9,18 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class OneGangSwitchHelper { - static Future?> showSwitchFunctionsDialog( + static Future showSwitchFunctionsDialog( BuildContext context, List> functions) async { List> switchFunctions = functions .where( (f) => f is OneGangSwitchFunction || f is OneGangCountdownFunction) .toList(); - String? selectedFunction; - dynamic selectedValue; + Map selectedValues = {}; + List selectedFunctions = []; + String? selectedCondition = "<"; + List selectedConditions = [true, false, false]; - return showDialog?>( + await showDialog( context: context, builder: (BuildContext context) { return StatefulBuilder( @@ -24,7 +28,7 @@ class OneGangSwitchHelper { return AlertDialog( contentPadding: EdgeInsets.zero, content: Container( - width: selectedFunction != null ? 600 : 360, + width: 600, height: 450, decoration: BoxDecoration( color: Colors.white, @@ -50,21 +54,23 @@ class OneGangSwitchHelper { color: ColorsManager.greyColor, ), ), - Flexible( + Expanded( child: Row( children: [ + // Left side: Function list Expanded( child: ListView.separated( - shrinkWrap: false, - physics: const AlwaysScrollableScrollPhysics(), itemCount: switchFunctions.length, - separatorBuilder: (context, index) => - const Divider( + separatorBuilder: (_, __) => const Divider( color: ColorsManager.dividerColor, ), itemBuilder: (context, index) { final function = switchFunctions[index]; + final isSelected = + selectedValues.containsKey(function.code); return ListTile( + tileColor: + isSelected ? Colors.grey.shade100 : null, leading: SvgPicture.asset( function.icon, width: 24, @@ -74,62 +80,129 @@ class OneGangSwitchHelper { function.operationName, style: context.textTheme.bodyMedium, ), - trailing: const Icon( - Icons.arrow_forward_ios, - size: 16, - color: ColorsManager.textGray, - ), + trailing: isSelected + ? Icon( + Icons.check_circle, + color: ColorsManager + .primaryColorWithOpacity, + size: 20, + ) + : const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, + ), onTap: () { - setState(() { - selectedFunction = function.code; - selectedValue = null; - }); + if (isSelected) { + selectedValues.remove(function.code); + selectedFunctions.removeWhere( + (f) => f.function == function.code); + } + (context as Element).markNeedsBuild(); }, ); }, ), ), - if (selectedFunction != null) - Expanded( - child: Builder( - builder: (context) { - final selectedFn = switchFunctions.firstWhere( - (f) => f.code == selectedFunction) - as BaseSwitchFunction; - final values = - selectedFn.getOperationalValues(); - return ListView.builder( - shrinkWrap: false, - physics: - const AlwaysScrollableScrollPhysics(), - itemCount: values.length, - itemBuilder: (context, index) { - final value = values[index]; - return ListTile( - leading: SvgPicture.asset( - value.icon, - width: 24, - height: 24, - ), - title: Text( - value.description, - style: context.textTheme.bodyMedium, - ), - trailing: Radio( - value: value.value, - groupValue: selectedValue, - onChanged: (newValue) { - setState(() { - selectedValue = newValue; - }); - }, - ), + // Right side: Value selector + Expanded( + child: Builder( + builder: (context) { + final selectedFn = switchFunctions.firstWhere( + (f) => selectedValues.containsKey(f.code), + orElse: () => switchFunctions.first, + ) as BaseSwitchFunction; + + if (selectedFn is OneGangCountdownFunction) { + return _buildCountDownSelector( + context, + setState, + selectedValues[selectedFn.code] ?? 0, + selectedCondition, + selectedConditions, + (value) { + selectedValues[selectedFn.code] = value; + final functionData = DeviceFunctionData( + entityId: selectedFn.deviceId, + function: selectedFn.code, + operationName: selectedFn.operationName, + value: value, + condition: selectedCondition, + valueDescription: '${value} sec', ); + + final existingIndex = + selectedFunctions.indexWhere((f) => + f.function == selectedFn.code); + if (existingIndex != -1) { + selectedFunctions[existingIndex] = + functionData; + } else { + selectedFunctions.add(functionData); + } + (context as Element).markNeedsBuild(); + }, + (condition) { + setState(() { + selectedCondition = condition; + }); }, ); - }, - ), + } + + final values = + selectedFn.getOperationalValues(); + return ListView.builder( + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Radio( + value: value.value, + groupValue: + selectedValues[selectedFn.code], + onChanged: (newValue) { + selectedValues[selectedFn.code] = + newValue; + final functionData = + DeviceFunctionData( + entityId: selectedFn.deviceId, + function: selectedFn.code, + operationName: + selectedFn.operationName, + value: newValue, + valueDescription: value.description, + ); + + final existingIndex = + selectedFunctions.indexWhere( + (f) => + f.function == + selectedFn.code); + if (existingIndex != -1) { + selectedFunctions[existingIndex] = + functionData; + } else { + selectedFunctions.add(functionData); + } + (context as Element).markNeedsBuild(); + }, + ), + ); + }, + ); + }, ), + ), ], ), ), @@ -141,55 +214,35 @@ class OneGangSwitchHelper { Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - GestureDetector( - onTap: () { - Navigator.pop(context); - }, - child: Container( - height: 50, - width: selectedFunction != null ? 299 : 179, - decoration: const BoxDecoration( - border: Border( - right: - BorderSide(color: ColorsManager.greyColor), - ), - ), - child: Center( - child: Text( - 'Cancel', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: ColorsManager.greyColor), - ), - ), + TextButton( + onPressed: () => Navigator.pop(context), + child: Text( + 'Cancel', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: ColorsManager.greyColor), ), ), - GestureDetector( - onTap: () { - if (selectedFunction != null && - selectedValue != null) { - Navigator.pop(context, { - 'function': selectedFunction, - 'value': selectedValue, - }); - } - }, - child: SizedBox( - height: 50, - width: selectedFunction != null ? 299 : 179, - child: Center( - child: Text( - 'Confirm', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: - ColorsManager.primaryColorWithOpacity, - ), - ), - ), + TextButton( + onPressed: selectedFunctions.isNotEmpty + ? () { + for (final function in selectedFunctions) { + context + .read() + .add(AddFunction(function)); + } + Navigator.pop(context, true); + } + : null, + child: Text( + 'Confirm', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), ), ), ], @@ -203,4 +256,78 @@ class OneGangSwitchHelper { }, ); } + + /// Build countdown selector for switch functions dialog + static Widget _buildCountDownSelector( + BuildContext context, + StateSetter setState, + dynamic selectedValue, + String? selectedCondition, + List selectedConditions, + Function(dynamic) onValueSelected, + Function(String?) onConditionSelected, + ) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildConditionToggle( + context, + setState, + selectedConditions, + onConditionSelected, + ), + const SizedBox(height: 20), + Text( + '${selectedValue.toString()} sec', + style: Theme.of(context).textTheme.headlineMedium, + ), + const SizedBox(height: 20), + Slider( + value: selectedValue.toDouble(), + min: 0, + max: 300, // 5 minutes in seconds + divisions: 300, + onChanged: (value) { + setState(() { + onValueSelected(value.toInt()); + }); + }, + ), + ], + ); + } + + /// Build condition toggle for AC functions dialog + static Widget _buildConditionToggle( + BuildContext context, + StateSetter setState, + List selectedConditions, + Function(String?) onConditionSelected, + ) { + return ToggleButtons( + onPressed: (int index) { + setState(() { + for (int i = 0; i < selectedConditions.length; i++) { + selectedConditions[i] = i == index; + } + onConditionSelected(index == 0 + ? "<" + : index == 1 + ? "==" + : ">"); + }); + }, + borderRadius: const BorderRadius.all(Radius.circular(8)), + selectedBorderColor: ColorsManager.primaryColorWithOpacity, + selectedColor: Colors.white, + fillColor: ColorsManager.primaryColorWithOpacity, + color: ColorsManager.primaryColorWithOpacity, + constraints: const BoxConstraints( + minHeight: 40.0, + minWidth: 40.0, + ), + isSelected: selectedConditions, + children: const [Text("<"), Text("="), Text(">")], + ); + } } diff --git a/lib/pages/routiens/helper/three_gang_switch_helper.dart b/lib/pages/routiens/helper/three_gang_switch_helper.dart index 89aa2d95..56d1f3bb 100644 --- a/lib/pages/routiens/helper/three_gang_switch_helper.dart +++ b/lib/pages/routiens/helper/three_gang_switch_helper.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart'; @@ -7,7 +9,7 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class ThreeGangSwitchHelper { - static Future?> showSwitchFunctionsDialog( + static Future showSwitchFunctionsDialog( BuildContext context, List> functions) async { List> switchFunctions = functions .where((f) => @@ -18,10 +20,12 @@ class ThreeGangSwitchHelper { f is ThreeGangCountdown2Function || f is ThreeGangCountdown3Function) .toList(); - String? selectedFunction; - dynamic selectedValue; + Map selectedValues = {}; + List selectedFunctions = []; + String? selectedCondition = "<"; + List selectedConditions = [true, false, false]; - return showDialog?>( + await showDialog( context: context, builder: (BuildContext context) { return StatefulBuilder( @@ -29,7 +33,7 @@ class ThreeGangSwitchHelper { return AlertDialog( contentPadding: EdgeInsets.zero, content: Container( - width: selectedFunction != null ? 600 : 360, + width: 600, height: 450, decoration: BoxDecoration( color: Colors.white, @@ -55,21 +59,23 @@ class ThreeGangSwitchHelper { color: ColorsManager.greyColor, ), ), - Flexible( + Expanded( child: Row( children: [ + // Left side: Function list Expanded( child: ListView.separated( - shrinkWrap: false, - physics: const AlwaysScrollableScrollPhysics(), itemCount: switchFunctions.length, - separatorBuilder: (context, index) => - const Divider( + separatorBuilder: (_, __) => const Divider( color: ColorsManager.dividerColor, ), itemBuilder: (context, index) { final function = switchFunctions[index]; + final isSelected = + selectedValues.containsKey(function.code); return ListTile( + tileColor: + isSelected ? Colors.grey.shade100 : null, leading: SvgPicture.asset( function.icon, width: 24, @@ -79,62 +85,131 @@ class ThreeGangSwitchHelper { function.operationName, style: context.textTheme.bodyMedium, ), - trailing: const Icon( - Icons.arrow_forward_ios, - size: 16, - color: ColorsManager.textGray, - ), + trailing: isSelected + ? Icon( + Icons.check_circle, + color: ColorsManager + .primaryColorWithOpacity, + size: 20, + ) + : const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, + ), onTap: () { - setState(() { - selectedFunction = function.code; - selectedValue = null; - }); + if (isSelected) { + selectedValues.remove(function.code); + selectedFunctions.removeWhere( + (f) => f.function == function.code); + } + (context as Element).markNeedsBuild(); }, ); }, ), ), - if (selectedFunction != null) - Expanded( - child: Builder( - builder: (context) { - final selectedFn = switchFunctions.firstWhere( - (f) => f.code == selectedFunction) - as BaseSwitchFunction; - final values = - selectedFn.getOperationalValues(); - return ListView.builder( - shrinkWrap: false, - physics: - const AlwaysScrollableScrollPhysics(), - itemCount: values.length, - itemBuilder: (context, index) { - final value = values[index]; - return ListTile( - leading: SvgPicture.asset( - value.icon, - width: 24, - height: 24, - ), - title: Text( - value.description, - style: context.textTheme.bodyMedium, - ), - trailing: Radio( - value: value.value, - groupValue: selectedValue, - onChanged: (newValue) { - setState(() { - selectedValue = newValue; - }); - }, - ), + // Right side: Value selector + Expanded( + child: Builder( + builder: (context) { + final selectedFn = switchFunctions.firstWhere( + (f) => selectedValues.containsKey(f.code), + orElse: () => switchFunctions.first, + ) as BaseSwitchFunction; + + if (selectedFn is ThreeGangCountdown1Function || + selectedFn is ThreeGangCountdown2Function || + selectedFn is ThreeGangCountdown3Function) { + return _buildCountDownSelector( + context, + setState, + selectedValues[selectedFn.code] ?? 0, + selectedCondition, + selectedConditions, + (value) { + selectedValues[selectedFn.code] = value; + final functionData = DeviceFunctionData( + entityId: selectedFn.deviceId, + function: selectedFn.code, + operationName: selectedFn.operationName, + value: value, + condition: selectedCondition, + valueDescription: '${value} sec', ); + + final existingIndex = + selectedFunctions.indexWhere((f) => + f.function == selectedFn.code); + if (existingIndex != -1) { + selectedFunctions[existingIndex] = + functionData; + } else { + selectedFunctions.add(functionData); + } + (context as Element).markNeedsBuild(); + }, + (condition) { + setState(() { + selectedCondition = condition; + }); }, ); - }, - ), + } + + final values = + selectedFn.getOperationalValues(); + return ListView.builder( + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Radio( + value: value.value, + groupValue: + selectedValues[selectedFn.code], + onChanged: (newValue) { + selectedValues[selectedFn.code] = + newValue; + final functionData = + DeviceFunctionData( + entityId: selectedFn.deviceId, + function: selectedFn.code, + operationName: + selectedFn.operationName, + value: newValue, + valueDescription: value.description, + ); + + final existingIndex = + selectedFunctions.indexWhere( + (f) => + f.function == + selectedFn.code); + if (existingIndex != -1) { + selectedFunctions[existingIndex] = + functionData; + } else { + selectedFunctions.add(functionData); + } + (context as Element).markNeedsBuild(); + }, + ), + ); + }, + ); + }, ), + ), ], ), ), @@ -146,55 +221,35 @@ class ThreeGangSwitchHelper { Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - GestureDetector( - onTap: () { - Navigator.pop(context); - }, - child: Container( - height: 50, - width: selectedFunction != null ? 299 : 179, - decoration: const BoxDecoration( - border: Border( - right: - BorderSide(color: ColorsManager.greyColor), - ), - ), - child: Center( - child: Text( - 'Cancel', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: ColorsManager.greyColor), - ), - ), + TextButton( + onPressed: () => Navigator.pop(context), + child: Text( + 'Cancel', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: ColorsManager.greyColor), ), ), - GestureDetector( - onTap: () { - if (selectedFunction != null && - selectedValue != null) { - Navigator.pop(context, { - 'function': selectedFunction, - 'value': selectedValue, - }); - } - }, - child: SizedBox( - height: 50, - width: selectedFunction != null ? 299 : 179, - child: Center( - child: Text( - 'Confirm', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: - ColorsManager.primaryColorWithOpacity, - ), - ), - ), + TextButton( + onPressed: selectedFunctions.isNotEmpty + ? () { + for (final function in selectedFunctions) { + context + .read() + .add(AddFunction(function)); + } + Navigator.pop(context, true); + } + : null, + child: Text( + 'Confirm', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), ), ), ], @@ -208,4 +263,78 @@ class ThreeGangSwitchHelper { }, ); } + + /// Build countdown selector for switch functions dialog + static Widget _buildCountDownSelector( + BuildContext context, + StateSetter setState, + dynamic selectedValue, + String? selectedCondition, + List selectedConditions, + Function(dynamic) onValueSelected, + Function(String?) onConditionSelected, + ) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildConditionToggle( + context, + setState, + selectedConditions, + onConditionSelected, + ), + const SizedBox(height: 20), + Text( + '${selectedValue.toString()} sec', + style: Theme.of(context).textTheme.headlineMedium, + ), + const SizedBox(height: 20), + Slider( + value: selectedValue.toDouble(), + min: 0, + max: 300, // 5 minutes in seconds + divisions: 300, + onChanged: (value) { + setState(() { + onValueSelected(value.toInt()); + }); + }, + ), + ], + ); + } + + /// Build condition toggle for AC functions dialog + static Widget _buildConditionToggle( + BuildContext context, + StateSetter setState, + List selectedConditions, + Function(String?) onConditionSelected, + ) { + return ToggleButtons( + onPressed: (int index) { + setState(() { + for (int i = 0; i < selectedConditions.length; i++) { + selectedConditions[i] = i == index; + } + onConditionSelected(index == 0 + ? "<" + : index == 1 + ? "==" + : ">"); + }); + }, + borderRadius: const BorderRadius.all(Radius.circular(8)), + selectedBorderColor: ColorsManager.primaryColorWithOpacity, + selectedColor: Colors.white, + fillColor: ColorsManager.primaryColorWithOpacity, + color: ColorsManager.primaryColorWithOpacity, + constraints: const BoxConstraints( + minHeight: 40.0, + minWidth: 40.0, + ), + isSelected: selectedConditions, + children: const [Text("<"), Text("="), Text(">")], + ); + } } diff --git a/lib/pages/routiens/helper/two_gang_switch_helper.dart b/lib/pages/routiens/helper/two_gang_switch_helper.dart index 1d271ac7..6f2febc7 100644 --- a/lib/pages/routiens/helper/two_gang_switch_helper.dart +++ b/lib/pages/routiens/helper/two_gang_switch_helper.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart'; @@ -7,7 +9,7 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class TwoGangSwitchHelper { - static Future?> showSwitchFunctionsDialog( + static Future showSwitchFunctionsDialog( BuildContext context, List> functions) async { List> switchFunctions = functions .where((f) => @@ -16,10 +18,12 @@ class TwoGangSwitchHelper { f is TwoGangCountdown1Function || f is TwoGangCountdown2Function) .toList(); - String? selectedFunction; - dynamic selectedValue; + Map selectedValues = {}; + List selectedFunctions = []; + String? selectedCondition = "<"; + List selectedConditions = [true, false, false]; - return showDialog?>( + await showDialog( context: context, builder: (BuildContext context) { return StatefulBuilder( @@ -27,7 +31,7 @@ class TwoGangSwitchHelper { return AlertDialog( contentPadding: EdgeInsets.zero, content: Container( - width: selectedFunction != null ? 600 : 360, + width: 600, height: 450, decoration: BoxDecoration( color: Colors.white, @@ -53,21 +57,23 @@ class TwoGangSwitchHelper { color: ColorsManager.greyColor, ), ), - Flexible( + Expanded( child: Row( children: [ + // Left side: Function list Expanded( child: ListView.separated( - shrinkWrap: false, - physics: const AlwaysScrollableScrollPhysics(), itemCount: switchFunctions.length, - separatorBuilder: (context, index) => - const Divider( + separatorBuilder: (_, __) => const Divider( color: ColorsManager.dividerColor, ), itemBuilder: (context, index) { final function = switchFunctions[index]; + final isSelected = + selectedValues.containsKey(function.code); return ListTile( + tileColor: + isSelected ? Colors.grey.shade100 : null, leading: SvgPicture.asset( function.icon, width: 24, @@ -77,62 +83,130 @@ class TwoGangSwitchHelper { function.operationName, style: context.textTheme.bodyMedium, ), - trailing: const Icon( - Icons.arrow_forward_ios, - size: 16, - color: ColorsManager.textGray, - ), + trailing: isSelected + ? Icon( + Icons.check_circle, + color: ColorsManager + .primaryColorWithOpacity, + size: 20, + ) + : const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, + ), onTap: () { - setState(() { - selectedFunction = function.code; - selectedValue = null; - }); + if (isSelected) { + selectedValues.remove(function.code); + selectedFunctions.removeWhere( + (f) => f.function == function.code); + } + (context as Element).markNeedsBuild(); }, ); }, ), ), - if (selectedFunction != null) - Expanded( - child: Builder( - builder: (context) { - final selectedFn = switchFunctions.firstWhere( - (f) => f.code == selectedFunction) - as BaseSwitchFunction; - final values = - selectedFn.getOperationalValues(); - return ListView.builder( - shrinkWrap: false, - physics: - const AlwaysScrollableScrollPhysics(), - itemCount: values.length, - itemBuilder: (context, index) { - final value = values[index]; - return ListTile( - leading: SvgPicture.asset( - value.icon, - width: 24, - height: 24, - ), - title: Text( - value.description, - style: context.textTheme.bodyMedium, - ), - trailing: Radio( - value: value.value, - groupValue: selectedValue, - onChanged: (newValue) { - setState(() { - selectedValue = newValue; - }); - }, - ), + // Right side: Value selector + Expanded( + child: Builder( + builder: (context) { + final selectedFn = switchFunctions.firstWhere( + (f) => selectedValues.containsKey(f.code), + orElse: () => switchFunctions.first, + ) as BaseSwitchFunction; + + if (selectedFn is TwoGangCountdown1Function || + selectedFn is TwoGangCountdown2Function) { + return _buildCountDownSelector( + context, + setState, + selectedValues[selectedFn.code] ?? 0, + selectedCondition, + selectedConditions, + (value) { + selectedValues[selectedFn.code] = value; + final functionData = DeviceFunctionData( + entityId: selectedFn.deviceId, + function: selectedFn.code, + operationName: selectedFn.operationName, + value: value, + condition: selectedCondition, + valueDescription: '${value} sec', ); + + final existingIndex = + selectedFunctions.indexWhere((f) => + f.function == selectedFn.code); + if (existingIndex != -1) { + selectedFunctions[existingIndex] = + functionData; + } else { + selectedFunctions.add(functionData); + } + (context as Element).markNeedsBuild(); + }, + (condition) { + setState(() { + selectedCondition = condition; + }); }, ); - }, - ), + } + + final values = + selectedFn.getOperationalValues(); + return ListView.builder( + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Radio( + value: value.value, + groupValue: + selectedValues[selectedFn.code], + onChanged: (newValue) { + selectedValues[selectedFn.code] = + newValue; + final functionData = + DeviceFunctionData( + entityId: selectedFn.deviceId, + function: selectedFn.code, + operationName: + selectedFn.operationName, + value: newValue, + valueDescription: value.description, + ); + + final existingIndex = + selectedFunctions.indexWhere( + (f) => + f.function == + selectedFn.code); + if (existingIndex != -1) { + selectedFunctions[existingIndex] = + functionData; + } else { + selectedFunctions.add(functionData); + } + (context as Element).markNeedsBuild(); + }, + ), + ); + }, + ); + }, ), + ), ], ), ), @@ -144,55 +218,35 @@ class TwoGangSwitchHelper { Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - GestureDetector( - onTap: () { - Navigator.pop(context); - }, - child: Container( - height: 50, - width: selectedFunction != null ? 299 : 179, - decoration: const BoxDecoration( - border: Border( - right: - BorderSide(color: ColorsManager.greyColor), - ), - ), - child: Center( - child: Text( - 'Cancel', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: ColorsManager.greyColor), - ), - ), + TextButton( + onPressed: () => Navigator.pop(context), + child: Text( + 'Cancel', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: ColorsManager.greyColor), ), ), - GestureDetector( - onTap: () { - if (selectedFunction != null && - selectedValue != null) { - Navigator.pop(context, { - 'function': selectedFunction, - 'value': selectedValue, - }); - } - }, - child: SizedBox( - height: 50, - width: selectedFunction != null ? 299 : 179, - child: Center( - child: Text( - 'Confirm', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: - ColorsManager.primaryColorWithOpacity, - ), - ), - ), + TextButton( + onPressed: selectedFunctions.isNotEmpty + ? () { + for (final function in selectedFunctions) { + context + .read() + .add(AddFunction(function)); + } + Navigator.pop(context, true); + } + : null, + child: Text( + 'Confirm', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), ), ), ], @@ -206,4 +260,78 @@ class TwoGangSwitchHelper { }, ); } + + /// Build countdown selector for switch functions dialog + static Widget _buildCountDownSelector( + BuildContext context, + StateSetter setState, + dynamic selectedValue, + String? selectedCondition, + List selectedConditions, + Function(dynamic) onValueSelected, + Function(String?) onConditionSelected, + ) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildConditionToggle( + context, + setState, + selectedConditions, + onConditionSelected, + ), + const SizedBox(height: 20), + Text( + '${selectedValue.toString()} sec', + style: Theme.of(context).textTheme.headlineMedium, + ), + const SizedBox(height: 20), + Slider( + value: selectedValue.toDouble(), + min: 0, + max: 300, // 5 minutes in seconds + divisions: 300, + onChanged: (value) { + setState(() { + onValueSelected(value.toInt()); + }); + }, + ), + ], + ); + } + + /// Build condition toggle for AC functions dialog + static Widget _buildConditionToggle( + BuildContext context, + StateSetter setState, + List selectedConditions, + Function(String?) onConditionSelected, + ) { + return ToggleButtons( + onPressed: (int index) { + setState(() { + for (int i = 0; i < selectedConditions.length; i++) { + selectedConditions[i] = i == index; + } + onConditionSelected(index == 0 + ? "<" + : index == 1 + ? "==" + : ">"); + }); + }, + borderRadius: const BorderRadius.all(Radius.circular(8)), + selectedBorderColor: ColorsManager.primaryColorWithOpacity, + selectedColor: Colors.white, + fillColor: ColorsManager.primaryColorWithOpacity, + color: ColorsManager.primaryColorWithOpacity, + constraints: const BoxConstraints( + minHeight: 40.0, + minWidth: 40.0, + ), + isSelected: selectedConditions, + children: const [Text("<"), Text("="), Text(">")], + ); + } } diff --git a/lib/pages/routiens/models/device_functions.dart b/lib/pages/routiens/models/device_functions.dart index fa6ac673..303fe48c 100644 --- a/lib/pages/routiens/models/device_functions.dart +++ b/lib/pages/routiens/models/device_functions.dart @@ -15,3 +15,47 @@ abstract class DeviceFunction { T execute(T currentStatus, dynamic newValue); } + +class DeviceFunctionData { + final String entityId; + final String actionExecutor; + final String function; + final String operationName; + final dynamic value; + final String? condition; + final String? valueDescription; + + DeviceFunctionData({ + required this.entityId, + this.actionExecutor = 'function', + required this.function, + required this.operationName, + required this.value, + this.condition, + this.valueDescription, + }); + + Map toJson() { + return { + 'entityId': entityId, + 'actionExecutor': actionExecutor, + 'function': function, + 'operationName': operationName, + 'value': value, + if (condition != null) 'condition': condition, + if (valueDescription != null) 'valueDescription': valueDescription, + }; + } + + factory DeviceFunctionData.fromJson(Map json) { + return DeviceFunctionData( + entityId: json['entityId'], + actionExecutor: json['actionExecutor'] ?? 'function', + function: json['function'], + operationName: json['operationName'], + value: json['value'], + condition: json['condition'], + valueDescription: json['valueDescription'], + ); + } +} diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index 8ed4afff..7ee078ff 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -46,51 +48,110 @@ class DraggableCard extends StatelessWidget { } Widget _buildCardContent(BuildContext context) { - return Card( - color: ColorsManager.whiteColors, - child: SizedBox( - height: 123, - width: 90, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - height: 50, - width: 50, - decoration: BoxDecoration( - color: ColorsManager.CircleImageBackground, - borderRadius: BorderRadius.circular(90), - border: Border.all( - color: ColorsManager.graysColor, + return BlocBuilder( + builder: (context, state) { + // Filter functions for this device + final deviceFunctions = state.selectedFunctions + .where((f) => f.entityId == deviceData?['deviceId']) + .toList(); + + return Card( + color: ColorsManager.whiteColors, + child: SizedBox( + width: 90, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + height: 123, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + height: 50, + width: 50, + decoration: BoxDecoration( + color: ColorsManager.CircleImageBackground, + borderRadius: BorderRadius.circular(90), + border: Border.all( + color: ColorsManager.graysColor, + ), + ), + padding: const EdgeInsets.all(8), + child: imagePath.contains('.svg') + ? SvgPicture.asset( + imagePath, + ) + : Image.network(imagePath), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 3), + child: Text( + title, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: context.textTheme.bodySmall?.copyWith( + color: titleColor ?? ColorsManager.blackColor, + fontSize: 12, + ), + ), + ), + ], + ), ), - ), - padding: const EdgeInsets.all(8), - child: imagePath.contains('.svg') - ? SvgPicture.asset( - imagePath, - ) - : Image.network(imagePath), + if (deviceFunctions.isNotEmpty) ...[ + const Divider(height: 1), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: + const EdgeInsets.symmetric(vertical: 4, horizontal: 4), + itemCount: deviceFunctions.length, + itemBuilder: (context, index) { + final function = deviceFunctions[index]; + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Text( + '${function.operationName}: ${function.valueDescription}', + style: context.textTheme.bodySmall?.copyWith( + fontSize: 9, + color: ColorsManager.textGray, + height: 1.2, + ), + overflow: TextOverflow.ellipsis, + ), + ), + InkWell( + onTap: () { + context.read().add( + RemoveFunction(function), + ); + }, + child: const Padding( + padding: EdgeInsets.all(2), + child: Icon( + Icons.close, + size: 12, + color: ColorsManager.textGray, + ), + ), + ), + ], + ); + }, + ), + ], + ], ), - const SizedBox( - height: 8, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 3), - child: Text( - title, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 2, - style: context.textTheme.bodySmall?.copyWith( - color: titleColor ?? ColorsManager.blackColor, - fontSize: 12, - ), - ), - ), - ], - ), - ), + ), + ); + }, ); } From fb4a4d4d6cb69623060932cbd01792dba524e112 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Fri, 22 Nov 2024 19:55:16 +0300 Subject: [PATCH 08/40] push value notifers --- lib/main.dart | 12 +- lib/pages/routiens/helper/ac_helper.dart | 481 +++++++++++------- .../helper/one_gang_switch_helper.dart | 426 +++++++--------- .../helper/two_gang_switch_helper.dart | 428 +++++++--------- .../view/create_new_routine_view.dart | 125 +++-- lib/pages/routiens/view/routines_view.dart | 107 ++-- .../conditions_routines_devices_view.dart | 170 ++++--- lib/pages/routiens/widgets/dialog_footer.dart | 76 +++ lib/pages/routiens/widgets/dialog_header.dart | 31 ++ lib/pages/routiens/widgets/dragable_card.dart | 235 ++++----- lib/pages/routiens/widgets/if_container.dart | 1 + .../routiens/widgets/routine_devices.dart | 59 ++- .../widgets/scenes_and_automations.dart | 67 ++- .../routiens/widgets/then_container.dart | 1 + 14 files changed, 1173 insertions(+), 1046 deletions(-) create mode 100644 lib/pages/routiens/widgets/dialog_footer.dart create mode 100644 lib/pages/routiens/widgets/dialog_header.dart diff --git a/lib/main.dart b/lib/main.dart index c544f227..ac002f85 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ import 'package:go_router/go_router.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_event.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/utils/app_routes.dart'; @@ -14,7 +15,8 @@ import 'package:syncrow_web/utils/theme/theme.dart'; Future main() async { try { - const environment = String.fromEnvironment('FLAVOR', defaultValue: 'development'); + const environment = + String.fromEnvironment('FLAVOR', defaultValue: 'development'); await dotenv.load(fileName: '.env.$environment'); WidgetsFlutterBinding.ensureInitialized(); initialSetup(); @@ -46,10 +48,14 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ - BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())), + BlocProvider( + create: (context) => HomeBloc()..add(const FetchUserInfo())), BlocProvider( create: (context) => VisitorPasswordBloc(), - ) + ), + BlocProvider( + create: (context) => RoutineBloc(), + ), ], child: MaterialApp.router( debugShowCheckedModeBanner: false, diff --git a/lib/pages/routiens/helper/ac_helper.dart b/lib/pages/routiens/helper/ac_helper.dart index b14acd3a..9ba0b224 100644 --- a/lib/pages/routiens/helper/ac_helper.dart +++ b/lib/pages/routiens/helper/ac_helper.dart @@ -1,219 +1,330 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class ACHelper { - static Future showACFunctionsDialog( + static Future?> showACFunctionsDialog( BuildContext context, List> functions, ) async { List acFunctions = functions.whereType().toList(); - // Track multiple selections using a map - Map selectedValues = {}; - List selectedFunctions = []; + String? selectedFunction; + dynamic selectedValue = 20; + String? selectedCondition = "=="; + List _selectedConditions = [false, true, false]; - await showDialog( + return showDialog?>( context: context, builder: (BuildContext context) { - return Dialog( - child: Container( - width: 600, - height: 450, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - ), - padding: const EdgeInsets.only(top: 20), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'AC Functions', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - fontWeight: FontWeight.bold, - ), - ), - Padding( - padding: - const EdgeInsets.symmetric(vertical: 15, horizontal: 50), - child: Container( - height: 1, - width: double.infinity, - color: ColorsManager.greyColor, - ), - ), - Expanded( - child: Row( - children: [ - Expanded( - child: ListView.separated( - itemCount: acFunctions.length, - separatorBuilder: (_, __) => const Divider( - color: ColorsManager.dividerColor, - ), - itemBuilder: (context, index) { - final function = acFunctions[index]; - final isSelected = - selectedValues.containsKey(function.code); - return ListTile( - tileColor: - isSelected ? Colors.grey.shade100 : null, - leading: SvgPicture.asset( - function.icon, - width: 24, - height: 24, - ), - title: Text( - function.operationName, - style: context.textTheme.bodyMedium, - ), - trailing: isSelected - ? Icon( - Icons.check_circle, - color: - ColorsManager.primaryColorWithOpacity, - size: 20, - ) - : const Icon( - Icons.arrow_forward_ios, - size: 16, - color: ColorsManager.textGray, - ), - onTap: () { - if (isSelected) { - selectedValues.remove(function.code); - selectedFunctions.removeWhere( - (f) => f.function == function.code); - } - (context as Element).markNeedsBuild(); - }, - ); - }, - ), - ), - Expanded( - child: Builder( - builder: (context) { - final selectedFunction = acFunctions.firstWhere( - (f) => selectedValues.containsKey(f.code), - orElse: () => acFunctions.first, - ); - return _buildValueSelector( - context, - selectedFunction, - selectedValues[selectedFunction.code], - (value) { - selectedValues[selectedFunction.code] = value; - // Update or add the function data - final functionData = DeviceFunctionData( - entityId: selectedFunction.deviceId, - function: selectedFunction.code, - operationName: selectedFunction.operationName, - value: value, - valueDescription: _getValueDescription( - selectedFunction, value), - ); - - final existingIndex = - selectedFunctions.indexWhere((f) => - f.function == selectedFunction.code); - if (existingIndex != -1) { - selectedFunctions[existingIndex] = - functionData; - } else { - selectedFunctions.add(functionData); - } - - (context as Element).markNeedsBuild(); - }, - ); - }, - ), - ), - ], - ), - ), - Container( - height: 1, - width: double.infinity, - color: ColorsManager.greyColor, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text( - 'Cancel', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: ColorsManager.greyColor), - ), - ), - TextButton( - onPressed: selectedFunctions.isNotEmpty - ? () { - // Add all selected functions to the bloc - for (final function in selectedFunctions) { - context - .read() - .add(AddFunction(function)); - } - Navigator.pop(context, true); - } - : null, - child: Text( - 'Confirm', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), - ), - ), - ], - ), - ], - ), - ), + return StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: _buildDialogContent( + context, + setState, + acFunctions, + selectedFunction, + selectedValue, + selectedCondition, + _selectedConditions, + (fn) => selectedFunction = fn, + (val) => selectedValue = val, + (cond) => selectedCondition = cond, + ), + ); + }, ); }, ); } - static Widget _buildValueSelector( + /// Build dialog content for AC functions dialog + static Widget _buildDialogContent( BuildContext context, - ACFunction function, + StateSetter setState, + List acFunctions, + String? selectedFunction, dynamic selectedValue, + String? selectedCondition, + List selectedConditions, + Function(String?) onFunctionSelected, Function(dynamic) onValueSelected, + Function(String?) onConditionSelected, ) { - final values = function.getOperationalValues(); return Container( - height: 200, - padding: const EdgeInsets.symmetric(horizontal: 20), - child: ListView.builder( - itemCount: values.length, + width: selectedFunction != null ? 600 : 360, + height: 450, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const DialogHeader('AC Functions'), + Flexible( + child: Row( + children: [ + _buildFunctionsList( + context, + setState, + acFunctions, + selectedFunction, + onFunctionSelected, + ), + if (selectedFunction != null) + _buildValueSelector( + context, + setState, + selectedFunction, + selectedValue, + selectedCondition, + selectedConditions, + onValueSelected, + onConditionSelected, + acFunctions, + ), + ], + ), + ), + DialogFooter( + onCancel: () => Navigator.pop(context), + onConfirm: selectedFunction != null && selectedValue != null + ? () => Navigator.pop(context, { + 'function': selectedFunction, + 'value': selectedValue, + 'condition': selectedCondition ?? "==", + }) + : null, + isConfirmEnabled: selectedFunction != null && selectedValue != null, + ), + ], + ), + ); + } + + /// Build functions list for AC functions dialog + static Widget _buildFunctionsList( + BuildContext context, + StateSetter setState, + List acFunctions, + String? selectedFunction, + Function(String?) onFunctionSelected, + ) { + return Expanded( + child: ListView.separated( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: acFunctions.length, + separatorBuilder: (context, index) => const Divider( + color: ColorsManager.dividerColor, + ), itemBuilder: (context, index) { - final value = values[index]; - return RadioListTile( - value: value.value, - groupValue: selectedValue, - onChanged: onValueSelected, - title: Text(value.description), + final function = acFunctions[index]; + return ListTile( + leading: SvgPicture.asset( + function.icon, + width: 24, + height: 24, + ), + title: Text( + function.operationName, + style: context.textTheme.bodyMedium, + ), + trailing: const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, + ), + onTap: () => setState(() => onFunctionSelected(function.code)), ); }, ), ); } - static String _getValueDescription(ACFunction function, dynamic value) { - final values = function.getOperationalValues(); - final selectedValue = values.firstWhere((v) => v.value == value); - return selectedValue.description; + /// Build value selector for AC functions dialog + static Widget _buildValueSelector( + BuildContext context, + StateSetter setState, + String selectedFunction, + dynamic selectedValue, + String? selectedCondition, + List selectedConditions, + Function(dynamic) onValueSelected, + Function(String?) onConditionSelected, + List acFunctions, + ) { + if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') { + return Expanded( + child: _buildTemperatureSelector( + context, + setState, + selectedValue, + selectedCondition, + selectedConditions, + onValueSelected, + onConditionSelected, + ), + ); + } + + final selectedFn = + acFunctions.firstWhere((f) => f.code == selectedFunction); + final values = selectedFn.getOperationalValues(); + return Expanded( + child: _buildOperationalValuesList( + context, + setState, + values, + selectedValue, + onValueSelected, + ), + ); + } + + /// Build temperature selector for AC functions dialog + static Widget _buildTemperatureSelector( + BuildContext context, + StateSetter setState, + dynamic selectedValue, + String? selectedCondition, + List selectedConditions, + Function(dynamic) onValueSelected, + Function(String?) onConditionSelected, + ) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildConditionToggle( + context, + setState, + selectedConditions, + onConditionSelected, + ), + const SizedBox(height: 20), + _buildTemperatureDisplay(context, selectedValue), + const SizedBox(height: 20), + _buildTemperatureSlider( + context, + setState, + selectedValue, + onValueSelected, + ), + ], + ); + } + + /// Build condition toggle for AC functions dialog + static Widget _buildConditionToggle( + BuildContext context, + StateSetter setState, + List selectedConditions, + Function(String?) onConditionSelected, + ) { + return ToggleButtons( + onPressed: (int index) { + setState(() { + for (int i = 0; i < selectedConditions.length; i++) { + selectedConditions[i] = i == index; + } + onConditionSelected(index == 0 + ? "<" + : index == 1 + ? "==" + : ">"); + }); + }, + borderRadius: const BorderRadius.all(Radius.circular(8)), + selectedBorderColor: ColorsManager.primaryColorWithOpacity, + selectedColor: Colors.white, + fillColor: ColorsManager.primaryColorWithOpacity, + color: ColorsManager.primaryColorWithOpacity, + constraints: const BoxConstraints( + minHeight: 40.0, + minWidth: 40.0, + ), + isSelected: selectedConditions, + children: const [Text("<"), Text("="), Text(">")], + ); + } + + /// Build temperature display for AC functions dialog + static Widget _buildTemperatureDisplay( + BuildContext context, dynamic selectedValue) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + '${selectedValue ?? 20}°C', + style: context.textTheme.headlineMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), + ), + ); + } + + static Widget _buildTemperatureSlider( + BuildContext context, + StateSetter setState, + dynamic selectedValue, + Function(dynamic) onValueSelected, + ) { + final currentValue = selectedValue is int ? selectedValue.toDouble() : 20.0; + return Slider( + value: currentValue, + min: 16, + max: 30, + divisions: 14, + label: '${currentValue.toInt()}°C', + onChanged: (value) { + setState(() => onValueSelected(value.toInt())); + }, + ); + } + + static Widget _buildOperationalValuesList( + BuildContext context, + StateSetter setState, + List values, + dynamic selectedValue, + Function(dynamic) onValueSelected, + ) { + return ListView.builder( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Radio( + value: value.value, + groupValue: selectedValue, + onChanged: (newValue) { + setState(() => onValueSelected(newValue)); + }, + ), + ); + }, + ); } } diff --git a/lib/pages/routiens/helper/one_gang_switch_helper.dart b/lib/pages/routiens/helper/one_gang_switch_helper.dart index c9a79377..0837829b 100644 --- a/lib/pages/routiens/helper/one_gang_switch_helper.dart +++ b/lib/pages/routiens/helper/one_gang_switch_helper.dart @@ -1,34 +1,38 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class OneGangSwitchHelper { - static Future showSwitchFunctionsDialog( - BuildContext context, List> functions) async { + static Future?> showSwitchFunctionsDialog( + BuildContext context, + List> functions, + ) async { List> switchFunctions = functions .where( (f) => f is OneGangSwitchFunction || f is OneGangCountdownFunction) .toList(); - Map selectedValues = {}; - List selectedFunctions = []; - String? selectedCondition = "<"; - List selectedConditions = [true, false, false]; + final selectedFunctionNotifier = ValueNotifier(null); + final selectedValueNotifier = ValueNotifier(null); + final selectedConditionNotifier = ValueNotifier("<"); + final selectedConditionsNotifier = + ValueNotifier>([true, false, false]); - await showDialog( + return showDialog?>( context: context, builder: (BuildContext context) { - return StatefulBuilder( - builder: (context, setState) { + return ValueListenableBuilder( + valueListenable: selectedFunctionNotifier, + builder: (context, selectedFunction, _) { return AlertDialog( contentPadding: EdgeInsets.zero, content: Container( - width: 600, + width: selectedFunction != null ? 600 : 360, height: 450, decoration: BoxDecoration( color: Colors.white, @@ -38,22 +42,7 @@ class OneGangSwitchHelper { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text( - '1 Gang Light Switch Condition', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - fontWeight: FontWeight.bold, - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 15, horizontal: 50), - child: Container( - height: 1, - width: double.infinity, - color: ColorsManager.greyColor, - ), - ), + const DialogHeader('1 Gang Light Switch Condition'), Expanded( child: Row( children: [ @@ -66,143 +55,67 @@ class OneGangSwitchHelper { ), itemBuilder: (context, index) { final function = switchFunctions[index]; - final isSelected = - selectedValues.containsKey(function.code); - return ListTile( - tileColor: - isSelected ? Colors.grey.shade100 : null, - leading: SvgPicture.asset( - function.icon, - width: 24, - height: 24, - ), - title: Text( - function.operationName, - style: context.textTheme.bodyMedium, - ), - trailing: isSelected - ? Icon( - Icons.check_circle, - color: ColorsManager - .primaryColorWithOpacity, - size: 20, - ) - : const Icon( - Icons.arrow_forward_ios, - size: 16, - color: ColorsManager.textGray, - ), - onTap: () { - if (isSelected) { - selectedValues.remove(function.code); - selectedFunctions.removeWhere( - (f) => f.function == function.code); - } - (context as Element).markNeedsBuild(); - }, - ); - }, - ), - ), - // Right side: Value selector - Expanded( - child: Builder( - builder: (context) { - final selectedFn = switchFunctions.firstWhere( - (f) => selectedValues.containsKey(f.code), - orElse: () => switchFunctions.first, - ) as BaseSwitchFunction; - - if (selectedFn is OneGangCountdownFunction) { - return _buildCountDownSelector( - context, - setState, - selectedValues[selectedFn.code] ?? 0, - selectedCondition, - selectedConditions, - (value) { - selectedValues[selectedFn.code] = value; - final functionData = DeviceFunctionData( - entityId: selectedFn.deviceId, - function: selectedFn.code, - operationName: selectedFn.operationName, - value: value, - condition: selectedCondition, - valueDescription: '${value} sec', - ); - - final existingIndex = - selectedFunctions.indexWhere((f) => - f.function == selectedFn.code); - if (existingIndex != -1) { - selectedFunctions[existingIndex] = - functionData; - } else { - selectedFunctions.add(functionData); - } - (context as Element).markNeedsBuild(); - }, - (condition) { - setState(() { - selectedCondition = condition; - }); - }, - ); - } - - final values = - selectedFn.getOperationalValues(); - return ListView.builder( - itemCount: values.length, - itemBuilder: (context, index) { - final value = values[index]; + return ValueListenableBuilder( + valueListenable: selectedFunctionNotifier, + builder: (context, selectedFunction, _) { + final isSelected = + selectedFunction == function.code; return ListTile( + tileColor: isSelected + ? Colors.grey.shade100 + : null, leading: SvgPicture.asset( - value.icon, + function.icon, width: 24, height: 24, ), title: Text( - value.description, + function.operationName, style: context.textTheme.bodyMedium, ), - trailing: Radio( - value: value.value, - groupValue: - selectedValues[selectedFn.code], - onChanged: (newValue) { - selectedValues[selectedFn.code] = - newValue; - final functionData = - DeviceFunctionData( - entityId: selectedFn.deviceId, - function: selectedFn.code, - operationName: - selectedFn.operationName, - value: newValue, - valueDescription: value.description, - ); - - final existingIndex = - selectedFunctions.indexWhere( - (f) => - f.function == - selectedFn.code); - if (existingIndex != -1) { - selectedFunctions[existingIndex] = - functionData; - } else { - selectedFunctions.add(functionData); - } - (context as Element).markNeedsBuild(); - }, + trailing: const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, ), + onTap: () { + selectedFunctionNotifier.value = + function.code; + selectedValueNotifier.value = + function is OneGangCountdownFunction + ? 0 + : null; + }, ); }, ); }, ), ), + // Right side: Value selector + if (selectedFunction != null) + ValueListenableBuilder( + valueListenable: selectedFunctionNotifier, + builder: (context, selectedFunction, _) { + final selectedFn = switchFunctions.firstWhere( + (f) => f.code == selectedFunction, + ); + return Expanded( + child: selectedFn is OneGangCountdownFunction + ? _buildCountDownSelector( + context, + selectedValueNotifier, + selectedConditionNotifier, + selectedConditionsNotifier, + ) + : _buildOperationalValuesList( + context, + selectedFn as BaseSwitchFunction, + selectedValueNotifier, + ), + ); + }, + ), ], ), ), @@ -211,41 +124,36 @@ class OneGangSwitchHelper { width: double.infinity, color: ColorsManager.greyColor, ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text( - 'Cancel', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: ColorsManager.greyColor), - ), - ), - TextButton( - onPressed: selectedFunctions.isNotEmpty - ? () { - for (final function in selectedFunctions) { - context - .read() - .add(AddFunction(function)); - } - Navigator.pop(context, true); - } - : null, - child: Text( - 'Confirm', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), - ), - ), - ], + DialogFooter( + onCancel: () => Navigator.pop(context), + onConfirm: selectedFunctionNotifier.value != null && + selectedValueNotifier.value != null + ? () { + final selectedFn = switchFunctions.firstWhere( + (f) => f.code == selectedFunctionNotifier.value, + ); + final value = selectedValueNotifier.value; + final functionData = DeviceFunctionData( + entityId: selectedFn.deviceId, + function: selectedFn.code, + operationName: selectedFn.operationName, + value: value, + condition: selectedConditionNotifier.value, + valueDescription: + selectedFn is OneGangCountdownFunction + ? '${value} sec' + : ((selectedFn as BaseSwitchFunction) + .getOperationalValues() + .firstWhere((v) => v.value == value) + .description), + ); + Navigator.pop( + context, {selectedFn.code: functionData}); + } + : null, + isConfirmEnabled: + selectedFunctionNotifier.value != null && + selectedValueNotifier.value != null, ), ], ), @@ -257,77 +165,101 @@ class OneGangSwitchHelper { ); } - /// Build countdown selector for switch functions dialog static Widget _buildCountDownSelector( BuildContext context, - StateSetter setState, - dynamic selectedValue, - String? selectedCondition, - List selectedConditions, - Function(dynamic) onValueSelected, - Function(String?) onConditionSelected, + ValueNotifier valueNotifier, + ValueNotifier conditionNotifier, + ValueNotifier> conditionsNotifier, ) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _buildConditionToggle( - context, - setState, - selectedConditions, - onConditionSelected, - ), - const SizedBox(height: 20), - Text( - '${selectedValue.toString()} sec', - style: Theme.of(context).textTheme.headlineMedium, - ), - const SizedBox(height: 20), - Slider( - value: selectedValue.toDouble(), - min: 0, - max: 300, // 5 minutes in seconds - divisions: 300, - onChanged: (value) { - setState(() { - onValueSelected(value.toInt()); - }); - }, - ), - ], + return ValueListenableBuilder( + valueListenable: valueNotifier, + builder: (context, value, _) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ValueListenableBuilder>( + valueListenable: conditionsNotifier, + builder: (context, selectedConditions, _) { + return ToggleButtons( + onPressed: (int index) { + final newConditions = List.filled(3, false); + newConditions[index] = true; + conditionsNotifier.value = newConditions; + conditionNotifier.value = index == 0 + ? "<" + : index == 1 + ? "==" + : ">"; + }, + borderRadius: const BorderRadius.all(Radius.circular(8)), + selectedBorderColor: ColorsManager.primaryColorWithOpacity, + selectedColor: Colors.white, + fillColor: ColorsManager.primaryColorWithOpacity, + color: ColorsManager.primaryColorWithOpacity, + constraints: const BoxConstraints( + minHeight: 40.0, + minWidth: 40.0, + ), + isSelected: selectedConditions, + children: const [Text("<"), Text("="), Text(">")], + ); + }, + ), + const SizedBox(height: 20), + Text( + '${value ?? 0} sec', + style: Theme.of(context).textTheme.headlineMedium, + ), + const SizedBox(height: 20), + Slider( + value: (value ?? 0).toDouble(), + min: 0, + max: 300, + divisions: 300, + onChanged: (newValue) { + valueNotifier.value = newValue.toInt(); + }, + ), + ], + ); + }, ); } - /// Build condition toggle for AC functions dialog - static Widget _buildConditionToggle( + static Widget _buildOperationalValuesList( BuildContext context, - StateSetter setState, - List selectedConditions, - Function(String?) onConditionSelected, + BaseSwitchFunction function, + ValueNotifier valueNotifier, ) { - return ToggleButtons( - onPressed: (int index) { - setState(() { - for (int i = 0; i < selectedConditions.length; i++) { - selectedConditions[i] = i == index; - } - onConditionSelected(index == 0 - ? "<" - : index == 1 - ? "==" - : ">"); - }); + final values = function.getOperationalValues(); + return ValueListenableBuilder( + valueListenable: valueNotifier, + builder: (context, selectedValue, _) { + return ListView.builder( + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Radio( + value: value.value, + groupValue: selectedValue, + onChanged: (newValue) { + valueNotifier.value = newValue; + }, + ), + ); + }, + ); }, - borderRadius: const BorderRadius.all(Radius.circular(8)), - selectedBorderColor: ColorsManager.primaryColorWithOpacity, - selectedColor: Colors.white, - fillColor: ColorsManager.primaryColorWithOpacity, - color: ColorsManager.primaryColorWithOpacity, - constraints: const BoxConstraints( - minHeight: 40.0, - minWidth: 40.0, - ), - isSelected: selectedConditions, - children: const [Text("<"), Text("="), Text(">")], ); } } diff --git a/lib/pages/routiens/helper/two_gang_switch_helper.dart b/lib/pages/routiens/helper/two_gang_switch_helper.dart index 6f2febc7..ae6abac4 100644 --- a/lib/pages/routiens/helper/two_gang_switch_helper.dart +++ b/lib/pages/routiens/helper/two_gang_switch_helper.dart @@ -5,6 +5,8 @@ import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -18,20 +20,23 @@ class TwoGangSwitchHelper { f is TwoGangCountdown1Function || f is TwoGangCountdown2Function) .toList(); - Map selectedValues = {}; - List selectedFunctions = []; - String? selectedCondition = "<"; - List selectedConditions = [true, false, false]; + + final selectedFunctionNotifier = ValueNotifier(null); + final selectedValueNotifier = ValueNotifier(null); + final selectedConditionNotifier = ValueNotifier('<'); + final selectedConditionsNotifier = + ValueNotifier>([true, false, false]); await showDialog( context: context, builder: (BuildContext context) { - return StatefulBuilder( - builder: (context, setState) { + return ValueListenableBuilder( + valueListenable: selectedFunctionNotifier, + builder: (context, selectedFunction, _) { return AlertDialog( contentPadding: EdgeInsets.zero, content: Container( - width: 600, + width: selectedFunction != null ? 600 : 300, height: 450, decoration: BoxDecoration( color: Colors.white, @@ -41,22 +46,7 @@ class TwoGangSwitchHelper { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text( - '2 Gangs Light Switch Condition', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - fontWeight: FontWeight.bold, - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 15, horizontal: 50), - child: Container( - height: 1, - width: double.infinity, - color: ColorsManager.greyColor, - ), - ), + const DialogHeader('2 Gangs Light Switch Condition'), Expanded( child: Row( children: [ @@ -69,187 +59,105 @@ class TwoGangSwitchHelper { ), itemBuilder: (context, index) { final function = switchFunctions[index]; - final isSelected = - selectedValues.containsKey(function.code); - return ListTile( - tileColor: - isSelected ? Colors.grey.shade100 : null, - leading: SvgPicture.asset( - function.icon, - width: 24, - height: 24, - ), - title: Text( - function.operationName, - style: context.textTheme.bodyMedium, - ), - trailing: isSelected - ? Icon( - Icons.check_circle, - color: ColorsManager - .primaryColorWithOpacity, - size: 20, - ) - : const Icon( - Icons.arrow_forward_ios, - size: 16, - color: ColorsManager.textGray, - ), - onTap: () { - if (isSelected) { - selectedValues.remove(function.code); - selectedFunctions.removeWhere( - (f) => f.function == function.code); - } - (context as Element).markNeedsBuild(); - }, - ); - }, - ), - ), - // Right side: Value selector - Expanded( - child: Builder( - builder: (context) { - final selectedFn = switchFunctions.firstWhere( - (f) => selectedValues.containsKey(f.code), - orElse: () => switchFunctions.first, - ) as BaseSwitchFunction; - - if (selectedFn is TwoGangCountdown1Function || - selectedFn is TwoGangCountdown2Function) { - return _buildCountDownSelector( - context, - setState, - selectedValues[selectedFn.code] ?? 0, - selectedCondition, - selectedConditions, - (value) { - selectedValues[selectedFn.code] = value; - final functionData = DeviceFunctionData( - entityId: selectedFn.deviceId, - function: selectedFn.code, - operationName: selectedFn.operationName, - value: value, - condition: selectedCondition, - valueDescription: '${value} sec', - ); - - final existingIndex = - selectedFunctions.indexWhere((f) => - f.function == selectedFn.code); - if (existingIndex != -1) { - selectedFunctions[existingIndex] = - functionData; - } else { - selectedFunctions.add(functionData); - } - (context as Element).markNeedsBuild(); - }, - (condition) { - setState(() { - selectedCondition = condition; - }); - }, - ); - } - - final values = - selectedFn.getOperationalValues(); - return ListView.builder( - itemCount: values.length, - itemBuilder: (context, index) { - final value = values[index]; + return ValueListenableBuilder( + valueListenable: selectedFunctionNotifier, + builder: (context, selectedFunction, _) { + final isSelected = + selectedFunction == function.code; return ListTile( + tileColor: isSelected + ? Colors.grey.shade100 + : null, leading: SvgPicture.asset( - value.icon, + function.icon, width: 24, height: 24, ), title: Text( - value.description, + function.operationName, style: context.textTheme.bodyMedium, ), - trailing: Radio( - value: value.value, - groupValue: - selectedValues[selectedFn.code], - onChanged: (newValue) { - selectedValues[selectedFn.code] = - newValue; - final functionData = - DeviceFunctionData( - entityId: selectedFn.deviceId, - function: selectedFn.code, - operationName: - selectedFn.operationName, - value: newValue, - valueDescription: value.description, - ); - - final existingIndex = - selectedFunctions.indexWhere( - (f) => - f.function == - selectedFn.code); - if (existingIndex != -1) { - selectedFunctions[existingIndex] = - functionData; - } else { - selectedFunctions.add(functionData); - } - (context as Element).markNeedsBuild(); - }, + trailing: const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, ), + onTap: () { + selectedFunctionNotifier.value = + function.code; + selectedValueNotifier.value = function + is TwoGangCountdown1Function || + function + is TwoGangCountdown2Function + ? 0 + : null; + }, ); }, ); }, ), ), + // Right side: Value selector + if (selectedFunction != null) + Expanded( + child: ValueListenableBuilder( + valueListenable: selectedValueNotifier, + builder: (context, selectedValue, _) { + final selectedFn = switchFunctions.firstWhere( + (f) => f.code == selectedFunction, + ); + + if (selectedFn is TwoGangCountdown1Function || + selectedFn is TwoGangCountdown2Function) { + return _buildCountDownSelector( + context, + selectedValueNotifier, + selectedConditionNotifier, + selectedConditionsNotifier, + ); + } + + return _buildOperationalValuesList( + context, + selectedFn as BaseSwitchFunction, + selectedValueNotifier, + ); + }, + ), + ), ], ), ), - Container( - height: 1, - width: double.infinity, - color: ColorsManager.greyColor, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text( - 'Cancel', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: ColorsManager.greyColor), - ), - ), - TextButton( - onPressed: selectedFunctions.isNotEmpty - ? () { - for (final function in selectedFunctions) { - context - .read() - .add(AddFunction(function)); - } - Navigator.pop(context, true); - } - : null, - child: Text( - 'Confirm', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), - ), - ), - ], + DialogFooter( + onCancel: () => Navigator.pop(context), + onConfirm: selectedFunction != null && + selectedValueNotifier.value != null + ? () { + final selectedFn = switchFunctions.firstWhere( + (f) => f.code == selectedFunction, + ); + final value = selectedValueNotifier.value; + final functionData = DeviceFunctionData( + entityId: selectedFn.deviceId, + function: selectedFn.code, + operationName: selectedFn.operationName, + value: value, + condition: selectedConditionNotifier.value, + valueDescription: selectedFn + is TwoGangCountdown1Function || + selectedFn is TwoGangCountdown2Function + ? '${value} sec' + : ((selectedFn as BaseSwitchFunction) + .getOperationalValues() + .firstWhere((v) => v.value == value) + .description), + ); + Navigator.pop( + context, {selectedFn.code: functionData}); + } + : null, + isConfirmEnabled: selectedFunction != null, ), ], ), @@ -261,77 +169,101 @@ class TwoGangSwitchHelper { ); } - /// Build countdown selector for switch functions dialog - static Widget _buildCountDownSelector( + static Widget _buildOperationalValuesList( BuildContext context, - StateSetter setState, - dynamic selectedValue, - String? selectedCondition, - List selectedConditions, - Function(dynamic) onValueSelected, - Function(String?) onConditionSelected, + BaseSwitchFunction function, + ValueNotifier valueNotifier, ) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _buildConditionToggle( - context, - setState, - selectedConditions, - onConditionSelected, - ), - const SizedBox(height: 20), - Text( - '${selectedValue.toString()} sec', - style: Theme.of(context).textTheme.headlineMedium, - ), - const SizedBox(height: 20), - Slider( - value: selectedValue.toDouble(), - min: 0, - max: 300, // 5 minutes in seconds - divisions: 300, - onChanged: (value) { - setState(() { - onValueSelected(value.toInt()); - }); + final values = function.getOperationalValues(); + return ValueListenableBuilder( + valueListenable: valueNotifier, + builder: (context, selectedValue, _) { + return ListView.builder( + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Radio( + value: value.value, + groupValue: selectedValue, + onChanged: (newValue) { + valueNotifier.value = newValue; + }, + ), + ); }, - ), - ], + ); + }, ); } - /// Build condition toggle for AC functions dialog - static Widget _buildConditionToggle( + static Widget _buildCountDownSelector( BuildContext context, - StateSetter setState, - List selectedConditions, - Function(String?) onConditionSelected, + ValueNotifier valueNotifier, + ValueNotifier conditionNotifier, + ValueNotifier> conditionsNotifier, ) { - return ToggleButtons( - onPressed: (int index) { - setState(() { - for (int i = 0; i < selectedConditions.length; i++) { - selectedConditions[i] = i == index; - } - onConditionSelected(index == 0 - ? "<" - : index == 1 - ? "==" - : ">"); - }); + return ValueListenableBuilder( + valueListenable: valueNotifier, + builder: (context, value, _) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ValueListenableBuilder>( + valueListenable: conditionsNotifier, + builder: (context, selectedConditions, _) { + return ToggleButtons( + onPressed: (int index) { + final newConditions = List.filled(3, false); + newConditions[index] = true; + conditionsNotifier.value = newConditions; + conditionNotifier.value = index == 0 + ? "<" + : index == 1 + ? "==" + : ">"; + }, + borderRadius: const BorderRadius.all(Radius.circular(8)), + selectedBorderColor: ColorsManager.primaryColorWithOpacity, + selectedColor: Colors.white, + fillColor: ColorsManager.primaryColorWithOpacity, + color: ColorsManager.primaryColorWithOpacity, + constraints: const BoxConstraints( + minHeight: 40.0, + minWidth: 40.0, + ), + isSelected: selectedConditions, + children: const [Text("<"), Text("="), Text(">")], + ); + }, + ), + const SizedBox(height: 20), + Text( + '${value ?? 0} sec', + style: Theme.of(context).textTheme.headlineMedium, + ), + const SizedBox(height: 20), + Slider( + value: (value ?? 0).toDouble(), + min: 0, + max: 300, + divisions: 300, + onChanged: (newValue) { + valueNotifier.value = newValue.toInt(); + }, + ), + ], + ); }, - borderRadius: const BorderRadius.all(Radius.circular(8)), - selectedBorderColor: ColorsManager.primaryColorWithOpacity, - selectedColor: Colors.white, - fillColor: ColorsManager.primaryColorWithOpacity, - color: ColorsManager.primaryColorWithOpacity, - constraints: const BoxConstraints( - minHeight: 40.0, - minWidth: 40.0, - ), - isSelected: selectedConditions, - children: const [Text("<"), Text("="), Text(">")], ); } } diff --git a/lib/pages/routiens/view/create_new_routine_view.dart b/lib/pages/routiens/view/create_new_routine_view.dart index 99b4df71..c81e878f 100644 --- a/lib/pages/routiens/view/create_new_routine_view.dart +++ b/lib/pages/routiens/view/create_new_routine_view.dart @@ -12,82 +12,79 @@ class CreateNewRoutineView extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => RoutineBloc(), - child: Container( - alignment: Alignment.topLeft, - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const RoutineSearchAndButtons(), - const SizedBox(height: 20), - Flexible( - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: Card( - child: Container( - decoration: BoxDecoration( - color: ColorsManager.whiteColors, - borderRadius: BorderRadius.circular(15), - ), - child: const ConditionsRoutinesDevicesView()), - ), + return Container( + alignment: Alignment.topLeft, + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const RoutineSearchAndButtons(), + const SizedBox(height: 20), + Flexible( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Card( + child: Container( + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(15), + ), + child: const ConditionsRoutinesDevicesView()), ), - const SizedBox( - width: 10, - ), - Expanded( - child: Column( - children: [ - /// IF Container - Expanded( - child: Card( - margin: EdgeInsets.zero, - child: Container( - decoration: const BoxDecoration( - color: ColorsManager.whiteColors, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(15), - topRight: Radius.circular(15), - ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: Column( + children: [ + /// IF Container + Expanded( + child: Card( + margin: EdgeInsets.zero, + child: Container( + decoration: const BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(15), + topRight: Radius.circular(15), ), - child: const IfContainer(), ), + child: const IfContainer(), ), ), - Container( - height: 2, - width: double.infinity, - color: ColorsManager.dialogBlueTitle, - ), + ), + Container( + height: 2, + width: double.infinity, + color: ColorsManager.dialogBlueTitle, + ), - /// THEN Container - Expanded( - child: Card( - margin: EdgeInsets.zero, - child: Container( - decoration: const BoxDecoration( - color: ColorsManager.boxColor, - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(15), - bottomRight: Radius.circular(15), - ), + /// THEN Container + Expanded( + child: Card( + margin: EdgeInsets.zero, + child: Container( + decoration: const BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(15), + bottomRight: Radius.circular(15), ), - child: const ThenContainer(), ), + child: const ThenContainer(), ), ), - ], - ), + ), + ], ), - ], - ), + ), + ], ), - ], - ), + ), + ], ), ); } diff --git a/lib/pages/routiens/view/routines_view.dart b/lib/pages/routiens/view/routines_view.dart index 5a19db08..b229dd62 100644 --- a/lib/pages/routiens/view/routines_view.dart +++ b/lib/pages/routiens/view/routines_view.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class RoutinesView extends StatelessWidget { @@ -8,54 +10,67 @@ class RoutinesView extends StatelessWidget { @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text("Create New Routines", - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.bold, - color: ColorsManager.grayColor, - )), - SizedBox( - height: 200, - width: 150, - child: GestureDetector( - onTap: () { - BlocProvider.of(context).add( - const CreateNewRoutineViewEvent(true), - ); - }, - child: Card( - elevation: 3, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - color: ColorsManager.whiteColors, - child: Center( - child: Container( - decoration: BoxDecoration( - color: ColorsManager.graysColor, - borderRadius: BorderRadius.circular(120), - border: Border.all(color: ColorsManager.greyColor, width: 2.0), - ), - height: 70, - width: 70, - child: Icon( - Icons.add, - color: ColorsManager.dialogBlueTitle, - size: 40, - ), - )), + return BlocBuilder( + builder: (context, state) { + if (state is ShowCreateRoutineState && state.showCreateRoutine) { + return const CreateNewRoutineView(); + } + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Create New Routines", + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + color: ColorsManager.grayColor, + ), ), - ), + SizedBox( + height: 200, + width: 150, + child: GestureDetector( + onTap: () { + BlocProvider.of(context).add( + const CreateNewRoutineViewEvent(true), + ); + }, + child: Card( + elevation: 3, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + color: ColorsManager.whiteColors, + child: Center( + child: Container( + decoration: BoxDecoration( + color: ColorsManager.graysColor, + borderRadius: BorderRadius.circular(120), + border: Border.all( + color: ColorsManager.greyColor, + width: 2.0, + ), + ), + height: 70, + width: 70, + child: Icon( + Icons.add, + color: ColorsManager.dialogBlueTitle, + size: 40, + ), + ), + ), + ), + ), + ), + const Spacer(), + ], ), - const Spacer(), - ], - ), + ); + }, ); } } diff --git a/lib/pages/routiens/widgets/conditions_routines_devices_view.dart b/lib/pages/routiens/widgets/conditions_routines_devices_view.dart index 91100220..2728d329 100644 --- a/lib/pages/routiens/widgets/conditions_routines_devices_view.dart +++ b/lib/pages/routiens/widgets/conditions_routines_devices_view.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; import 'package:syncrow_web/pages/routiens/widgets/routine_devices.dart'; import 'package:syncrow_web/pages/routiens/widgets/routines_title_widget.dart'; @@ -11,87 +13,107 @@ class ConditionsRoutinesDevicesView extends StatelessWidget { @override Widget build(BuildContext context) { - return Padding( - padding: EdgeInsets.symmetric(horizontal: 8.0), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const ConditionTitleAndSearchBar(), - const SizedBox( - height: 10, - ), - const Wrap( - spacing: 10, - runSpacing: 10, + return BlocBuilder( + builder: (context, state) { + return const Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - DraggableCard( - imagePath: Assets.tabToRun, - title: 'Tab to run', + ConditionTitleAndSearchBar(), + SizedBox(height: 10), + Wrap( + spacing: 10, + runSpacing: 10, + children: [ + DraggableCard( + imagePath: Assets.tabToRun, + title: 'Tab to run', + deviceData: { + 'deviceId': 'tab_to_run', + 'type': 'trigger', + 'name': 'Tab to run', + }, + ), + DraggableCard( + imagePath: Assets.map, + title: 'Location', + deviceData: { + 'deviceId': 'location', + 'type': 'trigger', + 'name': 'Location', + }, + ), + DraggableCard( + imagePath: Assets.weather, + title: 'Weather', + deviceData: { + 'deviceId': 'weather', + 'type': 'trigger', + 'name': 'Weather', + }, + ), + DraggableCard( + imagePath: Assets.schedule, + title: 'Schedule', + deviceData: { + 'deviceId': 'schedule', + 'type': 'trigger', + 'name': 'Schedule', + }, + ), + ], ), - DraggableCard( - imagePath: Assets.map, - title: 'Location', + const SizedBox(height: 10), + const TitleRoutine( + title: 'Conditions', + subtitle: '(THEN)', ), - DraggableCard( - imagePath: Assets.weather, - title: 'Weather', + const SizedBox(height: 10), + const Wrap( + spacing: 10, + runSpacing: 10, + children: [ + DraggableCard( + imagePath: Assets.notification, + title: 'Send Notification', + deviceData: { + 'deviceId': 'notification', + 'type': 'action', + 'name': 'Send Notification', + }, + ), + DraggableCard( + imagePath: Assets.delay, + title: 'Delay the action', + deviceData: { + 'deviceId': 'delay', + 'type': 'action', + 'name': 'Delay the action', + }, + ), + ], ), - DraggableCard( - imagePath: Assets.schedule, - title: 'Schedule', + const SizedBox(height: 10), + const TitleRoutine( + title: 'Routines', + subtitle: '(THEN)', ), + const SizedBox(height: 10), + const ScenesAndAutomations(), + const SizedBox(height: 10), + const TitleRoutine( + title: 'Devices', + subtitle: '', + ), + const SizedBox(height: 10), + const RoutineDevices(), ], ), - const SizedBox( - height: 10, - ), - const TitleRoutine( - title: 'Conditions', - subtitle: '(THEN)', - ), - const SizedBox( - height: 10, - ), - const Wrap( - spacing: 10, - runSpacing: 10, - children: [ - DraggableCard( - imagePath: Assets.notification, - title: 'Send Notification', - ), - DraggableCard( - imagePath: Assets.delay, - title: 'Delay the action', - ), - ], - ), - const SizedBox( - height: 10, - ), - const TitleRoutine( - title: 'Routines', - subtitle: '(THEN)', - ), - const SizedBox( - height: 10, - ), - const ScenesAndAutomations(), - const SizedBox( - height: 10, - ), - const TitleRoutine( - title: 'Devices', - subtitle: '', - ), - const SizedBox( - height: 10, - ), - const RoutineDevices(), - ], - ), - ), + ), + ); + }, ); } } diff --git a/lib/pages/routiens/widgets/dialog_footer.dart b/lib/pages/routiens/widgets/dialog_footer.dart new file mode 100644 index 00000000..90c6baec --- /dev/null +++ b/lib/pages/routiens/widgets/dialog_footer.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DialogFooter extends StatelessWidget { + final VoidCallback onCancel; + final VoidCallback? onConfirm; + final bool isConfirmEnabled; + + const DialogFooter({ + Key? key, + required this.onCancel, + required this.onConfirm, + required this.isConfirmEnabled, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + decoration: const BoxDecoration( + border: Border( + top: BorderSide( + color: ColorsManager.greyColor, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _buildFooterButton( + context, + 'Cancel', + onCancel, + width: isConfirmEnabled ? 299 : 179, + ), + if (isConfirmEnabled) + Row( + children: [ + Container(width: 1, height: 50, color: ColorsManager.greyColor), + _buildFooterButton( + context, + 'Confirm', + onConfirm, + width: 299, + ), + ], + ), + ], + ), + ); + } + + Widget _buildFooterButton( + BuildContext context, + String text, + VoidCallback? onTap, { + required double width, + }) { + return GestureDetector( + onTap: onTap, + child: SizedBox( + height: 50, + width: width, + child: Center( + child: Text( + text, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: text == 'Confirm' + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.textGray, + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/routiens/widgets/dialog_header.dart b/lib/pages/routiens/widgets/dialog_header.dart new file mode 100644 index 00000000..6701b5b0 --- /dev/null +++ b/lib/pages/routiens/widgets/dialog_header.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DialogHeader extends StatelessWidget { + final String title; + + const DialogHeader(this.title, {super.key}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text( + title, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontWeight.bold, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 50), + child: Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + ), + ], + ); + } +} diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index 7ee078ff..227e440b 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -2,156 +2,139 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class DraggableCard extends StatelessWidget { + final String imagePath; + final String title; + final Map deviceData; + const DraggableCard({ super.key, required this.imagePath, required this.title, - this.titleColor, - this.isDragged = false, - this.isDisabled = false, - this.deviceData, + required this.deviceData, }); - final String imagePath; - final String title; - final Color? titleColor; - final bool isDragged; - final bool isDisabled; - final Map? deviceData; - @override Widget build(BuildContext context) { - Widget card = Draggable>( - data: deviceData ?? - { - 'key': UniqueKey().toString(), - 'imagePath': imagePath, - 'title': title, - }, - feedback: Transform.rotate( - angle: -0.1, - child: _buildCardContent(context), - ), - childWhenDragging: _buildGreyContainer(), - child: _buildCardContent(context), - ); - - if (isDisabled) { - card = AbsorbPointer(child: card); - } - - return card; - } - - Widget _buildCardContent(BuildContext context) { return BlocBuilder( builder: (context, state) { - // Filter functions for this device final deviceFunctions = state.selectedFunctions - .where((f) => f.entityId == deviceData?['deviceId']) + .where((f) => f.entityId == deviceData['deviceId']) .toList(); - return Card( - color: ColorsManager.whiteColors, - child: SizedBox( - width: 90, - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - height: 123, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - height: 50, - width: 50, - decoration: BoxDecoration( - color: ColorsManager.CircleImageBackground, - borderRadius: BorderRadius.circular(90), - border: Border.all( - color: ColorsManager.graysColor, - ), - ), - padding: const EdgeInsets.all(8), - child: imagePath.contains('.svg') - ? SvgPicture.asset( - imagePath, - ) - : Image.network(imagePath), + return Draggable>( + data: deviceData, + feedback: Transform.rotate( + angle: -0.1, + child: _buildCardContent(context, deviceFunctions), + ), + childWhenDragging: _buildGreyContainer(), + child: _buildCardContent(context, deviceFunctions), + ); + }, + ); + } + + Widget _buildCardContent( + BuildContext context, List deviceFunctions) { + return Card( + color: ColorsManager.whiteColors, + child: SizedBox( + width: 90, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + height: 123, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + height: 50, + width: 50, + decoration: BoxDecoration( + color: ColorsManager.CircleImageBackground, + borderRadius: BorderRadius.circular(90), + border: Border.all( + color: ColorsManager.graysColor, ), - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 3), + ), + padding: const EdgeInsets.all(8), + child: imagePath.contains('.svg') + ? SvgPicture.asset( + imagePath, + ) + : Image.network(imagePath), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 3), + child: Text( + title, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontSize: 12, + ), + ), + ), + ], + ), + ), + if (deviceFunctions.isNotEmpty) ...[ + const Divider(height: 1), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 4), + itemCount: deviceFunctions.length, + itemBuilder: (context, index) { + final function = deviceFunctions[index]; + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( child: Text( - title, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 2, + '${function.operationName}: ${function.valueDescription}', style: context.textTheme.bodySmall?.copyWith( - color: titleColor ?? ColorsManager.blackColor, - fontSize: 12, + fontSize: 9, + color: ColorsManager.textGray, + height: 1.2, + ), + overflow: TextOverflow.ellipsis, + ), + ), + InkWell( + onTap: () { + context.read().add( + RemoveFunction(function), + ); + }, + child: const Padding( + padding: EdgeInsets.all(2), + child: Icon( + Icons.close, + size: 12, + color: ColorsManager.textGray, ), ), ), ], - ), - ), - if (deviceFunctions.isNotEmpty) ...[ - const Divider(height: 1), - ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - padding: - const EdgeInsets.symmetric(vertical: 4, horizontal: 4), - itemCount: deviceFunctions.length, - itemBuilder: (context, index) { - final function = deviceFunctions[index]; - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: Text( - '${function.operationName}: ${function.valueDescription}', - style: context.textTheme.bodySmall?.copyWith( - fontSize: 9, - color: ColorsManager.textGray, - height: 1.2, - ), - overflow: TextOverflow.ellipsis, - ), - ), - InkWell( - onTap: () { - context.read().add( - RemoveFunction(function), - ); - }, - child: const Padding( - padding: EdgeInsets.all(2), - child: Icon( - Icons.close, - size: 12, - color: ColorsManager.textGray, - ), - ), - ), - ], - ); - }, - ), - ], - ], - ), - ), - ); - }, + ); + }, + ), + ], + ], + ), + ), ); } diff --git a/lib/pages/routiens/widgets/if_container.dart b/lib/pages/routiens/widgets/if_container.dart index ba0d67ab..af1394c6 100644 --- a/lib/pages/routiens/widgets/if_container.dart +++ b/lib/pages/routiens/widgets/if_container.dart @@ -31,6 +31,7 @@ class IfContainer extends StatelessWidget { key: Key(item['key']!), imagePath: item['imagePath']!, title: item['title']!, + deviceData: item, )) .toList(), ), diff --git a/lib/pages/routiens/widgets/routine_devices.dart b/lib/pages/routiens/widgets/routine_devices.dart index c845af19..7d97de18 100644 --- a/lib/pages/routiens/widgets/routine_devices.dart +++ b/lib/pages/routiens/widgets/routine_devices.dart @@ -1,20 +1,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; class RoutineDevices extends StatelessWidget { - const RoutineDevices({ - super.key, - }); + const RoutineDevices({super.key}); @override Widget build(BuildContext context) { + // Get the RoutineBloc instance from the parent + final routineBloc = context.read(); + return BlocProvider( - create: (context) => DeviceManagementBloc() - ..add( - FetchDevices(), - ), + create: (context) => DeviceManagementBloc()..add(FetchDevices()), child: BlocBuilder( builder: (context, state) { if (state is DeviceManagementLoaded) { @@ -25,24 +24,34 @@ class RoutineDevices extends StatelessWidget { device.productType == '2G' || device.productType == '3G') .toList(); - return Wrap( - spacing: 10, - runSpacing: 10, - children: deviceList.asMap().entries.map((entry) { - final device = entry.value; - return DraggableCard( - imagePath: device.getDefaultIcon(device.productType), - title: device.name ?? '', - deviceData: { - 'key': UniqueKey().toString(), - 'imagePath': device.getDefaultIcon(device.productType), - 'title': device.name ?? '', - 'deviceId': device.uuid, - 'productType': device.productType, - 'functions': device.functions, - }, - ); - }).toList(), + + // Provide the RoutineBloc to the child widgets + return BlocProvider.value( + value: routineBloc, + child: BlocBuilder( + builder: (context, routineState) { + return Wrap( + spacing: 10, + runSpacing: 10, + children: deviceList.asMap().entries.map((entry) { + final device = entry.value; + return DraggableCard( + imagePath: device.getDefaultIcon(device.productType), + title: device.name ?? '', + deviceData: { + 'key': UniqueKey().toString(), + 'imagePath': + device.getDefaultIcon(device.productType), + 'title': device.name ?? '', + 'deviceId': device.uuid, + 'productType': device.productType, + 'functions': device.functions, + }, + ); + }).toList(), + ); + }, + ), ); } return const Center(child: CircularProgressIndicator()); diff --git a/lib/pages/routiens/widgets/scenes_and_automations.dart b/lib/pages/routiens/widgets/scenes_and_automations.dart index b455b2f5..ef40b605 100644 --- a/lib/pages/routiens/widgets/scenes_and_automations.dart +++ b/lib/pages/routiens/widgets/scenes_and_automations.dart @@ -4,40 +4,51 @@ import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; -class ScenesAndAutomations extends StatelessWidget { +class ScenesAndAutomations extends StatefulWidget { const ScenesAndAutomations({ super.key, }); + @override + State createState() => _ScenesAndAutomationsState(); +} + +class _ScenesAndAutomationsState extends State { + @override + void initState() { + super.initState(); + context.read() + ..add(const LoadScenes(spaceId)) + ..add(const LoadAutomation(spaceId)); + } + @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => RoutineBloc() - ..add( - const LoadScenes(spaceId), - ) - ..add( - const LoadAutomation(spaceId), - ), - child: BlocBuilder( - builder: (context, state) { - if (state.scenes.isNotEmpty || state.automations.isNotEmpty) { - var scenes = [...state.scenes, ...state.automations]; - return Wrap( - spacing: 10, - runSpacing: 10, - children: scenes.asMap().entries.map((entry) { - final scene = entry.value; - return DraggableCard( - imagePath: Assets.logo, - title: scene.name, - ); - }).toList(), - ); - } - return const Center(child: CircularProgressIndicator()); - }, - ), + return BlocBuilder( + builder: (context, state) { + if (state.scenes.isNotEmpty || state.automations.isNotEmpty) { + var scenes = [...state.scenes, ...state.automations]; + return Wrap( + spacing: 10, + runSpacing: 10, + children: scenes.asMap().entries.map((entry) { + final scene = entry.value; + return DraggableCard( + imagePath: Assets.logo, + title: scene.name, + deviceData: { + 'deviceId': scene.id, + 'name': scene.name, + 'status': scene.status, + 'type': scene.type, + 'icon': scene.icon, + }, + ); + }).toList(), + ); + } + return const Center(child: CircularProgressIndicator()); + }, ); } } diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index 9bd7fd00..385ec4cb 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -33,6 +33,7 @@ class ThenContainer extends StatelessWidget { key: Key(item['key']!), imagePath: item['imagePath']!, title: item['title']!, + deviceData: item, )) .toList(), ), From a52604360fd656dfb249ae9cadb4902665b7e165 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Fri, 22 Nov 2024 20:07:41 +0300 Subject: [PATCH 09/40] dispose value notfiers --- .../helper/one_gang_switch_helper.dart | 12 +- .../helper/three_gang_switch_helper.dart | 445 ++++++++---------- .../helper/two_gang_switch_helper.dart | 10 +- .../view/create_new_routine_view.dart | 2 - lib/pages/routiens/view/routines_view.dart | 1 - 5 files changed, 208 insertions(+), 262 deletions(-) diff --git a/lib/pages/routiens/helper/one_gang_switch_helper.dart b/lib/pages/routiens/helper/one_gang_switch_helper.dart index 0837829b..014e41f9 100644 --- a/lib/pages/routiens/helper/one_gang_switch_helper.dart +++ b/lib/pages/routiens/helper/one_gang_switch_helper.dart @@ -151,9 +151,7 @@ class OneGangSwitchHelper { context, {selectedFn.code: functionData}); } : null, - isConfirmEnabled: - selectedFunctionNotifier.value != null && - selectedValueNotifier.value != null, + isConfirmEnabled: selectedFunctionNotifier.value != null, ), ], ), @@ -162,7 +160,13 @@ class OneGangSwitchHelper { }, ); }, - ); + ).then((value) { + selectedFunctionNotifier.dispose(); + selectedValueNotifier.dispose(); + selectedConditionNotifier.dispose(); + selectedConditionsNotifier.dispose(); + return value; + }); } static Widget _buildCountDownSelector( diff --git a/lib/pages/routiens/helper/three_gang_switch_helper.dart b/lib/pages/routiens/helper/three_gang_switch_helper.dart index 56d1f3bb..1e62f5c4 100644 --- a/lib/pages/routiens/helper/three_gang_switch_helper.dart +++ b/lib/pages/routiens/helper/three_gang_switch_helper.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -20,20 +20,23 @@ class ThreeGangSwitchHelper { f is ThreeGangCountdown2Function || f is ThreeGangCountdown3Function) .toList(); - Map selectedValues = {}; - List selectedFunctions = []; - String? selectedCondition = "<"; - List selectedConditions = [true, false, false]; + + final selectedFunctionNotifier = ValueNotifier(null); + final selectedValueNotifier = ValueNotifier(null); + final selectedConditionNotifier = ValueNotifier('<'); + final selectedConditionsNotifier = + ValueNotifier>([true, false, false]); await showDialog( context: context, builder: (BuildContext context) { - return StatefulBuilder( - builder: (context, setState) { + return ValueListenableBuilder( + valueListenable: selectedFunctionNotifier, + builder: (context, selectedFunction, _) { return AlertDialog( contentPadding: EdgeInsets.zero, content: Container( - width: 600, + width: selectedFunction != null ? 600 : 300, height: 450, decoration: BoxDecoration( color: Colors.white, @@ -43,22 +46,7 @@ class ThreeGangSwitchHelper { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text( - '3 Gangs Light Switch Condition', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - fontWeight: FontWeight.bold, - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 15, horizontal: 50), - child: Container( - height: 1, - width: double.infinity, - color: ColorsManager.greyColor, - ), - ), + const DialogHeader('3 Gangs Light Switch Condition'), Expanded( child: Row( children: [ @@ -71,188 +59,111 @@ class ThreeGangSwitchHelper { ), itemBuilder: (context, index) { final function = switchFunctions[index]; - final isSelected = - selectedValues.containsKey(function.code); - return ListTile( - tileColor: - isSelected ? Colors.grey.shade100 : null, - leading: SvgPicture.asset( - function.icon, - width: 24, - height: 24, - ), - title: Text( - function.operationName, - style: context.textTheme.bodyMedium, - ), - trailing: isSelected - ? Icon( - Icons.check_circle, - color: ColorsManager - .primaryColorWithOpacity, - size: 20, - ) - : const Icon( - Icons.arrow_forward_ios, - size: 16, - color: ColorsManager.textGray, - ), - onTap: () { - if (isSelected) { - selectedValues.remove(function.code); - selectedFunctions.removeWhere( - (f) => f.function == function.code); - } - (context as Element).markNeedsBuild(); - }, - ); - }, - ), - ), - // Right side: Value selector - Expanded( - child: Builder( - builder: (context) { - final selectedFn = switchFunctions.firstWhere( - (f) => selectedValues.containsKey(f.code), - orElse: () => switchFunctions.first, - ) as BaseSwitchFunction; - - if (selectedFn is ThreeGangCountdown1Function || - selectedFn is ThreeGangCountdown2Function || - selectedFn is ThreeGangCountdown3Function) { - return _buildCountDownSelector( - context, - setState, - selectedValues[selectedFn.code] ?? 0, - selectedCondition, - selectedConditions, - (value) { - selectedValues[selectedFn.code] = value; - final functionData = DeviceFunctionData( - entityId: selectedFn.deviceId, - function: selectedFn.code, - operationName: selectedFn.operationName, - value: value, - condition: selectedCondition, - valueDescription: '${value} sec', - ); - - final existingIndex = - selectedFunctions.indexWhere((f) => - f.function == selectedFn.code); - if (existingIndex != -1) { - selectedFunctions[existingIndex] = - functionData; - } else { - selectedFunctions.add(functionData); - } - (context as Element).markNeedsBuild(); - }, - (condition) { - setState(() { - selectedCondition = condition; - }); - }, - ); - } - - final values = - selectedFn.getOperationalValues(); - return ListView.builder( - itemCount: values.length, - itemBuilder: (context, index) { - final value = values[index]; + return ValueListenableBuilder( + valueListenable: selectedFunctionNotifier, + builder: (context, selectedFunction, _) { + final isSelected = + selectedFunction == function.code; return ListTile( + tileColor: isSelected + ? Colors.grey.shade100 + : null, leading: SvgPicture.asset( - value.icon, + function.icon, width: 24, height: 24, ), title: Text( - value.description, + function.operationName, style: context.textTheme.bodyMedium, ), - trailing: Radio( - value: value.value, - groupValue: - selectedValues[selectedFn.code], - onChanged: (newValue) { - selectedValues[selectedFn.code] = - newValue; - final functionData = - DeviceFunctionData( - entityId: selectedFn.deviceId, - function: selectedFn.code, - operationName: - selectedFn.operationName, - value: newValue, - valueDescription: value.description, - ); - - final existingIndex = - selectedFunctions.indexWhere( - (f) => - f.function == - selectedFn.code); - if (existingIndex != -1) { - selectedFunctions[existingIndex] = - functionData; - } else { - selectedFunctions.add(functionData); - } - (context as Element).markNeedsBuild(); - }, + trailing: const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, ), + onTap: () { + selectedFunctionNotifier.value = + function.code; + selectedValueNotifier.value = function + is ThreeGangCountdown1Function || + function + is ThreeGangCountdown2Function || + function + is ThreeGangCountdown3Function + ? 0 + : null; + }, ); }, ); }, ), ), + // Right side: Value selector + if (selectedFunction != null) + Expanded( + child: ValueListenableBuilder( + valueListenable: selectedValueNotifier, + builder: (context, selectedValue, _) { + final selectedFn = switchFunctions.firstWhere( + (f) => f.code == selectedFunction, + ); + return selectedFn + is ThreeGangCountdown1Function || + selectedFn + is ThreeGangCountdown2Function || + selectedFn + is ThreeGangCountdown3Function + ? _buildCountDownSelector( + context, + selectedValueNotifier, + selectedConditionNotifier, + selectedConditionsNotifier, + ) + : _buildOperationalValuesList( + context, + selectedFn as BaseSwitchFunction, + selectedValueNotifier, + ); + }, + ), + ), ], ), ), - Container( - height: 1, - width: double.infinity, - color: ColorsManager.greyColor, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text( - 'Cancel', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: ColorsManager.greyColor), - ), - ), - TextButton( - onPressed: selectedFunctions.isNotEmpty - ? () { - for (final function in selectedFunctions) { - context - .read() - .add(AddFunction(function)); - } - Navigator.pop(context, true); - } - : null, - child: Text( - 'Confirm', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), - ), - ), - ], + DialogFooter( + onCancel: () => Navigator.pop(context), + onConfirm: selectedFunctionNotifier.value != null && + selectedValueNotifier.value != null + ? () { + final selectedFn = switchFunctions.firstWhere( + (f) => f.code == selectedFunctionNotifier.value, + ); + final value = selectedValueNotifier.value; + final functionData = DeviceFunctionData( + entityId: selectedFn.deviceId, + function: selectedFn.code, + operationName: selectedFn.operationName, + value: value, + condition: selectedConditionNotifier.value, + valueDescription: selectedFn + is ThreeGangCountdown1Function || + selectedFn + is ThreeGangCountdown2Function || + selectedFn + is ThreeGangCountdown3Function + ? '${value} sec' + : ((selectedFn as BaseSwitchFunction) + .getOperationalValues() + .firstWhere((v) => v.value == value) + .description), + ); + Navigator.pop( + context, {selectedFn.code: functionData}); + } + : null, + isConfirmEnabled: selectedFunctionNotifier.value != null, ), ], ), @@ -261,80 +172,110 @@ class ThreeGangSwitchHelper { }, ); }, - ); + ).then((value) { + selectedFunctionNotifier.dispose(); + selectedValueNotifier.dispose(); + selectedConditionNotifier.dispose(); + selectedConditionsNotifier.dispose(); + return value; + }); } - /// Build countdown selector for switch functions dialog static Widget _buildCountDownSelector( BuildContext context, - StateSetter setState, - dynamic selectedValue, - String? selectedCondition, - List selectedConditions, - Function(dynamic) onValueSelected, - Function(String?) onConditionSelected, + ValueNotifier valueNotifier, + ValueNotifier conditionNotifier, + ValueNotifier> conditionsNotifier, ) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _buildConditionToggle( - context, - setState, - selectedConditions, - onConditionSelected, - ), - const SizedBox(height: 20), - Text( - '${selectedValue.toString()} sec', - style: Theme.of(context).textTheme.headlineMedium, - ), - const SizedBox(height: 20), - Slider( - value: selectedValue.toDouble(), - min: 0, - max: 300, // 5 minutes in seconds - divisions: 300, - onChanged: (value) { - setState(() { - onValueSelected(value.toInt()); - }); - }, - ), - ], + return ValueListenableBuilder( + valueListenable: valueNotifier, + builder: (context, value, _) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ValueListenableBuilder>( + valueListenable: conditionsNotifier, + builder: (context, selectedConditions, _) { + return ToggleButtons( + onPressed: (int index) { + final newConditions = List.filled(3, false); + newConditions[index] = true; + conditionsNotifier.value = newConditions; + conditionNotifier.value = index == 0 + ? "<" + : index == 1 + ? "==" + : ">"; + }, + borderRadius: const BorderRadius.all(Radius.circular(8)), + selectedBorderColor: ColorsManager.primaryColorWithOpacity, + selectedColor: Colors.white, + fillColor: ColorsManager.primaryColorWithOpacity, + color: ColorsManager.primaryColorWithOpacity, + constraints: const BoxConstraints( + minHeight: 40.0, + minWidth: 40.0, + ), + isSelected: selectedConditions, + children: const [Text("<"), Text("="), Text(">")], + ); + }, + ), + const SizedBox(height: 20), + Text( + '${value ?? 0} sec', + style: Theme.of(context).textTheme.headlineMedium, + ), + const SizedBox(height: 20), + Slider( + value: (value ?? 0).toDouble(), + min: 0, + max: 300, + divisions: 300, + onChanged: (newValue) { + valueNotifier.value = newValue.toInt(); + }, + ), + ], + ); + }, ); } - /// Build condition toggle for AC functions dialog - static Widget _buildConditionToggle( + static Widget _buildOperationalValuesList( BuildContext context, - StateSetter setState, - List selectedConditions, - Function(String?) onConditionSelected, + BaseSwitchFunction function, + ValueNotifier valueNotifier, ) { - return ToggleButtons( - onPressed: (int index) { - setState(() { - for (int i = 0; i < selectedConditions.length; i++) { - selectedConditions[i] = i == index; - } - onConditionSelected(index == 0 - ? "<" - : index == 1 - ? "==" - : ">"); - }); + final values = function.getOperationalValues(); + return ValueListenableBuilder( + valueListenable: valueNotifier, + builder: (context, selectedValue, _) { + return ListView.builder( + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Radio( + value: value.value, + groupValue: selectedValue, + onChanged: (newValue) { + valueNotifier.value = newValue; + }, + ), + ); + }, + ); }, - borderRadius: const BorderRadius.all(Radius.circular(8)), - selectedBorderColor: ColorsManager.primaryColorWithOpacity, - selectedColor: Colors.white, - fillColor: ColorsManager.primaryColorWithOpacity, - color: ColorsManager.primaryColorWithOpacity, - constraints: const BoxConstraints( - minHeight: 40.0, - minWidth: 40.0, - ), - isSelected: selectedConditions, - children: const [Text("<"), Text("="), Text(">")], ); } } diff --git a/lib/pages/routiens/helper/two_gang_switch_helper.dart b/lib/pages/routiens/helper/two_gang_switch_helper.dart index ae6abac4..8a7e5580 100644 --- a/lib/pages/routiens/helper/two_gang_switch_helper.dart +++ b/lib/pages/routiens/helper/two_gang_switch_helper.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart'; @@ -166,7 +164,13 @@ class TwoGangSwitchHelper { }, ); }, - ); + ).then((value) { + selectedFunctionNotifier.dispose(); + selectedValueNotifier.dispose(); + selectedConditionNotifier.dispose(); + selectedConditionsNotifier.dispose(); + return value; + }); } static Widget _buildOperationalValuesList( diff --git a/lib/pages/routiens/view/create_new_routine_view.dart b/lib/pages/routiens/view/create_new_routine_view.dart index c81e878f..38750991 100644 --- a/lib/pages/routiens/view/create_new_routine_view.dart +++ b/lib/pages/routiens/view/create_new_routine_view.dart @@ -1,6 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/widgets/conditions_routines_devices_view.dart'; import 'package:syncrow_web/pages/routiens/widgets/if_container.dart'; import 'package:syncrow_web/pages/routiens/widgets/routine_search_and_buttons.dart'; diff --git a/lib/pages/routiens/view/routines_view.dart b/lib/pages/routiens/view/routines_view.dart index b229dd62..27690a9c 100644 --- a/lib/pages/routiens/view/routines_view.dart +++ b/lib/pages/routiens/view/routines_view.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart'; import 'package:syncrow_web/utils/color_manager.dart'; From 7e2f6054660996e44108066d07248176a7d29dd4 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Fri, 22 Nov 2024 21:38:39 +0300 Subject: [PATCH 10/40] push design changes --- lib/pages/routiens/helper/ac_helper.dart | 446 ++++++++++++----------- 1 file changed, 228 insertions(+), 218 deletions(-) diff --git a/lib/pages/routiens/helper/ac_helper.dart b/lib/pages/routiens/helper/ac_helper.dart index 9ba0b224..d940f4e4 100644 --- a/lib/pages/routiens/helper/ac_helper.dart +++ b/lib/pages/routiens/helper/ac_helper.dart @@ -13,210 +13,203 @@ class ACHelper { List> functions, ) async { List acFunctions = functions.whereType().toList(); - String? selectedFunction; - dynamic selectedValue = 20; - String? selectedCondition = "=="; - List _selectedConditions = [false, true, false]; + final selectedFunctionNotifier = ValueNotifier(null); + final selectedValueNotifier = ValueNotifier(null); + final selectedConditionNotifier = ValueNotifier('=='); + final selectedConditionsNotifier = + ValueNotifier>([false, true, false]); return showDialog?>( context: context, builder: (BuildContext context) { - return StatefulBuilder( - builder: (context, setState) { + return ValueListenableBuilder( + valueListenable: selectedFunctionNotifier, + builder: (context, selectedFunction, _) { return AlertDialog( contentPadding: EdgeInsets.zero, - content: _buildDialogContent( - context, - setState, - acFunctions, - selectedFunction, - selectedValue, - selectedCondition, - _selectedConditions, - (fn) => selectedFunction = fn, - (val) => selectedValue = val, - (cond) => selectedCondition = cond, + content: Container( + width: selectedFunction != null ? 600 : 360, + height: 450, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const DialogHeader('AC Functions'), + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Function list + SizedBox( + width: selectedFunction != null ? 320 : 360, + child: _buildFunctionsList( + context, + acFunctions, + selectedFunctionNotifier, + ), + ), + // Value selector + if (selectedFunction != null) + Expanded( + child: _buildValueSelector( + context, + selectedFunction, + selectedValueNotifier, + selectedConditionNotifier, + selectedConditionsNotifier, + acFunctions, + ), + ), + ], + ), + ), + DialogFooter( + onCancel: () { + selectedFunctionNotifier.dispose(); + selectedValueNotifier.dispose(); + selectedConditionNotifier.dispose(); + selectedConditionsNotifier.dispose(); + Navigator.pop(context); + }, + onConfirm: selectedFunctionNotifier.value != null && + selectedValueNotifier.value != null + ? () { + selectedFunctionNotifier.dispose(); + selectedValueNotifier.dispose(); + selectedConditionNotifier.dispose(); + selectedConditionsNotifier.dispose(); + Navigator.pop(context, { + 'function': selectedFunctionNotifier.value, + 'value': selectedValueNotifier.value, + 'condition': + selectedConditionNotifier.value ?? "==", + }); + } + : null, + isConfirmEnabled: selectedFunction != null, + ), + ], + ), ), ); }, ); }, - ); - } - - /// Build dialog content for AC functions dialog - static Widget _buildDialogContent( - BuildContext context, - StateSetter setState, - List acFunctions, - String? selectedFunction, - dynamic selectedValue, - String? selectedCondition, - List selectedConditions, - Function(String?) onFunctionSelected, - Function(dynamic) onValueSelected, - Function(String?) onConditionSelected, - ) { - return Container( - width: selectedFunction != null ? 600 : 360, - height: 450, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - ), - padding: const EdgeInsets.only(top: 20), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const DialogHeader('AC Functions'), - Flexible( - child: Row( - children: [ - _buildFunctionsList( - context, - setState, - acFunctions, - selectedFunction, - onFunctionSelected, - ), - if (selectedFunction != null) - _buildValueSelector( - context, - setState, - selectedFunction, - selectedValue, - selectedCondition, - selectedConditions, - onValueSelected, - onConditionSelected, - acFunctions, - ), - ], - ), - ), - DialogFooter( - onCancel: () => Navigator.pop(context), - onConfirm: selectedFunction != null && selectedValue != null - ? () => Navigator.pop(context, { - 'function': selectedFunction, - 'value': selectedValue, - 'condition': selectedCondition ?? "==", - }) - : null, - isConfirmEnabled: selectedFunction != null && selectedValue != null, - ), - ], - ), - ); + ).then((value) { + selectedFunctionNotifier.dispose(); + selectedValueNotifier.dispose(); + selectedConditionNotifier.dispose(); + selectedConditionsNotifier.dispose(); + return value; + }); } /// Build functions list for AC functions dialog static Widget _buildFunctionsList( BuildContext context, - StateSetter setState, List acFunctions, - String? selectedFunction, - Function(String?) onFunctionSelected, + ValueNotifier selectedFunctionNotifier, ) { - return Expanded( - child: ListView.separated( - shrinkWrap: false, - physics: const AlwaysScrollableScrollPhysics(), - itemCount: acFunctions.length, - separatorBuilder: (context, index) => const Divider( + return ListView.separated( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: acFunctions.length, + separatorBuilder: (context, index) => const Padding( + padding: EdgeInsets.symmetric(horizontal: 40.0), + child: Divider( color: ColorsManager.dividerColor, ), - itemBuilder: (context, index) { - final function = acFunctions[index]; - return ListTile( - leading: SvgPicture.asset( - function.icon, + ), + itemBuilder: (context, index) { + final function = acFunctions[index]; + return ListTile( + leading: SvgPicture.asset( + function.icon, + width: 24, + height: 24, + placeholderBuilder: (BuildContext context) => Container( width: 24, height: 24, + color: Colors.transparent, ), - title: Text( - function.operationName, - style: context.textTheme.bodyMedium, - ), - trailing: const Icon( - Icons.arrow_forward_ios, - size: 16, - color: ColorsManager.textGray, - ), - onTap: () => setState(() => onFunctionSelected(function.code)), - ); - }, - ), + ), + title: Text( + function.operationName, + style: context.textTheme.bodyMedium, + ), + trailing: const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, + ), + onTap: () => selectedFunctionNotifier.value = function.code, + ); + }, ); } /// Build value selector for AC functions dialog static Widget _buildValueSelector( BuildContext context, - StateSetter setState, String selectedFunction, - dynamic selectedValue, - String? selectedCondition, - List selectedConditions, - Function(dynamic) onValueSelected, - Function(String?) onConditionSelected, + ValueNotifier selectedValueNotifier, + ValueNotifier selectedConditionNotifier, + ValueNotifier> selectedConditionsNotifier, List acFunctions, ) { + // Handle temperature functions if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') { - return Expanded( - child: _buildTemperatureSelector( - context, - setState, - selectedValue, - selectedCondition, - selectedConditions, - onValueSelected, - onConditionSelected, - ), + // Initialize with 20°C (200 in internal representation) + if (selectedValueNotifier.value == null || + selectedValueNotifier.value is! int) { + selectedValueNotifier.value = 200; + } + return _buildTemperatureSelector( + context, + selectedValueNotifier, + selectedConditionNotifier, + selectedConditionsNotifier, ); } + // Handle other functions final selectedFn = acFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); - return Expanded( - child: _buildOperationalValuesList( - context, - setState, - values, - selectedValue, - onValueSelected, - ), + + // Don't set any default value for non-temperature functions + return _buildOperationalValuesList( + context, + values, + selectedValueNotifier, ); } /// Build temperature selector for AC functions dialog static Widget _buildTemperatureSelector( BuildContext context, - StateSetter setState, - dynamic selectedValue, - String? selectedCondition, - List selectedConditions, - Function(dynamic) onValueSelected, - Function(String?) onConditionSelected, + ValueNotifier selectedValueNotifier, + ValueNotifier selectedConditionNotifier, + ValueNotifier> selectedConditionsNotifier, ) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ _buildConditionToggle( context, - setState, - selectedConditions, - onConditionSelected, + selectedConditionNotifier, + selectedConditionsNotifier, ), const SizedBox(height: 20), - _buildTemperatureDisplay(context, selectedValue), + _buildTemperatureDisplay(context, selectedValueNotifier), const SizedBox(height: 20), _buildTemperatureSlider( context, - setState, - selectedValue, - onValueSelected, + selectedValueNotifier, ), ], ); @@ -225,104 +218,121 @@ class ACHelper { /// Build condition toggle for AC functions dialog static Widget _buildConditionToggle( BuildContext context, - StateSetter setState, - List selectedConditions, - Function(String?) onConditionSelected, + ValueNotifier selectedConditionNotifier, + ValueNotifier> selectedConditionsNotifier, ) { - return ToggleButtons( - onPressed: (int index) { - setState(() { - for (int i = 0; i < selectedConditions.length; i++) { - selectedConditions[i] = i == index; - } - onConditionSelected(index == 0 - ? "<" - : index == 1 - ? "==" - : ">"); - }); + return ValueListenableBuilder>( + valueListenable: selectedConditionsNotifier, + builder: (context, selectedConditions, _) { + return ToggleButtons( + onPressed: (int index) { + final newConditions = [false, false, false]; + newConditions[index] = true; + selectedConditionsNotifier.value = newConditions; + selectedConditionNotifier.value = index == 0 + ? "<" + : index == 1 + ? "==" + : ">"; + }, + borderRadius: const BorderRadius.all(Radius.circular(8)), + selectedBorderColor: ColorsManager.primaryColorWithOpacity, + selectedColor: Colors.white, + fillColor: ColorsManager.primaryColorWithOpacity, + color: ColorsManager.primaryColorWithOpacity, + constraints: const BoxConstraints( + minHeight: 40.0, + minWidth: 40.0, + ), + isSelected: selectedConditions, + children: const [Text("<"), Text("="), Text(">")], + ); }, - borderRadius: const BorderRadius.all(Radius.circular(8)), - selectedBorderColor: ColorsManager.primaryColorWithOpacity, - selectedColor: Colors.white, - fillColor: ColorsManager.primaryColorWithOpacity, - color: ColorsManager.primaryColorWithOpacity, - constraints: const BoxConstraints( - minHeight: 40.0, - minWidth: 40.0, - ), - isSelected: selectedConditions, - children: const [Text("<"), Text("="), Text(">")], ); } /// Build temperature display for AC functions dialog static Widget _buildTemperatureDisplay( - BuildContext context, dynamic selectedValue) { + BuildContext context, ValueNotifier selectedValueNotifier) { return Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), decoration: BoxDecoration( color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), borderRadius: BorderRadius.circular(10), ), - child: Text( - '${selectedValue ?? 20}°C', - style: context.textTheme.headlineMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), + child: ValueListenableBuilder( + valueListenable: selectedValueNotifier, + builder: (context, selectedValue, child) { + return Text( + '${(selectedValue ?? 200) / 10}°C', + style: context.textTheme.headlineMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), + ); + }, ), ); } static Widget _buildTemperatureSlider( BuildContext context, - StateSetter setState, - dynamic selectedValue, - Function(dynamic) onValueSelected, + ValueNotifier selectedValueNotifier, ) { - final currentValue = selectedValue is int ? selectedValue.toDouble() : 20.0; - return Slider( - value: currentValue, - min: 16, - max: 30, - divisions: 14, - label: '${currentValue.toInt()}°C', - onChanged: (value) { - setState(() => onValueSelected(value.toInt())); + return ValueListenableBuilder( + valueListenable: selectedValueNotifier, + builder: (context, selectedValue, child) { + final currentValue = + selectedValue is int ? selectedValue.toDouble() : 200.0; + return Slider( + value: currentValue, + min: 160, + max: 300, + divisions: 14, + label: '${(currentValue / 10).toInt()}°C', + onChanged: (value) => selectedValueNotifier.value = value.toInt(), + ); }, ); } static Widget _buildOperationalValuesList( BuildContext context, - StateSetter setState, List values, - dynamic selectedValue, - Function(dynamic) onValueSelected, + ValueNotifier selectedValueNotifier, ) { - return ListView.builder( - shrinkWrap: false, - physics: const AlwaysScrollableScrollPhysics(), - itemCount: values.length, - itemBuilder: (context, index) { - final value = values[index]; - return ListTile( - leading: SvgPicture.asset( - value.icon, - width: 24, - height: 24, - ), - title: Text( - value.description, - style: context.textTheme.bodyMedium, - ), - trailing: Radio( - value: value.value, - groupValue: selectedValue, - onChanged: (newValue) { - setState(() => onValueSelected(newValue)); - }, - ), + return ValueListenableBuilder( + valueListenable: selectedValueNotifier, + builder: (context, selectedValue, _) { + return ListView.builder( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + placeholderBuilder: (BuildContext context) => Container( + width: 24, + height: 24, + color: Colors.transparent, + ), + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Radio( + value: value.value, + groupValue: selectedValue, + onChanged: (_) => selectedValueNotifier.value = value.value, + activeColor: ColorsManager.primaryColorWithOpacity, + ), + onTap: () => selectedValueNotifier.value = value.value, + ); + }, ); }, ); From 53eb18c075a4d24e5ca23edda3dadb8ef46a44fb Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sat, 23 Nov 2024 00:54:52 +0300 Subject: [PATCH 11/40] push ac function state selection --- lib/main.dart | 2 +- .../functions_bloc/functions_bloc_bloc.dart | 148 +++++++ .../functions_bloc/functions_bloc_event.dart | 72 ++++ .../functions_bloc/functions_bloc_state.dart | 24 ++ .../bloc/{ => routine_bloc}/routine_bloc.dart | 0 .../{ => routine_bloc}/routine_event.dart | 0 .../{ => routine_bloc}/routine_state.dart | 0 lib/pages/routiens/helper/ac_helper.dart | 388 +++++++++--------- .../routiens/models/device_functions.dart | 25 ++ .../conditions_routines_devices_view.dart | 2 +- lib/pages/routiens/widgets/dragable_card.dart | 2 +- lib/pages/routiens/widgets/if_container.dart | 2 +- .../routiens/widgets/routine_devices.dart | 2 +- .../widgets/scenes_and_automations.dart | 2 +- .../routiens/widgets/then_container.dart | 2 +- 15 files changed, 475 insertions(+), 196 deletions(-) create mode 100644 lib/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart create mode 100644 lib/pages/routiens/bloc/functions_bloc/functions_bloc_event.dart create mode 100644 lib/pages/routiens/bloc/functions_bloc/functions_bloc_state.dart rename lib/pages/routiens/bloc/{ => routine_bloc}/routine_bloc.dart (100%) rename lib/pages/routiens/bloc/{ => routine_bloc}/routine_event.dart (100%) rename lib/pages/routiens/bloc/{ => routine_bloc}/routine_state.dart (100%) diff --git a/lib/main.dart b/lib/main.dart index ac002f85..2040d175 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,7 +6,7 @@ import 'package:go_router/go_router.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_event.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/utils/app_routes.dart'; diff --git a/lib/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart b/lib/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart new file mode 100644 index 00000000..54b47cad --- /dev/null +++ b/lib/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart @@ -0,0 +1,148 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; + +part 'functions_bloc_event.dart'; +part 'functions_bloc_state.dart'; + +class FunctionBloc extends Bloc { + FunctionBloc() : super(const FunctionBlocState()) { + on(_onInitializeFunctions); + on(_onAddFunction); + on(_onUpdateFunctions); + on(_onUpdateFunctionValue); + on(_onUpdateFunctionCondition); + on(_onRemoveFunction); + } + + void _onAddFunction(AddFunction event, Emitter emit) { + debugPrint('Adding function: ${event.functionData.function}'); + final functions = List.from(state.functions); + + // Find existing function data + final existingIndex = functions.indexWhere( + (f) => f.function == event.functionData.function, + ); + + // If function exists, preserve its value and condition + if (existingIndex != -1) { + final existingData = functions[existingIndex]; + functions[existingIndex] = DeviceFunctionData( + entityId: event.functionData.entityId, + function: event.functionData.function, + operationName: event.functionData.operationName, + value: existingData.value, // Preserve the existing value + condition: existingData.condition, // Preserve the existing condition + ); + } else { + functions.add(event.functionData); + } + + debugPrint('Functions after add: $functions'); + emit(state.copyWith( + functions: functions, + selectedFunction: event.functionData.function, + )); + } + + void _onUpdateFunctions( + UpdateFunction event, Emitter emit) { + final functions = state.functions.map((data) { + return data.function == event.functionData.function + ? event.functionData + : data; + }).toList(); + + emit(state.copyWith(functions: functions)); + } + + void _onUpdateFunctionValue( + UpdateFunctionValue event, + Emitter emit, + ) { + debugPrint('Updating function value: ${event.function} -> ${event.value}'); + + // Create a new list to ensure state immutability + final functions = List.from(state.functions); + + // Find the index of the function to update + final functionIndex = functions.indexWhere( + (data) => data.function == event.function, + ); + + if (functionIndex != -1) { + // Update the existing function data while preserving other fields + final existingData = functions[functionIndex]; + functions[functionIndex] = DeviceFunctionData( + entityId: existingData.entityId, + function: existingData.function, + operationName: existingData.operationName, + value: event.value, + condition: existingData.condition, + ); + } else { + // If function doesn't exist, add it + functions.add(DeviceFunctionData( + entityId: '', + function: event.function, + operationName: '', + value: event.value, + )); + } + + debugPrint('Functions after update: $functions'); + emit(state.copyWith(functions: functions)); + } + + void _onUpdateFunctionCondition( + UpdateFunctionCondition event, + Emitter emit, + ) { + final functions = state.functions.map((data) { + if (data.function == event.function) { + return DeviceFunctionData( + entityId: data.entityId, + function: data.function, + operationName: data.operationName, + value: data.value, + condition: event.condition, + ); + } + return data; + }).toList(); + + emit(state.copyWith(functions: functions)); + } + + void _onRemoveFunction( + RemoveFunction event, Emitter emit) { + final functions = state.functions + .where((data) => data.function != event.functionCode) + .toList(); + + emit(state.copyWith( + functions: functions, + selectedFunction: functions.isEmpty ? null : state.selectedFunction, + )); + } + + void _onInitializeFunctions( + InitializeFunctions event, + Emitter emit, + ) { + emit(state.copyWith(functions: event.functions)); + } + + DeviceFunctionData? getFunction(String functionCode) { + return state.functions.firstWhere( + (data) => data.function == functionCode, + orElse: () => DeviceFunctionData( + entityId: '', + function: functionCode, + operationName: '', + value: null, + ), + ); + } +} diff --git a/lib/pages/routiens/bloc/functions_bloc/functions_bloc_event.dart b/lib/pages/routiens/bloc/functions_bloc/functions_bloc_event.dart new file mode 100644 index 00000000..cceb6d21 --- /dev/null +++ b/lib/pages/routiens/bloc/functions_bloc/functions_bloc_event.dart @@ -0,0 +1,72 @@ +part of 'functions_bloc_bloc.dart'; + +abstract class FunctionBlocEvent extends Equatable { + const FunctionBlocEvent(); + + @override + List get props => []; +} + +class AddFunction extends FunctionBlocEvent { + final DeviceFunctionData functionData; + + const AddFunction({ + required this.functionData, + }); + + @override + List get props => [functionData]; +} + +class UpdateFunction extends FunctionBlocEvent { + final DeviceFunctionData functionData; + + const UpdateFunction(this.functionData); + + @override + List get props => [functionData]; +} + +class UpdateFunctionValue extends FunctionBlocEvent { + final String function; + final dynamic value; + + const UpdateFunctionValue({ + required this.function, + required this.value, + }); + + @override + List get props => [function, value]; +} + +class UpdateFunctionCondition extends FunctionBlocEvent { + final String function; + final String condition; + + const UpdateFunctionCondition({ + required this.function, + required this.condition, + }); + + @override + List get props => [function, condition]; +} + +class RemoveFunction extends FunctionBlocEvent { + final String functionCode; + + const RemoveFunction(this.functionCode); + + @override + List get props => [functionCode]; +} + +class InitializeFunctions extends FunctionBlocEvent { + final List functions; + + const InitializeFunctions(this.functions); + + @override + List get props => [functions]; +} diff --git a/lib/pages/routiens/bloc/functions_bloc/functions_bloc_state.dart b/lib/pages/routiens/bloc/functions_bloc/functions_bloc_state.dart new file mode 100644 index 00000000..af854b8a --- /dev/null +++ b/lib/pages/routiens/bloc/functions_bloc/functions_bloc_state.dart @@ -0,0 +1,24 @@ +part of 'functions_bloc_bloc.dart'; + +class FunctionBlocState extends Equatable { + final List functions; + final String? selectedFunction; + + const FunctionBlocState({ + this.functions = const [], + this.selectedFunction, + }); + + FunctionBlocState copyWith({ + List? functions, + String? selectedFunction, + }) { + return FunctionBlocState( + functions: functions ?? this.functions, + selectedFunction: selectedFunction ?? this.selectedFunction, + ); + } + + @override + List get props => [functions, selectedFunction]; +} diff --git a/lib/pages/routiens/bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart similarity index 100% rename from lib/pages/routiens/bloc/routine_bloc.dart rename to lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart diff --git a/lib/pages/routiens/bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart similarity index 100% rename from lib/pages/routiens/bloc/routine_event.dart rename to lib/pages/routiens/bloc/routine_bloc/routine_event.dart diff --git a/lib/pages/routiens/bloc/routine_state.dart b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart similarity index 100% rename from lib/pages/routiens/bloc/routine_state.dart rename to lib/pages/routiens/bloc/routine_bloc/routine_state.dart diff --git a/lib/pages/routiens/helper/ac_helper.dart b/lib/pages/routiens/helper/ac_helper.dart index d940f4e4..327d0787 100644 --- a/lib/pages/routiens/helper/ac_helper.dart +++ b/lib/pages/routiens/helper/ac_helper.dart @@ -6,6 +6,8 @@ import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart'; class ACHelper { static Future?> showACFunctionsDialog( @@ -13,97 +15,122 @@ class ACHelper { List> functions, ) async { List acFunctions = functions.whereType().toList(); - final selectedFunctionNotifier = ValueNotifier(null); - final selectedValueNotifier = ValueNotifier(null); - final selectedConditionNotifier = ValueNotifier('=='); - final selectedConditionsNotifier = - ValueNotifier>([false, true, false]); + + // Initialize the FunctionBloc with existing functions + final initialFunctions = acFunctions + .map((f) => DeviceFunctionData( + entityId: '', + function: f.code, + operationName: f.operationName, + value: null, + )) + .toList(); return showDialog?>( context: context, builder: (BuildContext context) { - return ValueListenableBuilder( - valueListenable: selectedFunctionNotifier, - builder: (context, selectedFunction, _) { - return AlertDialog( - contentPadding: EdgeInsets.zero, - content: Container( - width: selectedFunction != null ? 600 : 360, - height: 450, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - ), - padding: const EdgeInsets.only(top: 20), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const DialogHeader('AC Functions'), - Expanded( - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // Function list - SizedBox( - width: selectedFunction != null ? 320 : 360, - child: _buildFunctionsList( - context, - acFunctions, - selectedFunctionNotifier, - ), - ), - // Value selector - if (selectedFunction != null) - Expanded( - child: _buildValueSelector( + return BlocProvider( + create: (_) => + FunctionBloc()..add(InitializeFunctions(initialFunctions)), + child: AlertDialog( + contentPadding: EdgeInsets.zero, + content: BlocBuilder( + builder: (context, state) { + debugPrint( + 'Current state - Selected: ${state.selectedFunction}, Functions: ${state.functions}'); + + final selectedFunction = state.selectedFunction; + final selectedFunctionData = selectedFunction != null + ? state.functions.firstWhere( + (f) => f.function == selectedFunction, + orElse: () => DeviceFunctionData( + entityId: '', + function: selectedFunction, + operationName: '', + value: null, + ), + ) + : null; + + return Container( + width: selectedFunction != null ? 600 : 360, + height: 450, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const DialogHeader('AC Functions'), + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Function list + SizedBox( + width: selectedFunction != null ? 320 : 360, + child: _buildFunctionsList( context, - selectedFunction, - selectedValueNotifier, - selectedConditionNotifier, - selectedConditionsNotifier, acFunctions, + (functionCode) => + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: '', + function: functionCode, + operationName: '', + value: null, + )), + ), ), ), - ], + // Value selector + if (selectedFunction != null) + Expanded( + child: _buildValueSelector( + context, + selectedFunction, + selectedFunctionData, + (value) => context.read().add( + UpdateFunctionValue( + function: selectedFunction, + value: value, + ), + ), + (condition) => + context.read().add( + UpdateFunctionCondition( + function: selectedFunction, + condition: condition, + ), + ), + acFunctions, + ), + ), + ], + ), ), - ), - DialogFooter( - onCancel: () { - selectedFunctionNotifier.dispose(); - selectedValueNotifier.dispose(); - selectedConditionNotifier.dispose(); - selectedConditionsNotifier.dispose(); - Navigator.pop(context); - }, - onConfirm: selectedFunctionNotifier.value != null && - selectedValueNotifier.value != null - ? () { - selectedFunctionNotifier.dispose(); - selectedValueNotifier.dispose(); - selectedConditionNotifier.dispose(); - selectedConditionsNotifier.dispose(); - Navigator.pop(context, { - 'function': selectedFunctionNotifier.value, - 'value': selectedValueNotifier.value, - 'condition': - selectedConditionNotifier.value ?? "==", - }); - } - : null, - isConfirmEnabled: selectedFunction != null, - ), - ], - ), - ), - ); - }, + DialogFooter( + onCancel: () { + Navigator.pop(context); + }, + onConfirm: selectedFunction != null && + selectedFunctionData?.value != null + ? () {} + : null, + isConfirmEnabled: selectedFunction != null, + ), + ], + ), + ); + }, + ), + ), ); }, ).then((value) { - selectedFunctionNotifier.dispose(); - selectedValueNotifier.dispose(); - selectedConditionNotifier.dispose(); - selectedConditionsNotifier.dispose(); return value; }); } @@ -112,7 +139,7 @@ class ACHelper { static Widget _buildFunctionsList( BuildContext context, List acFunctions, - ValueNotifier selectedFunctionNotifier, + Function(String) onFunctionSelected, ) { return ListView.separated( shrinkWrap: false, @@ -146,7 +173,7 @@ class ACHelper { size: 16, color: ColorsManager.textGray, ), - onTap: () => selectedFunctionNotifier.value = function.code, + onTap: () => onFunctionSelected(function.code), ); }, ); @@ -156,60 +183,57 @@ class ACHelper { static Widget _buildValueSelector( BuildContext context, String selectedFunction, - ValueNotifier selectedValueNotifier, - ValueNotifier selectedConditionNotifier, - ValueNotifier> selectedConditionsNotifier, + DeviceFunctionData? selectedFunctionData, + Function(dynamic) onValueChanged, + Function(String) onConditionChanged, List acFunctions, ) { - // Handle temperature functions if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') { - // Initialize with 20°C (200 in internal representation) - if (selectedValueNotifier.value == null || - selectedValueNotifier.value is! int) { - selectedValueNotifier.value = 200; - } + final initialValue = selectedFunctionData?.value ?? 200; return _buildTemperatureSelector( context, - selectedValueNotifier, - selectedConditionNotifier, - selectedConditionsNotifier, + initialValue, + selectedFunctionData?.condition, + onValueChanged, + onConditionChanged, ); } - // Handle other functions final selectedFn = acFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); - // Don't set any default value for non-temperature functions return _buildOperationalValuesList( context, values, - selectedValueNotifier, + selectedFunctionData?.value, + onValueChanged, ); } /// Build temperature selector for AC functions dialog static Widget _buildTemperatureSelector( BuildContext context, - ValueNotifier selectedValueNotifier, - ValueNotifier selectedConditionNotifier, - ValueNotifier> selectedConditionsNotifier, + dynamic initialValue, + String? currentCondition, + Function(dynamic) onValueChanged, + Function(String) onConditionChanged, ) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ _buildConditionToggle( context, - selectedConditionNotifier, - selectedConditionsNotifier, + currentCondition, + onConditionChanged, ), const SizedBox(height: 20), - _buildTemperatureDisplay(context, selectedValueNotifier), + _buildTemperatureDisplay(context, initialValue), const SizedBox(height: 20), _buildTemperatureSlider( context, - selectedValueNotifier, + initialValue, + onValueChanged, ), ], ); @@ -218,79 +242,61 @@ class ACHelper { /// Build condition toggle for AC functions dialog static Widget _buildConditionToggle( BuildContext context, - ValueNotifier selectedConditionNotifier, - ValueNotifier> selectedConditionsNotifier, + String? currentCondition, + Function(String) onConditionChanged, ) { - return ValueListenableBuilder>( - valueListenable: selectedConditionsNotifier, - builder: (context, selectedConditions, _) { - return ToggleButtons( - onPressed: (int index) { - final newConditions = [false, false, false]; - newConditions[index] = true; - selectedConditionsNotifier.value = newConditions; - selectedConditionNotifier.value = index == 0 - ? "<" - : index == 1 - ? "==" - : ">"; - }, - borderRadius: const BorderRadius.all(Radius.circular(8)), - selectedBorderColor: ColorsManager.primaryColorWithOpacity, - selectedColor: Colors.white, - fillColor: ColorsManager.primaryColorWithOpacity, - color: ColorsManager.primaryColorWithOpacity, - constraints: const BoxConstraints( - minHeight: 40.0, - minWidth: 40.0, - ), - isSelected: selectedConditions, - children: const [Text("<"), Text("="), Text(">")], - ); + final conditions = ["<", "==", ">"]; + + return ToggleButtons( + onPressed: (int index) { + onConditionChanged(conditions[index]); }, + borderRadius: const BorderRadius.all(Radius.circular(8)), + selectedBorderColor: ColorsManager.primaryColorWithOpacity, + selectedColor: Colors.white, + fillColor: ColorsManager.primaryColorWithOpacity, + color: ColorsManager.primaryColorWithOpacity, + constraints: const BoxConstraints( + minHeight: 40.0, + minWidth: 40.0, + ), + isSelected: + conditions.map((c) => c == (currentCondition ?? "==")).toList(), + children: conditions.map((c) => Text(c)).toList(), ); } /// Build temperature display for AC functions dialog static Widget _buildTemperatureDisplay( - BuildContext context, ValueNotifier selectedValueNotifier) { + BuildContext context, dynamic initialValue) { return Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), decoration: BoxDecoration( color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), borderRadius: BorderRadius.circular(10), ), - child: ValueListenableBuilder( - valueListenable: selectedValueNotifier, - builder: (context, selectedValue, child) { - return Text( - '${(selectedValue ?? 200) / 10}°C', - style: context.textTheme.headlineMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), - ); - }, + child: Text( + '${(initialValue ?? 200) / 10}°C', + style: context.textTheme.headlineMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), ), ); } static Widget _buildTemperatureSlider( BuildContext context, - ValueNotifier selectedValueNotifier, + dynamic initialValue, + Function(dynamic) onValueChanged, ) { - return ValueListenableBuilder( - valueListenable: selectedValueNotifier, - builder: (context, selectedValue, child) { - final currentValue = - selectedValue is int ? selectedValue.toDouble() : 200.0; - return Slider( - value: currentValue, - min: 160, - max: 300, - divisions: 14, - label: '${(currentValue / 10).toInt()}°C', - onChanged: (value) => selectedValueNotifier.value = value.toInt(), - ); + return Slider( + value: initialValue is int ? initialValue.toDouble() : 200.0, + min: 160, + max: 300, + divisions: 14, + label: '${((initialValue ?? 200) / 10).toInt()}°C', + onChanged: (value) { + onValueChanged(value.toInt()); }, ); } @@ -298,40 +304,44 @@ class ACHelper { static Widget _buildOperationalValuesList( BuildContext context, List values, - ValueNotifier selectedValueNotifier, + dynamic selectedValue, + Function(dynamic) onValueChanged, ) { - return ValueListenableBuilder( - valueListenable: selectedValueNotifier, - builder: (context, selectedValue, _) { - return ListView.builder( - shrinkWrap: false, - physics: const AlwaysScrollableScrollPhysics(), - itemCount: values.length, - itemBuilder: (context, index) { - final value = values[index]; - return ListTile( - leading: SvgPicture.asset( - value.icon, - width: 24, - height: 24, - placeholderBuilder: (BuildContext context) => Container( - width: 24, - height: 24, - color: Colors.transparent, - ), - ), - title: Text( - value.description, - style: context.textTheme.bodyMedium, - ), - trailing: Radio( - value: value.value, - groupValue: selectedValue, - onChanged: (_) => selectedValueNotifier.value = value.value, - activeColor: ColorsManager.primaryColorWithOpacity, - ), - onTap: () => selectedValueNotifier.value = value.value, - ); + return ListView.builder( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + final isSelected = selectedValue == value.value; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + placeholderBuilder: (BuildContext context) => Container( + width: 24, + height: 24, + color: Colors.transparent, + ), + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Icon( + isSelected + ? Icons.radio_button_checked + : Icons.radio_button_unchecked, + size: 24, + color: isSelected + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.textGray, + ), + onTap: () { + if (!isSelected) { + onValueChanged(value.value); + } }, ); }, diff --git a/lib/pages/routiens/models/device_functions.dart b/lib/pages/routiens/models/device_functions.dart index 303fe48c..4234c812 100644 --- a/lib/pages/routiens/models/device_functions.dart +++ b/lib/pages/routiens/models/device_functions.dart @@ -58,4 +58,29 @@ class DeviceFunctionData { valueDescription: json['valueDescription'], ); } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is DeviceFunctionData && + other.entityId == entityId && + other.actionExecutor == actionExecutor && + other.function == function && + other.operationName == operationName && + other.value == value && + other.condition == condition && + other.valueDescription == valueDescription; + } + + @override + int get hashCode { + return entityId.hashCode ^ + actionExecutor.hashCode ^ + function.hashCode ^ + operationName.hashCode ^ + value.hashCode ^ + condition.hashCode ^ + valueDescription.hashCode; + } } diff --git a/lib/pages/routiens/widgets/conditions_routines_devices_view.dart b/lib/pages/routiens/widgets/conditions_routines_devices_view.dart index 2728d329..7bda1d51 100644 --- a/lib/pages/routiens/widgets/conditions_routines_devices_view.dart +++ b/lib/pages/routiens/widgets/conditions_routines_devices_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; import 'package:syncrow_web/pages/routiens/widgets/routine_devices.dart'; import 'package:syncrow_web/pages/routiens/widgets/routines_title_widget.dart'; diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index 227e440b..016b82c1 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; diff --git a/lib/pages/routiens/widgets/if_container.dart b/lib/pages/routiens/widgets/if_container.dart index af1394c6..779453f1 100644 --- a/lib/pages/routiens/widgets/if_container.dart +++ b/lib/pages/routiens/widgets/if_container.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; diff --git a/lib/pages/routiens/widgets/routine_devices.dart b/lib/pages/routiens/widgets/routine_devices.dart index 7d97de18..0dc6e35e 100644 --- a/lib/pages/routiens/widgets/routine_devices.dart +++ b/lib/pages/routiens/widgets/routine_devices.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; class RoutineDevices extends StatelessWidget { diff --git a/lib/pages/routiens/widgets/scenes_and_automations.dart b/lib/pages/routiens/widgets/scenes_and_automations.dart index ef40b605..a74634ba 100644 --- a/lib/pages/routiens/widgets/scenes_and_automations.dart +++ b/lib/pages/routiens/widgets/scenes_and_automations.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index 385ec4cb..e4df6d8b 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; From 5e91d7c03ade0016c0de5fbb66a9c4ce8ce35134 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sat, 23 Nov 2024 01:39:05 +0300 Subject: [PATCH 12/40] push adding the selected functiond and device to then container --- .../bloc/routine_bloc/routine_bloc.dart | 4 ++-- .../bloc/routine_bloc/routine_event.dart | 4 ++-- lib/pages/routiens/helper/ac_helper.dart | 17 +++++++++++---- .../dialog_helper/device_dialog_helper.dart | 21 +++++++++++-------- .../helper/three_gang_switch_helper.dart | 2 +- .../helper/two_gang_switch_helper.dart | 2 +- .../routiens/widgets/then_container.dart | 2 +- 7 files changed, 32 insertions(+), 20 deletions(-) diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index d69df552..20a3c393 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -15,7 +15,7 @@ class RoutineBloc extends Bloc { on(_onAddToThenContainer); on(_onLoadScenes); on(_onLoadAutomation); - on(_onAddFunction); + on(_onAddFunction); on(_onRemoveFunction); on(_onClearFunctions); } @@ -37,7 +37,7 @@ class RoutineBloc extends Bloc { } } - void _onAddFunction(AddFunction event, Emitter emit) { + void _onAddFunction(AddFunctionToRoutine event, Emitter emit) { final functions = List.from(state.selectedFunctions); functions.add(event.function); emit(state.copyWith(selectedFunctions: functions)); diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart index d3af8134..3321af5e 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart @@ -43,9 +43,9 @@ class LoadAutomation extends RoutineEvent { List get props => [unitId]; } -class AddFunction extends RoutineEvent { +class AddFunctionToRoutine extends RoutineEvent { final DeviceFunctionData function; - const AddFunction(this.function); + const AddFunctionToRoutine(this.function); @override List get props => [function]; } diff --git a/lib/pages/routiens/helper/ac_helper.dart b/lib/pages/routiens/helper/ac_helper.dart index 327d0787..4a42c43d 100644 --- a/lib/pages/routiens/helper/ac_helper.dart +++ b/lib/pages/routiens/helper/ac_helper.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; @@ -36,9 +37,6 @@ class ACHelper { contentPadding: EdgeInsets.zero, content: BlocBuilder( builder: (context, state) { - debugPrint( - 'Current state - Selected: ${state.selectedFunction}, Functions: ${state.functions}'); - final selectedFunction = state.selectedFunction; final selectedFunctionData = selectedFunction != null ? state.functions.firstWhere( @@ -118,7 +116,18 @@ class ACHelper { }, onConfirm: selectedFunction != null && selectedFunctionData?.value != null - ? () {} + ? () { + /// add the functions to the routine bloc + for (var function in state.functions) { + context.read().add( + AddFunctionToRoutine( + function, + ), + ); + } + // Return the device data to be added to the container + Navigator.pop(context); + } : null, isConfirmEnabled: selectedFunction != null, ), diff --git a/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart index ea4743b7..2b21d83d 100644 --- a/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart @@ -13,11 +13,15 @@ class DeviceDialogHelper { final functions = data['functions'] as List; try { - await _getDialogForDeviceType( + final result = await _getDialogForDeviceType( context, data['productType'], functions, ); + + if (result != null) { + return result; + } } catch (e) { debugPrint('Error: $e'); } @@ -25,23 +29,22 @@ class DeviceDialogHelper { return null; } - static Future _getDialogForDeviceType( + static Future?> _getDialogForDeviceType( BuildContext context, String productType, List functions, ) async { switch (productType) { case 'AC': - await ACHelper.showACFunctionsDialog(context, functions); - break; + return ACHelper.showACFunctionsDialog(context, functions); case '1G': - await OneGangSwitchHelper.showSwitchFunctionsDialog(context, functions); - break; + return OneGangSwitchHelper.showSwitchFunctionsDialog( + context, functions); case '2G': - await TwoGangSwitchHelper.showSwitchFunctionsDialog(context, functions); - break; + return TwoGangSwitchHelper.showSwitchFunctionsDialog( + context, functions); case '3G': - await ThreeGangSwitchHelper.showSwitchFunctionsDialog( + return ThreeGangSwitchHelper.showSwitchFunctionsDialog( context, functions); break; } diff --git a/lib/pages/routiens/helper/three_gang_switch_helper.dart b/lib/pages/routiens/helper/three_gang_switch_helper.dart index 1e62f5c4..ecbe81f2 100644 --- a/lib/pages/routiens/helper/three_gang_switch_helper.dart +++ b/lib/pages/routiens/helper/three_gang_switch_helper.dart @@ -9,7 +9,7 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class ThreeGangSwitchHelper { - static Future showSwitchFunctionsDialog( + static Future?> showSwitchFunctionsDialog( BuildContext context, List> functions) async { List> switchFunctions = functions .where((f) => diff --git a/lib/pages/routiens/helper/two_gang_switch_helper.dart b/lib/pages/routiens/helper/two_gang_switch_helper.dart index 8a7e5580..f0e3026a 100644 --- a/lib/pages/routiens/helper/two_gang_switch_helper.dart +++ b/lib/pages/routiens/helper/two_gang_switch_helper.dart @@ -9,7 +9,7 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class TwoGangSwitchHelper { - static Future showSwitchFunctionsDialog( + static Future?> showSwitchFunctionsDialog( BuildContext context, List> functions) async { List> switchFunctions = functions .where((f) => diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index e4df6d8b..633a1ad1 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -46,7 +46,7 @@ class ThenContainer extends StatelessWidget { final result = await DeviceDialogHelper.showDeviceDialog(context, data); if (result != null) { - context.read().add(AddToThenContainer(result)); + context.read().add(AddToThenContainer(data)); } else if (!['AC', '1G', '2G', '3G'] .contains(data['productType'])) { context.read().add(AddToThenContainer(data)); From e84df8b7a6a5d476270c91dd4e042b4e1a239b40 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Sat, 23 Nov 2024 21:44:47 +0300 Subject: [PATCH 13/40] Added scene setting --- .../effective_period/effect_period_bloc.dart | 125 ++++++ .../effective_period/effect_period_event.dart | 50 +++ .../effective_period/effect_period_state.dart | 54 +++ .../bloc/setting_bloc/setting_bloc.dart | 53 +++ .../bloc/setting_bloc/setting_event.dart | 32 ++ .../bloc/setting_bloc/setting_state.dart | 56 +++ .../helper/effictive_period_helper.dart | 151 +++++++ lib/pages/routiens/helper/setting_helper.dart | 384 ++++++++++++++++++ lib/pages/routiens/models/icon_model.dart | 39 ++ .../routiens/view/effective_period_view.dart | 50 +++ lib/pages/routiens/widgets/period_option.dart | 97 +++++ lib/pages/routiens/widgets/repeat_days.dart | 83 ++++ .../widgets/routine_search_and_buttons.dart | 10 +- lib/services/routines_api.dart | 16 + lib/utils/constants/api_const.dart | 7 +- lib/utils/constants/app_enum.dart | 8 + pubspec.lock | 32 +- pubspec.yaml | 1 + 18 files changed, 1231 insertions(+), 17 deletions(-) create mode 100644 lib/pages/routiens/bloc/effective_period/effect_period_bloc.dart create mode 100644 lib/pages/routiens/bloc/effective_period/effect_period_event.dart create mode 100644 lib/pages/routiens/bloc/effective_period/effect_period_state.dart create mode 100644 lib/pages/routiens/bloc/setting_bloc/setting_bloc.dart create mode 100644 lib/pages/routiens/bloc/setting_bloc/setting_event.dart create mode 100644 lib/pages/routiens/bloc/setting_bloc/setting_state.dart create mode 100644 lib/pages/routiens/helper/effictive_period_helper.dart create mode 100644 lib/pages/routiens/helper/setting_helper.dart create mode 100644 lib/pages/routiens/models/icon_model.dart create mode 100644 lib/pages/routiens/view/effective_period_view.dart create mode 100644 lib/pages/routiens/widgets/period_option.dart create mode 100644 lib/pages/routiens/widgets/repeat_days.dart diff --git a/lib/pages/routiens/bloc/effective_period/effect_period_bloc.dart b/lib/pages/routiens/bloc/effective_period/effect_period_bloc.dart new file mode 100644 index 00000000..05f63123 --- /dev/null +++ b/lib/pages/routiens/bloc/effective_period/effect_period_bloc.dart @@ -0,0 +1,125 @@ +import 'dart:async'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart'; +import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart'; +import 'package:syncrow_web/utils/constants/app_enum.dart'; + +class EffectPeriodBloc extends Bloc { + final daysMap = { + 'Sun': 'S', + 'Mon': 'M', + 'Tue': 'T', + 'Wed': 'W', + 'Thu': 'T', + 'Fri': 'F', + 'Sat': 'S', + }; + + EffectPeriodBloc() : super(EffectPeriodState.initial()) { + on(_onSetPeriod); + on(_onToggleDay); + on(_onSetCustomTime); + on(_onResetEffectivePeriod); + on(_onResetDays); + on(_setAllDays); + } + + void _onSetPeriod(SetPeriod event, Emitter emit) { + String startTime = ''; + String endTime = ''; + + switch (event.period) { + case EnumEffectivePeriodOptions.allDay: + startTime = '00:00'; + endTime = '23:59'; + break; + case EnumEffectivePeriodOptions.daytime: + startTime = '06:00'; + endTime = '18:00'; + break; + case EnumEffectivePeriodOptions.night: + startTime = '18:00'; + endTime = '06:00'; + break; + case EnumEffectivePeriodOptions.custom: + startTime = state.customStartTime ?? '00:00'; + endTime = state.customEndTime ?? '23:59'; + break; + default: + break; + } + + // BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( + // EffectiveTimePeriodEvent( + // EffectiveTime(start: startTime, end: endTime, loops: state.selectedDaysBinary))); + + emit(state.copyWith( + selectedPeriod: event.period, customStartTime: startTime, customEndTime: endTime)); + } + + void _onToggleDay(ToggleDay event, Emitter emit) { + final daysList = state.selectedDaysBinary.split(''); + final dayIndex = getDayIndex(event.day); + if (daysList[dayIndex] == '1') { + daysList[dayIndex] = '0'; + } else { + daysList[dayIndex] = '1'; + } + final newDaysBinary = daysList.join(); + emit(state.copyWith(selectedDaysBinary: newDaysBinary)); + + // BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( + // EffectiveTimePeriodEvent(EffectiveTime( + // start: state.customStartTime ?? '00:00', + // end: state.customEndTime ?? '23:59', + // loops: newDaysBinary))); + } + + void _onSetCustomTime(SetCustomTime event, Emitter emit) { + String startTime = event.startTime; + String endTime = event.endTime; + EnumEffectivePeriodOptions period; + + // Determine the period based on start and end times + if (startTime == '00:00' && endTime == '23:59') { + period = EnumEffectivePeriodOptions.allDay; + } else if (startTime == '06:00' && endTime == '18:00') { + period = EnumEffectivePeriodOptions.daytime; + } else if (startTime == '18:00' && endTime == '06:00') { + period = EnumEffectivePeriodOptions.night; + } else { + period = EnumEffectivePeriodOptions.custom; + } + + emit( + state.copyWith(customStartTime: startTime, customEndTime: endTime, selectedPeriod: period)); + + // BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( + // EffectiveTimePeriodEvent( + // EffectiveTime(start: startTime, end: endTime, loops: state.selectedDaysBinary))); + } + + void _onResetEffectivePeriod(ResetEffectivePeriod event, Emitter emit) { + emit(state.copyWith( + selectedPeriod: EnumEffectivePeriodOptions.allDay, + customStartTime: '00:00', + customEndTime: '23:59', + selectedDaysBinary: '1111111')); + + // BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( + // EffectiveTimePeriodEvent(EffectiveTime(start: '00:00', end: '23:59', loops: '1111111'))); + } + + void _onResetDays(ResetDays event, Emitter emit) { + emit(state.copyWith(selectedDaysBinary: '1111111')); + } + + int getDayIndex(String day) { + const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + return days.indexOf(day); + } + + FutureOr _setAllDays(SetDays event, Emitter emit) { + emit(state.copyWith(selectedDaysBinary: event.daysBinary)); + } +} diff --git a/lib/pages/routiens/bloc/effective_period/effect_period_event.dart b/lib/pages/routiens/bloc/effective_period/effect_period_event.dart new file mode 100644 index 00000000..e1a86915 --- /dev/null +++ b/lib/pages/routiens/bloc/effective_period/effect_period_event.dart @@ -0,0 +1,50 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/utils/constants/app_enum.dart'; + +abstract class EffectPeriodEvent extends Equatable { + const EffectPeriodEvent(); + + @override + List get props => []; +} + +class SetPeriod extends EffectPeriodEvent { + final EnumEffectivePeriodOptions period; + + const SetPeriod(this.period); + + @override + List get props => [period]; +} + +class ToggleDay extends EffectPeriodEvent { + final String day; + + const ToggleDay(this.day); + + @override + List get props => [day]; +} + +class SetCustomTime extends EffectPeriodEvent { + final String startTime; + final String endTime; + + const SetCustomTime(this.startTime, this.endTime); + + @override + List get props => [startTime, endTime]; +} + +class ResetEffectivePeriod extends EffectPeriodEvent {} + +class ResetDays extends EffectPeriodEvent { + @override + List get props => []; +} + +class SetDays extends EffectPeriodEvent { + final String daysBinary; + + const SetDays(this.daysBinary); +} diff --git a/lib/pages/routiens/bloc/effective_period/effect_period_state.dart b/lib/pages/routiens/bloc/effective_period/effect_period_state.dart new file mode 100644 index 00000000..2f8b66c8 --- /dev/null +++ b/lib/pages/routiens/bloc/effective_period/effect_period_state.dart @@ -0,0 +1,54 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/utils/constants/app_enum.dart'; + +class EffectPeriodState extends Equatable { + final EnumEffectivePeriodOptions selectedPeriod; + final String selectedDaysBinary; + final String? customStartTime; + final String? customEndTime; + + const EffectPeriodState({ + required this.selectedPeriod, + required this.selectedDaysBinary, + this.customStartTime, + this.customEndTime, + }); + + factory EffectPeriodState.initial() { + return const EffectPeriodState( + selectedPeriod: EnumEffectivePeriodOptions.allDay, + selectedDaysBinary: "1111111", // All days selected + customStartTime: "00:00", + customEndTime: "23:59", + ); + } + + EffectPeriodState copyWith({ + EnumEffectivePeriodOptions? selectedPeriod, + String? selectedDaysBinary, + String? customStartTime, + String? customEndTime, + }) { + return EffectPeriodState( + selectedPeriod: selectedPeriod ?? this.selectedPeriod, + selectedDaysBinary: selectedDaysBinary ?? this.selectedDaysBinary, + customStartTime: customStartTime ?? this.customStartTime, + customEndTime: customEndTime ?? this.customEndTime, + ); + } + + EnumEffectivePeriodOptions getEffectivePeriod() { + if (customStartTime == '00:00' && customEndTime == '23:59') { + return EnumEffectivePeriodOptions.allDay; + } else if (customStartTime == '06:00' && customEndTime == '18:00') { + return EnumEffectivePeriodOptions.daytime; + } else if (customStartTime == '18:00' && customEndTime == '06:00') { + return EnumEffectivePeriodOptions.night; + } else { + return EnumEffectivePeriodOptions.custom; + } + } + + @override + List get props => [selectedPeriod, selectedDaysBinary, customStartTime, customEndTime]; +} diff --git a/lib/pages/routiens/bloc/setting_bloc/setting_bloc.dart b/lib/pages/routiens/bloc/setting_bloc/setting_bloc.dart new file mode 100644 index 00000000..f70aed34 --- /dev/null +++ b/lib/pages/routiens/bloc/setting_bloc/setting_bloc.dart @@ -0,0 +1,53 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_event.dart'; +import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_state.dart'; +import 'package:syncrow_web/pages/routiens/models/icon_model.dart'; +import 'package:syncrow_web/services/routines_api.dart'; + +class SettingBloc extends Bloc { + bool isExpanded = false; + String selectedIcon = ''; + List iconModelList = []; + + SettingBloc() : super(const InitialState()) { + on(_initialSetting); + on(_fetchIcons); + on(_selectIcon); + } + + void _initialSetting(InitialEvent event, Emitter emit) async { + try { + emit(const LoadingState()); + selectedIcon = event.selectedIcon; + emit(TabToRunSettingLoaded( + showInDevice: true, selectedIcon: event.selectedIcon, iconList: iconModelList)); + } catch (e) { + emit(const FailedState(error: 'Something went wrong')); + } + } + + void _fetchIcons(FetchIcons event, Emitter emit) async { + try { + isExpanded = event.expanded; + emit(const LoadingState()); + if (isExpanded) { + iconModelList = await SceneApi.getIcon(); + emit(TabToRunSettingLoaded( + showInDevice: true, selectedIcon: selectedIcon, iconList: iconModelList)); + } + } catch (e) { + emit(const FailedState(error: 'Something went wrong')); + } + } + + void _selectIcon(SelectIcon event, Emitter emit) async { + try { + emit(const LoadingState()); + selectedIcon = event.iconId; + emit(TabToRunSettingLoaded( + showInDevice: true, selectedIcon: event.iconId, iconList: iconModelList)); + } catch (e) { + emit(const FailedState(error: 'Something went wrong')); + } + } +} diff --git a/lib/pages/routiens/bloc/setting_bloc/setting_event.dart b/lib/pages/routiens/bloc/setting_bloc/setting_event.dart new file mode 100644 index 00000000..0580e51e --- /dev/null +++ b/lib/pages/routiens/bloc/setting_bloc/setting_event.dart @@ -0,0 +1,32 @@ +import 'package:equatable/equatable.dart'; + +abstract class SettingEvent extends Equatable { + const SettingEvent(); + + @override + List get props => []; +} + +class InitialEvent extends SettingEvent { + final String selectedIcon; + const InitialEvent({required this.selectedIcon}); + + @override + List get props => [selectedIcon]; +} + +class FetchIcons extends SettingEvent { + final bool expanded; + const FetchIcons({required this.expanded}); + + @override + List get props => [expanded]; +} + +class SelectIcon extends SettingEvent { + final String iconId; + const SelectIcon({required this.iconId}); + + @override + List get props => [iconId]; +} diff --git a/lib/pages/routiens/bloc/setting_bloc/setting_state.dart b/lib/pages/routiens/bloc/setting_bloc/setting_state.dart new file mode 100644 index 00000000..7c88d67c --- /dev/null +++ b/lib/pages/routiens/bloc/setting_bloc/setting_state.dart @@ -0,0 +1,56 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/routiens/models/icon_model.dart'; + +abstract class SettingState extends Equatable { + const SettingState(); + + @override + List get props => []; +} + +class LoadingState extends SettingState { + const LoadingState(); + + @override + List get props => []; +} + +class InitialState extends SettingState { + const InitialState(); + + @override + List get props => []; +} + +class IconLoadedState extends SettingState { + final List status; + + const IconLoadedState(this.status); + + @override + List get props => [status]; +} + +class TabToRunSettingLoaded extends SettingState { + final String selectedIcon; + final List iconList; + final bool showInDevice; + + const TabToRunSettingLoaded({ + required this.selectedIcon, + required this.iconList, + required this.showInDevice, + }); + + @override + List get props => [selectedIcon, iconList, showInDevice]; +} + +class FailedState extends SettingState { + final String error; + + const FailedState({required this.error}); + + @override + List get props => [error]; +} diff --git a/lib/pages/routiens/helper/effictive_period_helper.dart b/lib/pages/routiens/helper/effictive_period_helper.dart new file mode 100644 index 00000000..70a6776d --- /dev/null +++ b/lib/pages/routiens/helper/effictive_period_helper.dart @@ -0,0 +1,151 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/app_enum.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:time_picker_spinner/time_picker_spinner.dart'; + +class EffectPeriodHelper { + static Future?> showCustomTimePicker(BuildContext context) async { + String selectedStartTime = "00:00"; + String selectedEndTime = "23:59"; + PageController pageController = PageController(initialPage: 0); + + DateTime startDateTime = DateTime(2022, 1, 1, 0, 0); + DateTime endDateTime = DateTime(2022, 1, 1, 23, 59); + + context.customAlertDialog( + alertBody: SizedBox( + height: 250, + child: PageView( + controller: pageController, + physics: const NeverScrollableScrollPhysics(), + children: [ + _buildTimePickerPage( + context: context, + pageController: pageController, + isStartTime: true, + time: startDateTime, + onTimeChange: (time) { + selectedStartTime = + "${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}"; + }, + ), + _buildTimePickerPage( + context: context, + pageController: pageController, + isStartTime: false, + time: endDateTime, + onTimeChange: (time) { + selectedEndTime = + "${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}"; + }, + ), + ], + ), + ), + title: "Custom", + onConfirm: () { + context.read().add( + SetCustomTime(selectedStartTime, selectedEndTime), + ); + context.read().add( + const SetPeriod(EnumEffectivePeriodOptions.custom), + ); + + Navigator.of(context).pop(); + }, + ); + return null; + } + + static Widget _buildTimePickerPage({ + required BuildContext context, + required PageController pageController, + required bool isStartTime, + required DateTime time, + required Function(DateTime) onTimeChange, + }) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (!isStartTime) + TextButton( + onPressed: () { + pageController.previousPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeIn, + ); + }, + child: const Text('Start'), + ), + TextButton( + onPressed: () {}, + child: Text(isStartTime ? "Start" : "End", + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 10)), + ), + if (isStartTime) + TextButton( + onPressed: () { + pageController.nextPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeIn, + ); + }, + child: Text('End', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 10)), + ), + ], + ), + ), + TimePickerSpinner( + is24HourMode: false, + normalTextStyle: const TextStyle( + fontSize: 24, + color: Colors.grey, + ), + highlightedTextStyle: const TextStyle( + fontSize: 24, + color: ColorsManager.primaryColor, + ), + spacing: 20, + itemHeight: 50, + isForce2Digits: true, + time: time, + onTimeChange: onTimeChange, + ), + const SizedBox(height: 16), + ], + ); + } + + static String formatEnumValue(EnumEffectivePeriodOptions value) { + switch (value) { + case EnumEffectivePeriodOptions.allDay: + return "All Day"; + case EnumEffectivePeriodOptions.daytime: + return "Daytime"; + case EnumEffectivePeriodOptions.night: + return "Night"; + case EnumEffectivePeriodOptions.custom: + return "Custom"; + case EnumEffectivePeriodOptions.none: + return "None"; + default: + return ""; + } + } +} diff --git a/lib/pages/routiens/helper/setting_helper.dart b/lib/pages/routiens/helper/setting_helper.dart new file mode 100644 index 00000000..ae8a1bc8 --- /dev/null +++ b/lib/pages/routiens/helper/setting_helper.dart @@ -0,0 +1,384 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_event.dart'; +import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_state.dart'; +import 'package:syncrow_web/pages/routiens/models/icon_model.dart'; +import 'package:syncrow_web/pages/routiens/view/effective_period_view.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:flutter/cupertino.dart'; + +class SettingHelper { + static Future?> showSettingDialog( + {required BuildContext context, String? iconId, required bool isAutomation}) async { + return showDialog?>( + context: context, + builder: (BuildContext context) { + return BlocProvider( + create: (_) => SettingBloc()..add(InitialEvent(selectedIcon: iconId ?? '')), + child: AlertDialog( + contentPadding: EdgeInsets.zero, + content: BlocBuilder( + builder: (context, state) { + String selectedIcon = ''; + List list = []; + if (state is TabToRunSettingLoaded) { + selectedIcon = state.selectedIcon; + list = state.iconList; + } + return Container( + width: context.read().isExpanded ? 800 : 400, + height: context.read().isExpanded && isAutomation ? 500 : 300, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const DialogHeader('Settings'), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 400, + child: isAutomation + ? Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.only( + top: 10, left: 10, right: 10, bottom: 10), + child: Column( + children: [ + InkWell( + onTap: () {}, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Validity', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 14), + ), + const Icon( + Icons.arrow_forward_ios_outlined, + color: ColorsManager.textGray, + size: 15, + ) + ], + ), + ), + const SizedBox( + height: 5, + ), + const Divider( + color: ColorsManager.graysColor, + ), + const SizedBox( + height: 5, + ), + InkWell( + onTap: () { + BlocProvider.of(context).add( + FetchIcons( + expanded: !context + .read() + .isExpanded)); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Effective Period', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 14), + ), + const Icon( + Icons.arrow_forward_ios_outlined, + color: ColorsManager.textGray, + size: 15, + ) + ], + ), + ), + const SizedBox( + height: 5, + ), + const Divider( + color: ColorsManager.graysColor, + ), + const SizedBox( + height: 5, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Executed by', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 14), + ), + Text('Cloud', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: ColorsManager.textGray, + fontWeight: FontWeight.w400, + fontSize: 14)), + ], + ), + ], + )), + ], + ) + : Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.only( + top: 10, left: 10, right: 10, bottom: 10), + child: Column( + children: [ + InkWell( + onTap: () { + BlocProvider.of(context).add( + FetchIcons( + expanded: !context + .read() + .isExpanded)); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Icons', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 14), + ), + const Icon( + Icons.arrow_forward_ios_outlined, + color: ColorsManager.textGray, + size: 15, + ) + ], + ), + ), + const SizedBox( + height: 5, + ), + const Divider( + color: ColorsManager.graysColor, + ), + const SizedBox( + height: 5, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Show on devices page', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 14), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + height: 30, + width: 1, + color: ColorsManager.graysColor, + ), + Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: true, + onChanged: (value) {}, + applyTheme: true, + ), + ), + ], + ) + ], + ), + const SizedBox( + height: 5, + ), + const Divider( + color: ColorsManager.graysColor, + ), + const SizedBox( + height: 5, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Executed by', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 14), + ), + Text('Cloud', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: ColorsManager.textGray, + fontWeight: FontWeight.w400, + fontSize: 14)), + ], + ), + ], + )), + ], + ), + ), + if (context.read().isExpanded && !isAutomation) + SizedBox( + width: 400, + height: 150, + child: state is LoadingState + ? const Center(child: CircularProgressIndicator()) + : GridView.builder( + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 6, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + shrinkWrap: true, + itemCount: list.length, + itemBuilder: (context, index) { + final iconModel = list[index]; + return SizedBox( + width: 35, + height: 35, + child: InkWell( + onTap: () { + BlocProvider.of(context) + .add(SelectIcon( + iconId: iconModel.uuid, + )); + selectedIcon = iconModel.uuid; + }, + child: SizedBox( + child: ClipOval( + child: Container( + padding: const EdgeInsets.all(1), + decoration: BoxDecoration( + border: Border.all( + color: selectedIcon == iconModel.uuid + ? ColorsManager.primaryColorWithOpacity + : Colors.transparent, + width: 2, + ), + shape: BoxShape.circle, + ), + child: Image.memory( + iconModel.iconBytes, + ), + ), + ), + ), + ), + ); + }, + )), + if (context.read().isExpanded && isAutomation) + const SizedBox(height: 350, width: 400, child: EffectivePeriodView()) + ], + ), + Container( + width: MediaQuery.sizeOf(context).width, + decoration: const BoxDecoration( + border: Border( + top: BorderSide( + color: ColorsManager.greyColor, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: Container( + alignment: AlignmentDirectional.center, + child: Text( + 'Cancel', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.textGray, + ), + ), + ), + ), + ), + Container(width: 1, height: 50, color: ColorsManager.greyColor), + Expanded( + child: InkWell( + onTap: () { + Navigator.of(context).pop(selectedIcon); + }, + child: Container( + alignment: AlignmentDirectional.center, + child: Text( + 'Confirm', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), + ), + ), + ), + ), + ], + ), + ) + ], + ), + ); + }, + ), + ), + ); + }, + ); + } +} diff --git a/lib/pages/routiens/models/icon_model.dart b/lib/pages/routiens/models/icon_model.dart new file mode 100644 index 00000000..70f15e8c --- /dev/null +++ b/lib/pages/routiens/models/icon_model.dart @@ -0,0 +1,39 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +class IconModel { + final String uuid; + final DateTime createdAt; + final DateTime updatedAt; + final String iconBase64; + + IconModel({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.iconBase64, + }); + + // Method to decode the icon from Base64 and return as Uint8List + Uint8List get iconBytes => base64Decode(iconBase64); + + // Factory constructor to create an instance from JSON + factory IconModel.fromJson(Map json) { + return IconModel( + uuid: json['uuid'] as String, + createdAt: DateTime.parse(json['createdAt'] as String), + updatedAt: DateTime.parse(json['updatedAt'] as String), + iconBase64: json['icon'] as String, + ); + } + + // Method to convert an instance back to JSON + Map toJson() { + return { + 'uuid': uuid, + 'createdAt': createdAt.toIso8601String(), + 'updatedAt': updatedAt.toIso8601String(), + 'icon': iconBase64, + }; + } +} diff --git a/lib/pages/routiens/view/effective_period_view.dart b/lib/pages/routiens/view/effective_period_view.dart new file mode 100644 index 00000000..5bcd9ff0 --- /dev/null +++ b/lib/pages/routiens/view/effective_period_view.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart'; +import 'package:syncrow_web/pages/routiens/helper/effictive_period_helper.dart'; +import 'package:syncrow_web/pages/routiens/widgets/period_option.dart'; +import 'package:syncrow_web/pages/routiens/widgets/repeat_days.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class EffectivePeriodView extends StatelessWidget { + const EffectivePeriodView({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => EffectPeriodBloc(), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: ListView( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Spacer(), + Expanded( + child: Text( + 'Effective Period', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 14), + ), + ), + const Spacer(), + ], + ), + const Divider( + color: ColorsManager.backgroundColor, + ), + const PeriodOptions( + showCustomTimePicker: EffectPeriodHelper.showCustomTimePicker, + ), + const SizedBox(height: 16), + const RepeatDays(), + const SizedBox(height: 24), + ], + ), + ), + ); + } +} diff --git a/lib/pages/routiens/widgets/period_option.dart b/lib/pages/routiens/widgets/period_option.dart new file mode 100644 index 00000000..acb69231 --- /dev/null +++ b/lib/pages/routiens/widgets/period_option.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart'; +import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart'; +import 'package:syncrow_web/pages/routiens/helper/effictive_period_helper.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/app_enum.dart'; + +class PeriodOptions extends StatelessWidget { + final Future?> Function(BuildContext) showCustomTimePicker; + + const PeriodOptions({ + required this.showCustomTimePicker, + super.key, + }); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Column( + children: [ + _buildRadioOption(context, EnumEffectivePeriodOptions.allDay, '24 Hours'), + _buildRadioOption(context, EnumEffectivePeriodOptions.daytime, 'Sunrise to Sunset'), + _buildRadioOption(context, EnumEffectivePeriodOptions.night, 'Sunset to Sunrise'), + ListTile( + contentPadding: EdgeInsets.zero, + onTap: () => showCustomTimePicker(context), + title: Text( + 'Custom', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 14), + ), + subtitle: state.customStartTime != null && state.customEndTime != null + ? Text( + '${"${state.customStartTime}"} - ${"${state.customEndTime}"}', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 10), + ) + : Text( + '00:00 - 23:59', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 10), + ), + trailing: Radio( + value: EnumEffectivePeriodOptions.custom, + groupValue: state.selectedPeriod, + onChanged: (value) async { + if (value != null) { + context.read().add(SetPeriod(value)); + } + showCustomTimePicker(context); + }, + ), + ), + ], + ); + }, + ); + } + + Widget _buildRadioOption( + BuildContext context, EnumEffectivePeriodOptions value, String subtitle) { + return BlocBuilder( + builder: (context, state) { + return ListTile( + contentPadding: EdgeInsets.zero, + onTap: () { + context.read().add(SetPeriod(value)); + }, + title: Text(EffectPeriodHelper.formatEnumValue(value)), + subtitle: Text( + subtitle, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.textPrimaryColor, fontWeight: FontWeight.w400, fontSize: 10), + ), + trailing: Radio( + value: value, + groupValue: state.selectedPeriod, + onChanged: (value) { + if (value != null) { + context.read().add(SetPeriod(value)); + } + }, + ), + ); + }, + ); + } +} diff --git a/lib/pages/routiens/widgets/repeat_days.dart b/lib/pages/routiens/widgets/repeat_days.dart new file mode 100644 index 00000000..8ee92367 --- /dev/null +++ b/lib/pages/routiens/widgets/repeat_days.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart'; +import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class RepeatDays extends StatelessWidget { + const RepeatDays({super.key}); + + @override + Widget build(BuildContext context) { + final effectiveBloc = context.read(); + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('Repeat', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.textPrimaryColor, fontWeight: FontWeight.w400, fontSize: 14)), + const SizedBox(width: 8), + BlocBuilder( + builder: (context, state) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: effectiveBloc.daysMap.entries.map((entry) { + final day = entry.key; + final abbreviation = entry.value; + final dayIndex = effectiveBloc.getDayIndex(day); + final isSelected = state.selectedDaysBinary[dayIndex] == '1'; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 3.0), + child: GestureDetector( + onTap: () { + effectiveBloc.add(ToggleDay(day)); + }, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: isSelected ? Colors.grey : Colors.grey.shade300, + width: 1, + ), + ), + child: CircleAvatar( + radius: 15, + backgroundColor: Colors.white, + child: Text( + abbreviation, + style: TextStyle( + fontSize: 16, + color: isSelected ? Colors.grey : Colors.grey.shade300, + ), + ), + ), + ), + ), + ); + }).toList(), + ), + const SizedBox( + height: 8, + ), + if (state.selectedDaysBinary == '0000000') + Text( + 'At least one day must be selected', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 14), + ), + ], + ); + }, + ), + ], + ); + } +} diff --git a/lib/pages/routiens/widgets/routine_search_and_buttons.dart b/lib/pages/routiens/widgets/routine_search_and_buttons.dart index 1acce727..598905e1 100644 --- a/lib/pages/routiens/widgets/routine_search_and_buttons.dart +++ b/lib/pages/routiens/widgets/routine_search_and_buttons.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart'; +import 'package:syncrow_web/pages/routiens/helper/setting_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -26,8 +27,8 @@ class RoutineSearchAndButtons extends StatelessWidget { crossAxisAlignment: WrapCrossAlignment.end, children: [ ConstrainedBox( - constraints: - BoxConstraints(maxWidth: constraints.maxWidth > 700 ? 450 : constraints.maxWidth - 32), + constraints: BoxConstraints( + maxWidth: constraints.maxWidth > 700 ? 450 : constraints.maxWidth - 32), child: StatefulTextField( title: 'Routine Name', height: 40, @@ -46,7 +47,10 @@ class RoutineSearchAndButtons extends StatelessWidget { width: 200, child: Center( child: DefaultButton( - onPressed: () {}, + onPressed: () async { + final result = await SettingHelper.showSettingDialog( + context: context, isAutomation: true); + }, borderRadius: 15, elevation: 0, borderColor: ColorsManager.greyColor, diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index edf865a3..7b001751 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -1,3 +1,4 @@ +import 'package:syncrow_web/pages/routiens/models/icon_model.dart'; import 'package:syncrow_web/pages/routiens/models/routine_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; @@ -41,6 +42,21 @@ class SceneApi { // } // } + static Future> getIcon() async { + final response = await _httpService.get( + path: ApiEndpoints.getIconScene, + showServerMessage: false, + expectedResponseModel: (json) { + List iconsList = []; + json.forEach((element) { + iconsList.add(IconModel.fromJson(element)); + }); + return iconsList; + }, + ); + return response; + } + //get scene by unit id static Future> getScenesByUnitId(String unitId) async { diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 759ef7d7..c7f7bccb 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -12,11 +12,13 @@ abstract class ApiEndpoints { static const String getDevices = '/visitor-password/devices'; static const String sendOnlineOneTime = '/visitor-password/temporary-password/online/one-time'; - static const String sendOnlineMultipleTime = '/visitor-password/temporary-password/online/multiple-time'; + static const String sendOnlineMultipleTime = + '/visitor-password/temporary-password/online/multiple-time'; //offline Password static const String sendOffLineOneTime = '/visitor-password/temporary-password/offline/one-time'; - static const String sendOffLineMultipleTime = '/visitor-password/temporary-password/offline/multiple-time'; + static const String sendOffLineMultipleTime = + '/visitor-password/temporary-password/offline/multiple-time'; static const String getUser = '/user/{userUuid}'; @@ -44,4 +46,5 @@ abstract class ApiEndpoints { static const String powerClamp = '/device/{powerClampUuid}/power-clamp/status'; static const String getSpaceScenes = '/scene/tap-to-run/{unitUuid}'; static const String getSpaceAutomation = '/automation/{unitUuid}'; + static const String getIconScene = '/scene/icon'; } diff --git a/lib/utils/constants/app_enum.dart b/lib/utils/constants/app_enum.dart index 4ca37d9b..feb5f55c 100644 --- a/lib/utils/constants/app_enum.dart +++ b/lib/utils/constants/app_enum.dart @@ -97,3 +97,11 @@ extension AccessStatusExtension on AccessStatus { enum TempModes { hot, cold, wind } enum FanSpeeds { auto, low, middle, high } + +enum EnumEffectivePeriodOptions { + allDay, + daytime, + night, + custom, + none, +} diff --git a/pubspec.lock b/pubspec.lock index 92a76b10..7c7270aa 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -300,18 +300,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.4" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.3" leak_tracker_testing: dependency: transitive description: @@ -348,18 +348,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.12.0" nested: dependency: transitive description: @@ -569,10 +569,18 @@ packages: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.0" + time_picker_spinner: + dependency: "direct main" + description: + name: time_picker_spinner + sha256: "53d824801d108890d22756501e7ade9db48b53dac1ec41580499dd4ebd128e3c" + url: "https://pub.dev" + source: hosted + version: "1.0.0" typed_data: dependency: transitive description: @@ -617,10 +625,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.2.1" web: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8dc6b6bc..c8f42ea5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -50,6 +50,7 @@ dependencies: dropdown_search: ^5.0.6 flutter_dotenv: ^5.1.0 fl_chart: ^0.69.0 + time_picker_spinner: ^1.0.0 dev_dependencies: flutter_test: From 7dccfb5a76ca44baddbc9636dacc07cedbfa195d Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sat, 23 Nov 2024 21:51:20 +0300 Subject: [PATCH 14/40] finished ac devices --- .../device_managment/ac/model/ac_model.dart | 5 +- .../batch_control_list/batch_ac_mode.dart | 14 +- .../batch_control_list/batch_fan_speed.dart | 17 +- .../ac/view/control_list/ac_mode.dart | 14 +- .../ac/view/control_list/fan_speed.dart | 17 +- .../functions_bloc/functions_bloc_bloc.dart | 126 +--- .../functions_bloc/functions_bloc_event.dart | 45 +- .../functions_bloc/functions_bloc_state.dart | 17 +- .../bloc/routine_bloc/routine_bloc.dart | 36 +- lib/pages/routiens/enum/ac_values_enum.dart | 9 - lib/pages/routiens/helper/ac_helper.dart | 250 ++++--- .../dialog_helper/device_dialog_helper.dart | 18 +- .../helper/one_gang_switch_helper.dart | 650 ++++++++++++------ .../helper/three_gang_switch_helper.dart | 2 +- .../helper/two_gang_switch_helper.dart | 2 +- lib/pages/routiens/models/ac/ac_function.dart | 90 +-- .../routiens/models/device_functions.dart | 21 +- .../gang_switches/base_switch_function.dart | 3 - .../one_gang_switch/one_gang_switch.dart | 10 - .../three_gang_switch/three_gang_switch.dart | 30 - .../two_gang_switch/two_gang_switch.dart | 24 +- lib/pages/routiens/widgets/dragable_card.dart | 109 ++- .../routiens/widgets/routine_devices.dart | 51 +- .../routiens/widgets/then_container.dart | 14 +- lib/utils/constants/app_enum.dart | 6 + 25 files changed, 810 insertions(+), 770 deletions(-) delete mode 100644 lib/pages/routiens/enum/ac_values_enum.dart diff --git a/lib/pages/device_managment/ac/model/ac_model.dart b/lib/pages/device_managment/ac/model/ac_model.dart index 2803e51e..1eb2145f 100644 --- a/lib/pages/device_managment/ac/model/ac_model.dart +++ b/lib/pages/device_managment/ac/model/ac_model.dart @@ -1,8 +1,5 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; - -enum TempModes { hot, cold, wind } - -enum FanSpeeds { auto, low, middle, high } +import 'package:syncrow_web/utils/constants/app_enum.dart'; class AcStatusModel { final String uuid; diff --git a/lib/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart b/lib/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart index 60d48256..81f0685d 100644 --- a/lib/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart +++ b/lib/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart @@ -3,9 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; -import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/app_enum.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class BatchAcMode extends StatelessWidget { @@ -27,15 +27,19 @@ class BatchAcMode extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - _buildIconContainer(context, TempModes.cold, Assets.freezing, value == TempModes.cold), - _buildIconContainer(context, TempModes.hot, Assets.acSun, value == TempModes.hot), - _buildIconContainer(context, TempModes.wind, Assets.acAirConditioner, value == TempModes.wind), + _buildIconContainer(context, TempModes.cold, Assets.freezing, + value == TempModes.cold), + _buildIconContainer( + context, TempModes.hot, Assets.acSun, value == TempModes.hot), + _buildIconContainer(context, TempModes.wind, Assets.acAirConditioner, + value == TempModes.wind), ], ), ); } - Widget _buildIconContainer(BuildContext context, TempModes mode, String assetPath, bool isSelected) { + Widget _buildIconContainer( + BuildContext context, TempModes mode, String assetPath, bool isSelected) { return Flexible( child: GestureDetector( onTap: () { diff --git a/lib/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart b/lib/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart index ba49047a..4d7eb449 100644 --- a/lib/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart +++ b/lib/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart @@ -3,9 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; -import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/app_enum.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class BatchFanSpeedControl extends StatelessWidget { @@ -30,8 +30,10 @@ class BatchFanSpeedControl extends StatelessWidget { runSpacing: 8, spacing: 8, children: [ - _buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto, value == FanSpeeds.auto), - _buildIconContainer(context, FanSpeeds.low, Assets.acFanLow, value == FanSpeeds.low), + _buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto, + value == FanSpeeds.auto), + _buildIconContainer(context, FanSpeeds.low, Assets.acFanLow, + value == FanSpeeds.low), ], ), const SizedBox(height: 8), @@ -39,8 +41,10 @@ class BatchFanSpeedControl extends StatelessWidget { runSpacing: 8, spacing: 8, children: [ - _buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle, value == FanSpeeds.middle), - _buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh, value == FanSpeeds.high), + _buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle, + value == FanSpeeds.middle), + _buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh, + value == FanSpeeds.high), ], ) ], @@ -48,7 +52,8 @@ class BatchFanSpeedControl extends StatelessWidget { ); } - Widget _buildIconContainer(BuildContext context, FanSpeeds speed, String assetPath, bool isSelected) { + Widget _buildIconContainer(BuildContext context, FanSpeeds speed, + String assetPath, bool isSelected) { return GestureDetector( onTap: () { context.read().add( diff --git a/lib/pages/device_managment/ac/view/control_list/ac_mode.dart b/lib/pages/device_managment/ac/view/control_list/ac_mode.dart index c6ffc052..91ca0f8c 100644 --- a/lib/pages/device_managment/ac/view/control_list/ac_mode.dart +++ b/lib/pages/device_managment/ac/view/control_list/ac_mode.dart @@ -3,9 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; -import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/app_enum.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class AcMode extends StatelessWidget { @@ -27,15 +27,19 @@ class AcMode extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - _buildIconContainer(context, TempModes.cold, Assets.freezing, value == TempModes.cold), - _buildIconContainer(context, TempModes.hot, Assets.acSun, value == TempModes.hot), - _buildIconContainer(context, TempModes.wind, Assets.acAirConditioner, value == TempModes.wind), + _buildIconContainer(context, TempModes.cold, Assets.freezing, + value == TempModes.cold), + _buildIconContainer( + context, TempModes.hot, Assets.acSun, value == TempModes.hot), + _buildIconContainer(context, TempModes.wind, Assets.acAirConditioner, + value == TempModes.wind), ], ), ); } - Widget _buildIconContainer(BuildContext context, TempModes mode, String assetPath, bool isSelected) { + Widget _buildIconContainer( + BuildContext context, TempModes mode, String assetPath, bool isSelected) { return Flexible( child: GestureDetector( onTap: () { diff --git a/lib/pages/device_managment/ac/view/control_list/fan_speed.dart b/lib/pages/device_managment/ac/view/control_list/fan_speed.dart index 952e112b..09ca80cb 100644 --- a/lib/pages/device_managment/ac/view/control_list/fan_speed.dart +++ b/lib/pages/device_managment/ac/view/control_list/fan_speed.dart @@ -3,9 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; -import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/app_enum.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class FanSpeedControl extends StatelessWidget { @@ -29,8 +29,10 @@ class FanSpeedControl extends StatelessWidget { runSpacing: 8, spacing: 8, children: [ - _buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto, value == FanSpeeds.auto), - _buildIconContainer(context, FanSpeeds.low, Assets.acFanLow, value == FanSpeeds.low), + _buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto, + value == FanSpeeds.auto), + _buildIconContainer(context, FanSpeeds.low, Assets.acFanLow, + value == FanSpeeds.low), ], ), const SizedBox(height: 8), @@ -38,8 +40,10 @@ class FanSpeedControl extends StatelessWidget { runSpacing: 8, spacing: 8, children: [ - _buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle, value == FanSpeeds.middle), - _buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh, value == FanSpeeds.high), + _buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle, + value == FanSpeeds.middle), + _buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh, + value == FanSpeeds.high), ], ) ], @@ -47,7 +51,8 @@ class FanSpeedControl extends StatelessWidget { ); } - Widget _buildIconContainer(BuildContext context, FanSpeeds speed, String assetPath, bool isSelected) { + Widget _buildIconContainer(BuildContext context, FanSpeeds speed, + String assetPath, bool isSelected) { return GestureDetector( onTap: () { context.read().add( diff --git a/lib/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart b/lib/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart index 54b47cad..760d5697 100644 --- a/lib/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart +++ b/lib/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart @@ -1,6 +1,7 @@ +import 'dart:async'; + import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; part 'functions_bloc_event.dart'; @@ -10,120 +11,32 @@ class FunctionBloc extends Bloc { FunctionBloc() : super(const FunctionBlocState()) { on(_onInitializeFunctions); on(_onAddFunction); - on(_onUpdateFunctions); - on(_onUpdateFunctionValue); - on(_onUpdateFunctionCondition); - on(_onRemoveFunction); + on(_onSelectFunction); } - void _onAddFunction(AddFunction event, Emitter emit) { - debugPrint('Adding function: ${event.functionData.function}'); - final functions = List.from(state.functions); - - // Find existing function data + final functions = List.from(state.addedFunctions); final existingIndex = functions.indexWhere( - (f) => f.function == event.functionData.function, + (f) => f.functionCode == event.functionData.functionCode, ); - // If function exists, preserve its value and condition if (existingIndex != -1) { final existingData = functions[existingIndex]; functions[existingIndex] = DeviceFunctionData( entityId: event.functionData.entityId, - function: event.functionData.function, + functionCode: event.functionData.functionCode, operationName: event.functionData.operationName, - value: existingData.value, // Preserve the existing value - condition: existingData.condition, // Preserve the existing condition + value: event.functionData.value ?? existingData.value, + valueDescription: event.functionData.valueDescription ?? + existingData.valueDescription, + condition: event.functionData.condition ?? existingData.condition, ); } else { functions.add(event.functionData); } - debugPrint('Functions after add: $functions'); emit(state.copyWith( - functions: functions, - selectedFunction: event.functionData.function, - )); - } - - void _onUpdateFunctions( - UpdateFunction event, Emitter emit) { - final functions = state.functions.map((data) { - return data.function == event.functionData.function - ? event.functionData - : data; - }).toList(); - - emit(state.copyWith(functions: functions)); - } - - void _onUpdateFunctionValue( - UpdateFunctionValue event, - Emitter emit, - ) { - debugPrint('Updating function value: ${event.function} -> ${event.value}'); - - // Create a new list to ensure state immutability - final functions = List.from(state.functions); - - // Find the index of the function to update - final functionIndex = functions.indexWhere( - (data) => data.function == event.function, - ); - - if (functionIndex != -1) { - // Update the existing function data while preserving other fields - final existingData = functions[functionIndex]; - functions[functionIndex] = DeviceFunctionData( - entityId: existingData.entityId, - function: existingData.function, - operationName: existingData.operationName, - value: event.value, - condition: existingData.condition, - ); - } else { - // If function doesn't exist, add it - functions.add(DeviceFunctionData( - entityId: '', - function: event.function, - operationName: '', - value: event.value, - )); - } - - debugPrint('Functions after update: $functions'); - emit(state.copyWith(functions: functions)); - } - - void _onUpdateFunctionCondition( - UpdateFunctionCondition event, - Emitter emit, - ) { - final functions = state.functions.map((data) { - if (data.function == event.function) { - return DeviceFunctionData( - entityId: data.entityId, - function: data.function, - operationName: data.operationName, - value: data.value, - condition: event.condition, - ); - } - return data; - }).toList(); - - emit(state.copyWith(functions: functions)); - } - - void _onRemoveFunction( - RemoveFunction event, Emitter emit) { - final functions = state.functions - .where((data) => data.function != event.functionCode) - .toList(); - - emit(state.copyWith( - functions: functions, - selectedFunction: functions.isEmpty ? null : state.selectedFunction, + addedFunctions: functions, + selectedFunction: event.functionData.functionCode, )); } @@ -131,18 +44,25 @@ class FunctionBloc extends Bloc { InitializeFunctions event, Emitter emit, ) { - emit(state.copyWith(functions: event.functions)); + emit(state.copyWith(addedFunctions: event.functions)); } DeviceFunctionData? getFunction(String functionCode) { - return state.functions.firstWhere( - (data) => data.function == functionCode, + return state.addedFunctions.firstWhere( + (data) => data.functionCode == functionCode, orElse: () => DeviceFunctionData( entityId: '', - function: functionCode, + functionCode: functionCode, operationName: '', value: null, ), ); } + + FutureOr _onSelectFunction( + SelectFunction event, Emitter emit) { + emit(state.copyWith( + selectedFunction: event.functionCode, + selectedOperationName: event.operationName)); + } } diff --git a/lib/pages/routiens/bloc/functions_bloc/functions_bloc_event.dart b/lib/pages/routiens/bloc/functions_bloc/functions_bloc_event.dart index cceb6d21..85ba62f4 100644 --- a/lib/pages/routiens/bloc/functions_bloc/functions_bloc_event.dart +++ b/lib/pages/routiens/bloc/functions_bloc/functions_bloc_event.dart @@ -18,48 +18,17 @@ class AddFunction extends FunctionBlocEvent { List get props => [functionData]; } -class UpdateFunction extends FunctionBlocEvent { - final DeviceFunctionData functionData; - - const UpdateFunction(this.functionData); - - @override - List get props => [functionData]; -} - -class UpdateFunctionValue extends FunctionBlocEvent { - final String function; - final dynamic value; - - const UpdateFunctionValue({ - required this.function, - required this.value, - }); - - @override - List get props => [function, value]; -} - -class UpdateFunctionCondition extends FunctionBlocEvent { - final String function; - final String condition; - - const UpdateFunctionCondition({ - required this.function, - required this.condition, - }); - - @override - List get props => [function, condition]; -} - -class RemoveFunction extends FunctionBlocEvent { +class SelectFunction extends FunctionBlocEvent { final String functionCode; + final String operationName; - const RemoveFunction(this.functionCode); + const SelectFunction({ + required this.functionCode, + required this.operationName, + }); @override - List get props => [functionCode]; + List get props => [functionCode, operationName]; } class InitializeFunctions extends FunctionBlocEvent { diff --git a/lib/pages/routiens/bloc/functions_bloc/functions_bloc_state.dart b/lib/pages/routiens/bloc/functions_bloc/functions_bloc_state.dart index af854b8a..2c6cd941 100644 --- a/lib/pages/routiens/bloc/functions_bloc/functions_bloc_state.dart +++ b/lib/pages/routiens/bloc/functions_bloc/functions_bloc_state.dart @@ -1,24 +1,29 @@ part of 'functions_bloc_bloc.dart'; class FunctionBlocState extends Equatable { - final List functions; + final List addedFunctions; final String? selectedFunction; - + final String? selectedOperationName; const FunctionBlocState({ - this.functions = const [], + this.addedFunctions = const [], this.selectedFunction, + this.selectedOperationName, }); FunctionBlocState copyWith({ - List? functions, + List? addedFunctions, String? selectedFunction, + String? selectedOperationName, }) { return FunctionBlocState( - functions: functions ?? this.functions, + addedFunctions: addedFunctions ?? this.addedFunctions, selectedFunction: selectedFunction ?? this.selectedFunction, + selectedOperationName: + selectedOperationName ?? this.selectedOperationName, ); } @override - List get props => [functions, selectedFunction]; + List get props => + [addedFunctions, selectedFunction, selectedOperationName]; } diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 20a3c393..2b711f39 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -1,5 +1,6 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/routine_model.dart'; import 'package:syncrow_web/services/routines_api.dart'; @@ -21,32 +22,33 @@ class RoutineBloc extends Bloc { } void _onAddToIfContainer(AddToIfContainer event, Emitter emit) { - if (!_isDuplicate(state.ifItems, event.item)) { - final updatedIfItems = List>.from(state.ifItems) - ..add(event.item); - emit(state.copyWith(ifItems: updatedIfItems)); - } + // if (!_isDuplicate(state.ifItems, event.item)) { + final updatedIfItems = List>.from(state.ifItems) + ..add(event.item); + emit(state.copyWith(ifItems: updatedIfItems)); + // } } void _onAddToThenContainer( AddToThenContainer event, Emitter emit) { - if (!_isDuplicate(state.thenItems, event.item)) { - final updatedThenItems = List>.from(state.thenItems) - ..add(event.item); - emit(state.copyWith(thenItems: updatedThenItems)); - } + // if (!_isDuplicate(state.thenItems, event.item)) { + final updatedThenItems = List>.from(state.thenItems) + ..add(event.item); + emit(state.copyWith(thenItems: updatedThenItems)); + // } } void _onAddFunction(AddFunctionToRoutine event, Emitter emit) { final functions = List.from(state.selectedFunctions); functions.add(event.function); + debugPrint("******" + functions.toString()); emit(state.copyWith(selectedFunctions: functions)); } void _onRemoveFunction(RemoveFunction event, Emitter emit) { final functions = List.from(state.selectedFunctions) ..removeWhere((f) => - f.function == event.function.function && + f.functionCode == event.function.functionCode && f.value == event.function.value); emit(state.copyWith(selectedFunctions: functions)); } @@ -55,12 +57,12 @@ class RoutineBloc extends Bloc { emit(state.copyWith(selectedFunctions: [])); } - bool _isDuplicate( - List> items, Map newItem) { - return items.any((item) => - item['imagePath'] == newItem['imagePath'] && - item['title'] == newItem['title']); - } + // bool _isDuplicate( + // List> items, Map newItem) { + // return items.any((item) => + // item['imagePath'] == newItem['imagePath'] && + // item['title'] == newItem['title']); + // } Future _onLoadScenes( LoadScenes event, Emitter emit) async { diff --git a/lib/pages/routiens/enum/ac_values_enum.dart b/lib/pages/routiens/enum/ac_values_enum.dart deleted file mode 100644 index e7d0f865..00000000 --- a/lib/pages/routiens/enum/ac_values_enum.dart +++ /dev/null @@ -1,9 +0,0 @@ -enum AcValuesEnums { - Cooling, - Heating, - Ventilation, -} - -enum TempModes { hot, cold, wind } - -enum FanSpeeds { auto, low, middle, high } diff --git a/lib/pages/routiens/helper/ac_helper.dart b/lib/pages/routiens/helper/ac_helper.dart index 4a42c43d..9ba02633 100644 --- a/lib/pages/routiens/helper/ac_helper.dart +++ b/lib/pages/routiens/helper/ac_helper.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart'; +import 'package:syncrow_web/pages/routiens/models/ac/ac_operational_value.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; @@ -13,42 +15,32 @@ import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bl class ACHelper { static Future?> showACFunctionsDialog( BuildContext context, - List> functions, + List functions, + AllDevicesModel? device, + List? deviceSelectedFunctions, ) async { List acFunctions = functions.whereType().toList(); - // Initialize the FunctionBloc with existing functions - final initialFunctions = acFunctions - .map((f) => DeviceFunctionData( - entityId: '', - function: f.code, - operationName: f.operationName, - value: null, - )) - .toList(); - return showDialog?>( context: context, builder: (BuildContext context) { return BlocProvider( - create: (_) => - FunctionBloc()..add(InitializeFunctions(initialFunctions)), + create: (_) => FunctionBloc() + ..add(InitializeFunctions(deviceSelectedFunctions ?? [])), child: AlertDialog( contentPadding: EdgeInsets.zero, content: BlocBuilder( builder: (context, state) { final selectedFunction = state.selectedFunction; - final selectedFunctionData = selectedFunction != null - ? state.functions.firstWhere( - (f) => f.function == selectedFunction, + final selectedOperationName = state.selectedOperationName; + final selectedFunctionData = state.addedFunctions + .firstWhere((f) => f.functionCode == selectedFunction, orElse: () => DeviceFunctionData( - entityId: '', - function: selectedFunction, - operationName: '', - value: null, - ), - ) - : null; + entityId: '', + functionCode: selectedFunction ?? '', + operationName: '', + value: null, + )); return Container( width: selectedFunction != null ? 600 : 360, @@ -70,41 +62,27 @@ class ACHelper { SizedBox( width: selectedFunction != null ? 320 : 360, child: _buildFunctionsList( - context, - acFunctions, - (functionCode) => - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: '', - function: functionCode, - operationName: '', - value: null, - )), - ), + context: context, + acFunctions: acFunctions, + onFunctionSelected: + (functionCode, operationName) => context + .read() + .add(SelectFunction( + functionCode: functionCode, + operationName: operationName, + )), ), ), // Value selector if (selectedFunction != null) Expanded( child: _buildValueSelector( - context, - selectedFunction, - selectedFunctionData, - (value) => context.read().add( - UpdateFunctionValue( - function: selectedFunction, - value: value, - ), - ), - (condition) => - context.read().add( - UpdateFunctionCondition( - function: selectedFunction, - condition: condition, - ), - ), - acFunctions, + context: context, + selectedFunction: selectedFunction, + selectedFunctionData: selectedFunctionData, + acFunctions: acFunctions, + device: device, + operationName: selectedOperationName ?? '', ), ), ], @@ -114,11 +92,10 @@ class ACHelper { onCancel: () { Navigator.pop(context); }, - onConfirm: selectedFunction != null && - selectedFunctionData?.value != null + onConfirm: state.addedFunctions.isNotEmpty ? () { /// add the functions to the routine bloc - for (var function in state.functions) { + for (var function in state.addedFunctions) { context.read().add( AddFunctionToRoutine( function, @@ -126,7 +103,9 @@ class ACHelper { ); } // Return the device data to be added to the container - Navigator.pop(context); + Navigator.pop(context, { + 'deviceId': functions.first.deviceId, + }); } : null, isConfirmEnabled: selectedFunction != null, @@ -145,11 +124,11 @@ class ACHelper { } /// Build functions list for AC functions dialog - static Widget _buildFunctionsList( - BuildContext context, - List acFunctions, - Function(String) onFunctionSelected, - ) { + static Widget _buildFunctionsList({ + required BuildContext context, + required List acFunctions, + required Function(String, String) onFunctionSelected, + }) { return ListView.separated( shrinkWrap: false, physics: const AlwaysScrollableScrollPhysics(), @@ -182,29 +161,34 @@ class ACHelper { size: 16, color: ColorsManager.textGray, ), - onTap: () => onFunctionSelected(function.code), + onTap: () => onFunctionSelected( + function.code, + function.operationName, + ), ); }, ); } /// Build value selector for AC functions dialog - static Widget _buildValueSelector( - BuildContext context, - String selectedFunction, - DeviceFunctionData? selectedFunctionData, - Function(dynamic) onValueChanged, - Function(String) onConditionChanged, - List acFunctions, - ) { + static Widget _buildValueSelector({ + required BuildContext context, + required String selectedFunction, + required DeviceFunctionData? selectedFunctionData, + required List acFunctions, + AllDevicesModel? device, + required String operationName, + }) { if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') { final initialValue = selectedFunctionData?.value ?? 200; return _buildTemperatureSelector( - context, - initialValue, - selectedFunctionData?.condition, - onValueChanged, - onConditionChanged, + context: context, + initialValue: initialValue, + selectCode: selectedFunction, + currentCondition: selectedFunctionData?.condition, + device: device, + operationName: operationName, + selectedFunctionData: selectedFunctionData, ); } @@ -213,37 +197,43 @@ class ACHelper { final values = selectedFn.getOperationalValues(); return _buildOperationalValuesList( - context, - values, - selectedFunctionData?.value, - onValueChanged, + context: context, + values: values, + selectedValue: selectedFunctionData?.value, + device: device, + operationName: operationName, + selectCode: selectedFunction, + selectedFunctionData: selectedFunctionData, ); } /// Build temperature selector for AC functions dialog - static Widget _buildTemperatureSelector( - BuildContext context, - dynamic initialValue, - String? currentCondition, - Function(dynamic) onValueChanged, - Function(String) onConditionChanged, - ) { + static Widget _buildTemperatureSelector({ + required BuildContext context, + required dynamic initialValue, + required String? currentCondition, + required String selectCode, + AllDevicesModel? device, + required String operationName, + DeviceFunctionData? selectedFunctionData, + }) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ _buildConditionToggle( context, currentCondition, - onConditionChanged, + selectCode, + device, + operationName, + selectedFunctionData, ), const SizedBox(height: 20), - _buildTemperatureDisplay(context, initialValue), + _buildTemperatureDisplay(context, initialValue, device, operationName, + selectedFunctionData, selectCode), const SizedBox(height: 20), - _buildTemperatureSlider( - context, - initialValue, - onValueChanged, - ), + _buildTemperatureSlider(context, initialValue, device, operationName, + selectedFunctionData, selectCode), ], ); } @@ -252,13 +242,28 @@ class ACHelper { static Widget _buildConditionToggle( BuildContext context, String? currentCondition, - Function(String) onConditionChanged, + String selectCode, + AllDevicesModel? device, + String operationName, + DeviceFunctionData? selectedFunctionData, + // Function(String) onConditionChanged, ) { final conditions = ["<", "==", ">"]; return ToggleButtons( onPressed: (int index) { - onConditionChanged(conditions[index]); + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + condition: conditions[index], + value: selectedFunctionData?.value, + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); }, borderRadius: const BorderRadius.all(Radius.circular(8)), selectedBorderColor: ColorsManager.primaryColorWithOpacity, @@ -277,7 +282,12 @@ class ACHelper { /// Build temperature display for AC functions dialog static Widget _buildTemperatureDisplay( - BuildContext context, dynamic initialValue) { + BuildContext context, + dynamic initialValue, + AllDevicesModel? device, + String operationName, + DeviceFunctionData? selectedFunctionData, + String selectCode) { return Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), decoration: BoxDecoration( @@ -296,7 +306,10 @@ class ACHelper { static Widget _buildTemperatureSlider( BuildContext context, dynamic initialValue, - Function(dynamic) onValueChanged, + AllDevicesModel? device, + String operationName, + DeviceFunctionData? selectedFunctionData, + String selectCode, ) { return Slider( value: initialValue is int ? initialValue.toDouble() : 200.0, @@ -305,17 +318,32 @@ class ACHelper { divisions: 14, label: '${((initialValue ?? 200) / 10).toInt()}°C', onChanged: (value) { - onValueChanged(value.toInt()); + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: value, + condition: selectedFunctionData?.condition, + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); }, ); } - static Widget _buildOperationalValuesList( - BuildContext context, - List values, - dynamic selectedValue, - Function(dynamic) onValueChanged, - ) { + static Widget _buildOperationalValuesList({ + required BuildContext context, + required List values, + required dynamic selectedValue, + AllDevicesModel? device, + required String operationName, + required String selectCode, + DeviceFunctionData? selectedFunctionData, + // required Function(dynamic) onValueChanged, + }) { return ListView.builder( shrinkWrap: false, physics: const AlwaysScrollableScrollPhysics(), @@ -349,7 +377,19 @@ class ACHelper { ), onTap: () { if (!isSelected) { - onValueChanged(value.value); + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: value.value, + condition: selectedFunctionData?.condition, + valueDescription: + selectedFunctionData?.valueDescription, + ), + ), + ); } }, ); diff --git a/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart index 2b21d83d..714feda3 100644 --- a/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/helper/ac_helper.dart'; import 'package:syncrow_web/pages/routiens/helper/one_gang_switch_helper.dart'; import 'package:syncrow_web/pages/routiens/helper/three_gang_switch_helper.dart'; @@ -16,6 +18,7 @@ class DeviceDialogHelper { final result = await _getDialogForDeviceType( context, data['productType'], + data, functions, ); @@ -32,21 +35,30 @@ class DeviceDialogHelper { static Future?> _getDialogForDeviceType( BuildContext context, String productType, + Map data, List functions, ) async { + final routineBloc = context.read(); + final deviceSelectedFunctions = routineBloc.state.selectedFunctions + .where((f) => f.entityId == data['deviceId']) + .toList(); + switch (productType) { case 'AC': - return ACHelper.showACFunctionsDialog(context, functions); + return ACHelper.showACFunctionsDialog( + context, functions, data['device'], deviceSelectedFunctions); + case '1G': return OneGangSwitchHelper.showSwitchFunctionsDialog( - context, functions); + context, functions, data['device'], deviceSelectedFunctions); case '2G': return TwoGangSwitchHelper.showSwitchFunctionsDialog( context, functions); case '3G': return ThreeGangSwitchHelper.showSwitchFunctionsDialog( context, functions); - break; + default: + return null; } } } diff --git a/lib/pages/routiens/helper/one_gang_switch_helper.dart b/lib/pages/routiens/helper/one_gang_switch_helper.dart index 014e41f9..6cb28424 100644 --- a/lib/pages/routiens/helper/one_gang_switch_helper.dart +++ b/lib/pages/routiens/helper/one_gang_switch_helper.dart @@ -1,5 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart'; +import 'package:syncrow_web/pages/routiens/models/ac/ac_operational_value.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart'; @@ -11,59 +17,57 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; class OneGangSwitchHelper { static Future?> showSwitchFunctionsDialog( BuildContext context, - List> functions, + List functions, + AllDevicesModel? device, + List? deviceSelectedFunctions, ) async { - List> switchFunctions = functions - .where( - (f) => f is OneGangSwitchFunction || f is OneGangCountdownFunction) - .toList(); - final selectedFunctionNotifier = ValueNotifier(null); - final selectedValueNotifier = ValueNotifier(null); - final selectedConditionNotifier = ValueNotifier("<"); - final selectedConditionsNotifier = - ValueNotifier>([true, false, false]); + List acFunctions = functions.whereType().toList(); return showDialog?>( context: context, builder: (BuildContext context) { - return ValueListenableBuilder( - valueListenable: selectedFunctionNotifier, - builder: (context, selectedFunction, _) { - return AlertDialog( + return BlocProvider( + create: (_) => FunctionBloc() + ..add(InitializeFunctions(deviceSelectedFunctions ?? [])), + child: AlertDialog( contentPadding: EdgeInsets.zero, - content: Container( - width: selectedFunction != null ? 600 : 360, - height: 450, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - ), - padding: const EdgeInsets.only(top: 20), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const DialogHeader('1 Gang Light Switch Condition'), - Expanded( - child: Row( - children: [ - // Left side: Function list - Expanded( - child: ListView.separated( - itemCount: switchFunctions.length, - separatorBuilder: (_, __) => const Divider( - color: ColorsManager.dividerColor, - ), - itemBuilder: (context, index) { - final function = switchFunctions[index]; - return ValueListenableBuilder( - valueListenable: selectedFunctionNotifier, - builder: (context, selectedFunction, _) { - final isSelected = - selectedFunction == function.code; + content: BlocBuilder( + builder: (context, state) { + final selectedFunction = state.selectedFunction; + final selectedOperationName = state.selectedOperationName; + final selectedFunctionData = state.addedFunctions + .firstWhere((f) => f.functionCode == selectedFunction, + orElse: () => DeviceFunctionData( + entityId: '', + functionCode: selectedFunction ?? '', + operationName: '', + value: null, + )); + return Container( + width: selectedFunction != null ? 600 : 360, + height: 450, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const DialogHeader('1 Gang Light Switch Condition'), + Expanded( + child: Row( + children: [ + // Left side: Function list + Expanded( + child: ListView.separated( + itemCount: acFunctions.length, + separatorBuilder: (_, __) => const Divider( + color: ColorsManager.dividerColor, + ), + itemBuilder: (context, index) { + final function = acFunctions[index]; return ListTile( - tileColor: isSelected - ? Colors.grey.shade100 - : null, leading: SvgPicture.asset( function.icon, width: 24, @@ -79,191 +83,413 @@ class OneGangSwitchHelper { color: ColorsManager.textGray, ), onTap: () { - selectedFunctionNotifier.value = - function.code; - selectedValueNotifier.value = - function is OneGangCountdownFunction - ? 0 - : null; + context + .read() + .add(SelectFunction( + functionCode: function.code, + operationName: + function.operationName, + )); }, ); }, - ); - }, - ), + ), + ), + // Right side: Value selector + if (selectedFunction != null) + Expanded( + child: _buildValueSelector( + context: context, + selectedFunction: selectedFunction, + selectedFunctionData: selectedFunctionData, + acFunctions: acFunctions, + device: device, + operationName: selectedOperationName ?? '', + ), + ), + // ValueListenableBuilder( + // valueListenable: selectedFunctionNotifier, + // builder: (context, selectedFunction, _) { + // final selectedFn = + // switchFunctions.firstWhere( + // (f) => f.code == selectedFunction, + // ); + // return Expanded( + // child: selectedFn + // is OneGangCountdownFunction + // ? _buildCountDownSelector( + // context, + // selectedValueNotifier, + // selectedConditionNotifier, + // selectedConditionsNotifier, + // ) + // : _buildOperationalValuesList( + // context, + // selectedFn as BaseSwitchFunction, + // selectedValueNotifier, + // ), + // ); + // }, + // ), + ], ), - // Right side: Value selector - if (selectedFunction != null) - ValueListenableBuilder( - valueListenable: selectedFunctionNotifier, - builder: (context, selectedFunction, _) { - final selectedFn = switchFunctions.firstWhere( - (f) => f.code == selectedFunction, - ); - return Expanded( - child: selectedFn is OneGangCountdownFunction - ? _buildCountDownSelector( - context, - selectedValueNotifier, - selectedConditionNotifier, - selectedConditionsNotifier, - ) - : _buildOperationalValuesList( - context, - selectedFn as BaseSwitchFunction, - selectedValueNotifier, - ), - ); - }, - ), - ], - ), + ), + Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + DialogFooter( + onCancel: () { + Navigator.pop(context); + }, + onConfirm: state.addedFunctions.isNotEmpty + ? () { + /// add the functions to the routine bloc + for (var function in state.addedFunctions) { + context.read().add( + AddFunctionToRoutine( + function, + ), + ); + } + // Return the device data to be added to the container + Navigator.pop(context, { + 'deviceId': functions.first.deviceId, + }); + } + : null, + isConfirmEnabled: selectedFunction != null, + ), + ], ), - Container( - height: 1, - width: double.infinity, - color: ColorsManager.greyColor, - ), - DialogFooter( - onCancel: () => Navigator.pop(context), - onConfirm: selectedFunctionNotifier.value != null && - selectedValueNotifier.value != null - ? () { - final selectedFn = switchFunctions.firstWhere( - (f) => f.code == selectedFunctionNotifier.value, - ); - final value = selectedValueNotifier.value; - final functionData = DeviceFunctionData( - entityId: selectedFn.deviceId, - function: selectedFn.code, - operationName: selectedFn.operationName, - value: value, - condition: selectedConditionNotifier.value, - valueDescription: - selectedFn is OneGangCountdownFunction - ? '${value} sec' - : ((selectedFn as BaseSwitchFunction) - .getOperationalValues() - .firstWhere((v) => v.value == value) - .description), - ); - Navigator.pop( - context, {selectedFn.code: functionData}); - } - : null, - isConfirmEnabled: selectedFunctionNotifier.value != null, - ), - ], + ); + }, + ), + )); + }, + ); + } + + static Widget _buildValueSelector({ + required BuildContext context, + required String selectedFunction, + required DeviceFunctionData? selectedFunctionData, + required List acFunctions, + AllDevicesModel? device, + required String operationName, + }) { + if (selectedFunction == 'countdown_1') { + final initialValue = selectedFunctionData?.value ?? 200; + return _buildTemperatureSelector( + context: context, + initialValue: initialValue, + selectCode: selectedFunction, + currentCondition: selectedFunctionData?.condition, + device: device, + operationName: operationName, + selectedFunctionData: selectedFunctionData, + ); + } + + final selectedFn = + acFunctions.firstWhere((f) => f.code == selectedFunction); + final values = selectedFn.getOperationalValues(); + + return _buildOperationalValuesList( + context: context, + values: values, + selectedValue: selectedFunctionData?.value, + device: device, + operationName: operationName, + selectCode: selectedFunction, + selectedFunctionData: selectedFunctionData, + ); + } + + static Widget _buildTemperatureSelector({ + required BuildContext context, + required dynamic initialValue, + required String? currentCondition, + required String selectCode, + AllDevicesModel? device, + required String operationName, + DeviceFunctionData? selectedFunctionData, + }) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildConditionToggle( + context, + currentCondition, + selectCode, + device, + operationName, + selectedFunctionData, + ), + const SizedBox(height: 20), + _buildTemperatureDisplay(context, initialValue, device, operationName, + selectedFunctionData, selectCode), + const SizedBox(height: 20), + _buildTemperatureSlider(context, initialValue, device, operationName, + selectedFunctionData, selectCode), + ], + ); + } + + /// Build condition toggle for AC functions dialog + static Widget _buildConditionToggle( + BuildContext context, + String? currentCondition, + String selectCode, + AllDevicesModel? device, + String operationName, + DeviceFunctionData? selectedFunctionData, + // Function(String) onConditionChanged, + ) { + final conditions = ["<", "==", ">"]; + + return ToggleButtons( + onPressed: (int index) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + condition: conditions[index], + value: selectedFunctionData?.value, + valueDescription: selectedFunctionData?.valueDescription, ), ), ); - }, - ); - }, - ).then((value) { - selectedFunctionNotifier.dispose(); - selectedValueNotifier.dispose(); - selectedConditionNotifier.dispose(); - selectedConditionsNotifier.dispose(); - return value; - }); - } - - static Widget _buildCountDownSelector( - BuildContext context, - ValueNotifier valueNotifier, - ValueNotifier conditionNotifier, - ValueNotifier> conditionsNotifier, - ) { - return ValueListenableBuilder( - valueListenable: valueNotifier, - builder: (context, value, _) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ValueListenableBuilder>( - valueListenable: conditionsNotifier, - builder: (context, selectedConditions, _) { - return ToggleButtons( - onPressed: (int index) { - final newConditions = List.filled(3, false); - newConditions[index] = true; - conditionsNotifier.value = newConditions; - conditionNotifier.value = index == 0 - ? "<" - : index == 1 - ? "==" - : ">"; - }, - borderRadius: const BorderRadius.all(Radius.circular(8)), - selectedBorderColor: ColorsManager.primaryColorWithOpacity, - selectedColor: Colors.white, - fillColor: ColorsManager.primaryColorWithOpacity, - color: ColorsManager.primaryColorWithOpacity, - constraints: const BoxConstraints( - minHeight: 40.0, - minWidth: 40.0, - ), - isSelected: selectedConditions, - children: const [Text("<"), Text("="), Text(">")], - ); - }, - ), - const SizedBox(height: 20), - Text( - '${value ?? 0} sec', - style: Theme.of(context).textTheme.headlineMedium, - ), - const SizedBox(height: 20), - Slider( - value: (value ?? 0).toDouble(), - min: 0, - max: 300, - divisions: 300, - onChanged: (newValue) { - valueNotifier.value = newValue.toInt(); - }, - ), - ], - ); }, + borderRadius: const BorderRadius.all(Radius.circular(8)), + selectedBorderColor: ColorsManager.primaryColorWithOpacity, + selectedColor: Colors.white, + fillColor: ColorsManager.primaryColorWithOpacity, + color: ColorsManager.primaryColorWithOpacity, + constraints: const BoxConstraints( + minHeight: 40.0, + minWidth: 40.0, + ), + isSelected: + conditions.map((c) => c == (currentCondition ?? "==")).toList(), + children: conditions.map((c) => Text(c)).toList(), ); } - static Widget _buildOperationalValuesList( + /// Build temperature display for AC functions dialog + static Widget _buildTemperatureDisplay( + BuildContext context, + dynamic initialValue, + AllDevicesModel? device, + String operationName, + DeviceFunctionData? selectedFunctionData, + String selectCode) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + '${initialValue ?? 0} sec', + style: context.textTheme.headlineMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), + ), + ); + } + + static Widget _buildTemperatureSlider( BuildContext context, - BaseSwitchFunction function, - ValueNotifier valueNotifier, + dynamic initialValue, + AllDevicesModel? device, + String operationName, + DeviceFunctionData? selectedFunctionData, + String selectCode, ) { - final values = function.getOperationalValues(); - return ValueListenableBuilder( - valueListenable: valueNotifier, - builder: (context, selectedValue, _) { - return ListView.builder( - itemCount: values.length, - itemBuilder: (context, index) { - final value = values[index]; - return ListTile( - leading: SvgPicture.asset( - value.icon, - width: 24, - height: 24, - ), - title: Text( - value.description, - style: context.textTheme.bodyMedium, - ), - trailing: Radio( - value: value.value, - groupValue: selectedValue, - onChanged: (newValue) { - valueNotifier.value = newValue; - }, + return Slider( + value: (initialValue ?? 0).toDouble(), + min: 0, + max: 300, + divisions: 300, + onChanged: (value) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: value, + condition: selectedFunctionData?.condition, + valueDescription: selectedFunctionData?.valueDescription, + ), ), ); + }, + ); + } + + static Widget _buildOperationalValuesList({ + required BuildContext context, + required List values, + required dynamic selectedValue, + AllDevicesModel? device, + required String operationName, + required String selectCode, + DeviceFunctionData? selectedFunctionData, + // required Function(dynamic) onValueChanged, + }) { + return ListView.builder( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + final isSelected = selectedValue == value.value; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + placeholderBuilder: (BuildContext context) => Container( + width: 24, + height: 24, + color: Colors.transparent, + ), + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Icon( + isSelected + ? Icons.radio_button_checked + : Icons.radio_button_unchecked, + size: 24, + color: isSelected + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.textGray, + ), + onTap: () { + if (!isSelected) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: value.value, + condition: selectedFunctionData?.condition, + valueDescription: + selectedFunctionData?.valueDescription, + ), + ), + ); + } }, ); }, ); } + + // static Widget _buildCountDownSelector( + // BuildContext context, + // ValueNotifier valueNotifier, + // ValueNotifier conditionNotifier, + // ValueNotifier> conditionsNotifier, + // ) { + // return ValueListenableBuilder( + // valueListenable: valueNotifier, + // builder: (context, value, _) { + // return Column( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // ValueListenableBuilder>( + // valueListenable: conditionsNotifier, + // builder: (context, selectedConditions, _) { + // return ToggleButtons( + // onPressed: (int index) { + // final newConditions = List.filled(3, false); + // newConditions[index] = true; + // conditionsNotifier.value = newConditions; + // conditionNotifier.value = index == 0 + // ? "<" + // : index == 1 + // ? "==" + // : ">"; + // }, + // borderRadius: const BorderRadius.all(Radius.circular(8)), + // selectedBorderColor: ColorsManager.primaryColorWithOpacity, + // selectedColor: Colors.white, + // fillColor: ColorsManager.primaryColorWithOpacity, + // color: ColorsManager.primaryColorWithOpacity, + // constraints: const BoxConstraints( + // minHeight: 40.0, + // minWidth: 40.0, + // ), + // isSelected: selectedConditions, + // children: const [Text("<"), Text("="), Text(">")], + // ); + // }, + // ), + // const SizedBox(height: 20), + // Text( + // '${value ?? 0} sec', + // style: Theme.of(context).textTheme.headlineMedium, + // ), + // const SizedBox(height: 20), + // Slider( + // value: (value ?? 0).toDouble(), + // min: 0, + // max: 300, + // divisions: 300, + // onChanged: (newValue) { + // valueNotifier.value = newValue.toInt(); + // }, + // ), + // ], + // ); + // }, + // ); + // } + + // static Widget _buildOperationalValuesList( + // BuildContext context, + // BaseSwitchFunction function, + // ValueNotifier valueNotifier, + // ) { + // final values = function.getOperationalValues(); + // return ValueListenableBuilder( + // valueListenable: valueNotifier, + // builder: (context, selectedValue, _) { + // return ListView.builder( + // itemCount: values.length, + // itemBuilder: (context, index) { + // final value = values[index]; + // return ListTile( + // leading: SvgPicture.asset( + // value.icon, + // width: 24, + // height: 24, + // ), + // title: Text( + // value.description, + // style: context.textTheme.bodyMedium, + // ), + // trailing: Radio( + // value: value.value, + // groupValue: selectedValue, + // onChanged: (newValue) { + // valueNotifier.value = newValue; + // }, + // ), + // ); + // }, + // ); + // }, + // ); + // } } diff --git a/lib/pages/routiens/helper/three_gang_switch_helper.dart b/lib/pages/routiens/helper/three_gang_switch_helper.dart index ecbe81f2..502b302e 100644 --- a/lib/pages/routiens/helper/three_gang_switch_helper.dart +++ b/lib/pages/routiens/helper/three_gang_switch_helper.dart @@ -143,7 +143,7 @@ class ThreeGangSwitchHelper { final value = selectedValueNotifier.value; final functionData = DeviceFunctionData( entityId: selectedFn.deviceId, - function: selectedFn.code, + functionCode: selectedFn.code, operationName: selectedFn.operationName, value: value, condition: selectedConditionNotifier.value, diff --git a/lib/pages/routiens/helper/two_gang_switch_helper.dart b/lib/pages/routiens/helper/two_gang_switch_helper.dart index f0e3026a..038ee345 100644 --- a/lib/pages/routiens/helper/two_gang_switch_helper.dart +++ b/lib/pages/routiens/helper/two_gang_switch_helper.dart @@ -138,7 +138,7 @@ class TwoGangSwitchHelper { final value = selectedValueNotifier.value; final functionData = DeviceFunctionData( entityId: selectedFn.deviceId, - function: selectedFn.code, + functionCode: selectedFn.code, operationName: selectedFn.operationName, value: value, condition: selectedConditionNotifier.value, diff --git a/lib/pages/routiens/models/ac/ac_function.dart b/lib/pages/routiens/models/ac/ac_function.dart index 3afe74c6..e5fa78ba 100644 --- a/lib/pages/routiens/models/ac/ac_function.dart +++ b/lib/pages/routiens/models/ac/ac_function.dart @@ -1,25 +1,17 @@ import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; import 'package:syncrow_web/pages/routiens/models/ac/ac_operational_value.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/utils/constants/app_enum.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; abstract class ACFunction extends DeviceFunction { ACFunction({ - required String deviceId, - required String deviceName, - required String code, - required String operationName, - required String icon, - }) : super( - deviceId: deviceId, - deviceName: deviceName, - code: code, - operationName: operationName, - icon: icon, - ); - - @override - AcStatusModel execute(AcStatusModel currentStatus, dynamic newValue); + required super.deviceId, + required super.deviceName, + required super.code, + required super.operationName, + required super.icon, + }); List getOperationalValues(); } @@ -32,11 +24,6 @@ class SwitchFunction extends ACFunction { icon: Assets.assetsAcPower, ); - @override - AcStatusModel execute(AcStatusModel currentStatus, dynamic newValue) { - return currentStatus.copyWith(acSwitch: newValue as bool); - } - @override List getOperationalValues() => [ ACOperationalValue( @@ -60,27 +47,22 @@ class ModeFunction extends ACFunction { icon: Assets.assetsFreezing, ); - @override - AcStatusModel execute(AcStatusModel currentStatus, dynamic newValue) { - return currentStatus.copyWith(modeString: (newValue as TempModes).toString().split('.').last); - } - @override List getOperationalValues() => [ ACOperationalValue( icon: Assets.assetsAcCooling, description: "Cooling", - value: TempModes.cold, + value: TempModes.cold.name, ), ACOperationalValue( icon: Assets.assetsAcHeating, description: "Heating", - value: TempModes.hot, + value: TempModes.hot.name, ), ACOperationalValue( icon: Assets.assetsFanSpeed, description: "Ventilation", - value: TempModes.wind, + value: TempModes.wind.name, ), ]; } @@ -100,11 +82,6 @@ class TempSetFunction extends ACFunction { icon: Assets.assetsTempreture, ); - @override - AcStatusModel execute(AcStatusModel currentStatus, dynamic newValue) { - return currentStatus.copyWith(tempSet: newValue as int); - } - @override List getOperationalValues() { List values = []; @@ -127,32 +104,27 @@ class LevelFunction extends ACFunction { icon: Assets.assetsFanSpeed, ); - @override - AcStatusModel execute(AcStatusModel currentStatus, dynamic newValue) { - return currentStatus.copyWith(fanSpeedsString: (newValue as FanSpeeds).toString().split('.').last); - } - @override List getOperationalValues() => [ ACOperationalValue( icon: Assets.assetsAcFanLow, description: "LOW", - value: FanSpeeds.low, + value: FanSpeeds.low.name, ), ACOperationalValue( icon: Assets.assetsAcFanMiddle, description: "MIDDLE", - value: FanSpeeds.middle, + value: FanSpeeds.middle.name, ), ACOperationalValue( icon: Assets.assetsAcFanHigh, description: "HIGH", - value: FanSpeeds.high, + value: FanSpeeds.high.name, ), ACOperationalValue( icon: Assets.assetsAcFanAuto, description: "AUTO", - value: FanSpeeds.auto, + value: FanSpeeds.auto.name, ), ]; } @@ -165,11 +137,6 @@ class ChildLockFunction extends ACFunction { icon: Assets.assetsChildLock, ); - @override - AcStatusModel execute(AcStatusModel currentStatus, dynamic newValue) { - return currentStatus.copyWith(childLock: newValue as bool); - } - @override List getOperationalValues() => [ ACOperationalValue( @@ -184,32 +151,3 @@ class ChildLockFunction extends ACFunction { ), ]; } - -/* - - final deviceId = 'AC001'; - final deviceName = 'Living Room AC'; - - // Initial AC status - var acStatus = AcStatusModel( - uuid: deviceId, - acSwitch: false, - modeString: 'cold', - tempSet: 220, - currentTemp: 250, - fanSpeedsString: 'auto', - childLock: false, - ); - - // Get all AC functions - final acFunctions = getACFunctions(deviceId, deviceName); - - // Example: Turn on the AC - final switchFunction = acFunctions.firstWhere((f) => f is SwitchFunction) as SwitchFunction; - acStatus = switchFunction.execute(acStatus, true); - - // Example: Change mode to heat - final modeFunction = acFunctions.firstWhere((f) => f is ModeFunction) as ModeFunction; - acStatus = modeFunction.execute(acStatus, TempModes.hot); - - */ diff --git a/lib/pages/routiens/models/device_functions.dart b/lib/pages/routiens/models/device_functions.dart index 4234c812..a1f5725e 100644 --- a/lib/pages/routiens/models/device_functions.dart +++ b/lib/pages/routiens/models/device_functions.dart @@ -1,3 +1,5 @@ +import 'package:flutter/material.dart'; + abstract class DeviceFunction { final String deviceId; final String deviceName; @@ -12,34 +14,33 @@ abstract class DeviceFunction { required this.operationName, required this.icon, }); - - T execute(T currentStatus, dynamic newValue); } class DeviceFunctionData { final String entityId; final String actionExecutor; - final String function; + final String functionCode; final String operationName; final dynamic value; final String? condition; final String? valueDescription; + final UniqueKey uniqueKey; DeviceFunctionData({ required this.entityId, - this.actionExecutor = 'function', - required this.function, + this.actionExecutor = 'device_issue', + required this.functionCode, required this.operationName, required this.value, this.condition, this.valueDescription, - }); + }) : uniqueKey = UniqueKey(); Map toJson() { return { 'entityId': entityId, 'actionExecutor': actionExecutor, - 'function': function, + 'function': functionCode, 'operationName': operationName, 'value': value, if (condition != null) 'condition': condition, @@ -51,7 +52,7 @@ class DeviceFunctionData { return DeviceFunctionData( entityId: json['entityId'], actionExecutor: json['actionExecutor'] ?? 'function', - function: json['function'], + functionCode: json['function'], operationName: json['operationName'], value: json['value'], condition: json['condition'], @@ -66,7 +67,7 @@ class DeviceFunctionData { return other is DeviceFunctionData && other.entityId == entityId && other.actionExecutor == actionExecutor && - other.function == function && + other.functionCode == functionCode && other.operationName == operationName && other.value == value && other.condition == condition && @@ -77,7 +78,7 @@ class DeviceFunctionData { int get hashCode { return entityId.hashCode ^ actionExecutor.hashCode ^ - function.hashCode ^ + functionCode.hashCode ^ operationName.hashCode ^ value.hashCode ^ condition.hashCode ^ diff --git a/lib/pages/routiens/models/gang_switches/base_switch_function.dart b/lib/pages/routiens/models/gang_switches/base_switch_function.dart index 3c89bebd..f180b203 100644 --- a/lib/pages/routiens/models/gang_switches/base_switch_function.dart +++ b/lib/pages/routiens/models/gang_switches/base_switch_function.dart @@ -10,8 +10,5 @@ abstract class BaseSwitchFunction extends DeviceFunction { required super.icon, }); - @override - bool execute(bool currentStatus, dynamic newValue); - List getOperationalValues(); } diff --git a/lib/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart b/lib/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart index 32bc436e..2e20e40e 100644 --- a/lib/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart +++ b/lib/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart @@ -10,11 +10,6 @@ class OneGangSwitchFunction extends BaseSwitchFunction { icon: Assets.assetsAcPower, ); - @override - bool execute(bool currentStatus, dynamic newValue) { - return newValue as bool; - } - @override List getOperationalValues() => [ SwitchOperationalValue( @@ -38,11 +33,6 @@ class OneGangCountdownFunction extends BaseSwitchFunction { icon: Assets.assetsLightCountdown, ); - @override - bool execute(bool currentStatus, dynamic newValue) { - return newValue as bool; - } - @override List getOperationalValues() => [ SwitchOperationalValue( diff --git a/lib/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart b/lib/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart index 8ff186ef..7f4710f0 100644 --- a/lib/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart +++ b/lib/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart @@ -10,11 +10,6 @@ class ThreeGangSwitch1Function extends BaseSwitchFunction { icon: Assets.assetsAcPower, ); - @override - bool execute(bool currentStatus, dynamic newValue) { - return newValue as bool; - } - @override List getOperationalValues() => [ SwitchOperationalValue( @@ -39,11 +34,6 @@ class ThreeGangCountdown1Function extends BaseSwitchFunction { icon: Assets.assetsLightCountdown, ); - @override - bool execute(bool currentStatus, dynamic newValue) { - return newValue as bool; - } - @override List getOperationalValues() => [ SwitchOperationalValue( @@ -65,11 +55,6 @@ class ThreeGangSwitch2Function extends BaseSwitchFunction { icon: Assets.assetsAcPower, ); - @override - bool execute(bool currentStatus, dynamic newValue) { - return newValue as bool; - } - @override List getOperationalValues() => [ SwitchOperationalValue( @@ -94,11 +79,6 @@ class ThreeGangCountdown2Function extends BaseSwitchFunction { icon: Assets.assetsLightCountdown, ); - @override - bool execute(bool currentStatus, dynamic newValue) { - return newValue as bool; - } - @override List getOperationalValues() => [ SwitchOperationalValue( @@ -120,11 +100,6 @@ class ThreeGangSwitch3Function extends BaseSwitchFunction { icon: Assets.assetsAcPower, ); - @override - bool execute(bool currentStatus, dynamic newValue) { - return newValue as bool; - } - @override List getOperationalValues() => [ SwitchOperationalValue( @@ -149,11 +124,6 @@ class ThreeGangCountdown3Function extends BaseSwitchFunction { icon: Assets.assetsLightCountdown, ); - @override - bool execute(bool currentStatus, dynamic newValue) { - return newValue as bool; - } - @override List getOperationalValues() => [ SwitchOperationalValue( diff --git a/lib/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart b/lib/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart index 7408d9d0..91bda15c 100644 --- a/lib/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart +++ b/lib/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart @@ -10,11 +10,6 @@ class TwoGangSwitch1Function extends BaseSwitchFunction { icon: Assets.assetsAcPower, ); - @override - bool execute(bool currentStatus, dynamic newValue) { - return newValue as bool; - } - @override List getOperationalValues() => [ SwitchOperationalValue( @@ -31,20 +26,13 @@ class TwoGangSwitch1Function extends BaseSwitchFunction { } class TwoGangSwitch2Function extends BaseSwitchFunction { - TwoGangSwitch2Function({required String deviceId, required String deviceName}) + TwoGangSwitch2Function({required super.deviceId, required super.deviceName}) : super( - deviceId: deviceId, - deviceName: deviceName, code: 'switch_2', operationName: 'Light 2 Switch', icon: Assets.assetsAcPower, ); - @override - bool execute(bool currentStatus, dynamic newValue) { - return newValue as bool; - } - @override List getOperationalValues() => [ SwitchOperationalValue( @@ -69,11 +57,6 @@ class TwoGangCountdown1Function extends BaseSwitchFunction { icon: Assets.assetsLightCountdown, ); - @override - bool execute(bool currentStatus, dynamic newValue) { - return newValue as bool; - } - @override List getOperationalValues() => [ SwitchOperationalValue( @@ -96,11 +79,6 @@ class TwoGangCountdown2Function extends BaseSwitchFunction { icon: Assets.assetsLightCountdown, ); - @override - bool execute(bool currentStatus, dynamic newValue) { - return newValue as bool; - } - @override List getOperationalValues() => [ SwitchOperationalValue( diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index 016b82c1..197ea0d2 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -43,95 +43,70 @@ class DraggableCard extends StatelessWidget { BuildContext context, List deviceFunctions) { return Card( color: ColorsManager.whiteColors, - child: SizedBox( + child: Container( + padding: const EdgeInsets.all(8), width: 90, + height: deviceFunctions.isEmpty ? 123 : null, child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - SizedBox( - height: 123, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - height: 50, - width: 50, - decoration: BoxDecoration( - color: ColorsManager.CircleImageBackground, - borderRadius: BorderRadius.circular(90), - border: Border.all( - color: ColorsManager.graysColor, - ), - ), - padding: const EdgeInsets.all(8), - child: imagePath.contains('.svg') - ? SvgPicture.asset( - imagePath, - ) - : Image.network(imagePath), - ), - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 3), - child: Text( - title, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 2, - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.blackColor, - fontSize: 12, - ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + height: 50, + width: 50, + decoration: BoxDecoration( + color: ColorsManager.CircleImageBackground, + borderRadius: BorderRadius.circular(90), + border: Border.all( + color: ColorsManager.graysColor, ), ), - ], - ), + padding: const EdgeInsets.all(8), + child: imagePath.contains('.svg') + ? SvgPicture.asset( + imagePath, + ) + : Image.network(imagePath), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 3), + child: Text( + title, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontSize: 12, + ), + ), + ), + ], ), - if (deviceFunctions.isNotEmpty) ...[ - const Divider(height: 1), - ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 4), - itemCount: deviceFunctions.length, - itemBuilder: (context, index) { - final function = deviceFunctions[index]; - return Row( + if (deviceFunctions.isNotEmpty) + // const Divider(height: 1), + ...deviceFunctions.map((function) => Row( mainAxisSize: MainAxisSize.min, children: [ Expanded( child: Text( - '${function.operationName}: ${function.valueDescription}', + '${function.operationName}: ${function.value}', style: context.textTheme.bodySmall?.copyWith( fontSize: 9, color: ColorsManager.textGray, height: 1.2, ), + maxLines: 2, overflow: TextOverflow.ellipsis, ), ), - InkWell( - onTap: () { - context.read().add( - RemoveFunction(function), - ); - }, - child: const Padding( - padding: EdgeInsets.all(2), - child: Icon( - Icons.close, - size: 12, - color: ColorsManager.textGray, - ), - ), - ), ], - ); - }, - ), - ], + )), ], ), ), diff --git a/lib/pages/routiens/widgets/routine_devices.dart b/lib/pages/routiens/widgets/routine_devices.dart index 0dc6e35e..20d47007 100644 --- a/lib/pages/routiens/widgets/routine_devices.dart +++ b/lib/pages/routiens/widgets/routine_devices.dart @@ -9,9 +9,6 @@ class RoutineDevices extends StatelessWidget { @override Widget build(BuildContext context) { - // Get the RoutineBloc instance from the parent - final routineBloc = context.read(); - return BlocProvider( create: (context) => DeviceManagementBloc()..add(FetchDevices()), child: BlocBuilder( @@ -26,32 +23,28 @@ class RoutineDevices extends StatelessWidget { .toList(); // Provide the RoutineBloc to the child widgets - return BlocProvider.value( - value: routineBloc, - child: BlocBuilder( - builder: (context, routineState) { - return Wrap( - spacing: 10, - runSpacing: 10, - children: deviceList.asMap().entries.map((entry) { - final device = entry.value; - return DraggableCard( - imagePath: device.getDefaultIcon(device.productType), - title: device.name ?? '', - deviceData: { - 'key': UniqueKey().toString(), - 'imagePath': - device.getDefaultIcon(device.productType), - 'title': device.name ?? '', - 'deviceId': device.uuid, - 'productType': device.productType, - 'functions': device.functions, - }, - ); - }).toList(), - ); - }, - ), + return BlocBuilder( + builder: (context, routineState) { + return Wrap( + spacing: 10, + runSpacing: 10, + children: deviceList.asMap().entries.map((entry) { + final device = entry.value; + return DraggableCard( + imagePath: device.getDefaultIcon(device.productType), + title: device.name ?? '', + deviceData: { + 'device': device, + 'imagePath': device.getDefaultIcon(device.productType), + 'title': device.name ?? '', + 'deviceId': device.uuid, + 'productType': device.productType, + 'functions': device.functions, + }, + ); + }).toList(), + ); + }, ); } return const Center(child: CircularProgressIndicator()); diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index 633a1ad1..9c868f09 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -30,7 +30,7 @@ class ThenContainer extends StatelessWidget { runSpacing: 8, children: state.thenItems .map((item) => DraggableCard( - key: Key(item['key']!), + // key: Key(item['key']!), imagePath: item['imagePath']!, title: item['title']!, deviceData: item, @@ -45,6 +45,18 @@ class ThenContainer extends StatelessWidget { onAccept: (data) async { final result = await DeviceDialogHelper.showDeviceDialog(context, data); + // if (result != null) { + // for (var function in routineBloc.state.selectedFunctions) { + // routineBloc.add(AddToThenContainer( + // { + // 'item': function, + // 'imagePath': data['imagePath'], + // 'title': data['name'], + + // } + // )); + // } + // } if (result != null) { context.read().add(AddToThenContainer(data)); } else if (!['AC', '1G', '2G', '3G'] diff --git a/lib/utils/constants/app_enum.dart b/lib/utils/constants/app_enum.dart index 4ca37d9b..e9a21193 100644 --- a/lib/utils/constants/app_enum.dart +++ b/lib/utils/constants/app_enum.dart @@ -97,3 +97,9 @@ extension AccessStatusExtension on AccessStatus { enum TempModes { hot, cold, wind } enum FanSpeeds { auto, low, middle, high } + +enum AcValuesEnums { + Cooling, + Heating, + Ventilation, +} From 5e94de5d78ef2035aa86f0f8a5101a0c69d573ee Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 24 Nov 2024 01:10:31 +0300 Subject: [PATCH 15/40] fixes unique id for card drag --- .../all_devices/models/devices_model.dart | 2 + .../bloc/routine_bloc/routine_bloc.dart | 45 +- .../bloc/routine_bloc/routine_event.dart | 7 +- .../bloc/routine_bloc/routine_state.dart | 6 +- lib/pages/routiens/helper/ac_helper.dart | 51 +- .../dialog_helper/device_dialog_helper.dart | 24 +- .../helper/one_gang_switch_helper.dart | 185 ++---- .../helper/three_gang_switch_helper.dart | 571 +++++++++++------- .../helper/two_gang_switch_helper.dart | 564 ++++++++++------- .../routiens/models/device_functions.dart | 5 +- lib/pages/routiens/models/routine_item.dart | 51 +- lib/pages/routiens/models/routine_model.dart | 14 +- lib/pages/routiens/widgets/dragable_card.dart | 7 +- .../routiens/widgets/routine_devices.dart | 1 + .../routiens/widgets/then_container.dart | 51 +- pubspec.lock | 32 + pubspec.yaml | 1 + 17 files changed, 905 insertions(+), 712 deletions(-) diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index 367a4fe8..b4f72287 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -105,6 +105,7 @@ class AllDevicesModel { this.productName, this.spaces, }); + AllDevicesModel.fromJson(Map json) { room = (json['room'] != null && (json['room'] is Map)) ? DevicesModelRoom.fromJson(json['room']) @@ -138,6 +139,7 @@ class AllDevicesModel { updateTime = int.tryParse(json['updateTime']?.toString() ?? ''); uuid = json['uuid']?.toString(); batteryLevel = int.tryParse(json['battery']?.toString() ?? ''); + productName = json['productName']?.toString(); if (json['spaces'] != null && json['spaces'] is List) { spaces = (json['spaces'] as List) diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 2b711f39..bbd711a2 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -16,9 +16,9 @@ class RoutineBloc extends Bloc { on(_onAddToThenContainer); on(_onLoadScenes); on(_onLoadAutomation); - on(_onAddFunction); - on(_onRemoveFunction); - on(_onClearFunctions); + on(_onAddFunctionsToRoutine); + // on(_onRemoveFunction); + // on(_onClearFunctions); } void _onAddToIfContainer(AddToIfContainer event, Emitter emit) { @@ -38,24 +38,33 @@ class RoutineBloc extends Bloc { // } } - void _onAddFunction(AddFunctionToRoutine event, Emitter emit) { - final functions = List.from(state.selectedFunctions); - functions.add(event.function); - debugPrint("******" + functions.toString()); - emit(state.copyWith(selectedFunctions: functions)); + void _onAddFunctionsToRoutine( + AddFunctionToRoutine event, Emitter emit) { + debugPrint(event.uniqueCustomId.toString()); + debugPrint(event.functions.toString()); + final currentSelectedFunctions = + Map>.from(state.selectedFunctions); + + if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) { + currentSelectedFunctions[event.uniqueCustomId]!.addAll(event.functions); + } else { + currentSelectedFunctions[event.uniqueCustomId] = event.functions; + } + + emit(state.copyWith(selectedFunctions: currentSelectedFunctions)); } - void _onRemoveFunction(RemoveFunction event, Emitter emit) { - final functions = List.from(state.selectedFunctions) - ..removeWhere((f) => - f.functionCode == event.function.functionCode && - f.value == event.function.value); - emit(state.copyWith(selectedFunctions: functions)); - } + // void _onRemoveFunction(RemoveFunction event, Emitter emit) { + // final functions = List.from(state.selectedFunctions) + // ..removeWhere((f) => + // f.functionCode == event.function.functionCode && + // f.value == event.function.value); + // emit(state.copyWith(selectedFunctions: functions)); + // } - void _onClearFunctions(ClearFunctions event, Emitter emit) { - emit(state.copyWith(selectedFunctions: [])); - } + // void _onClearFunctions(ClearFunctions event, Emitter emit) { + // emit(state.copyWith(selectedFunctions: [])); + // } // bool _isDuplicate( // List> items, Map newItem) { diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart index 3321af5e..e3071b15 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart @@ -44,10 +44,11 @@ class LoadAutomation extends RoutineEvent { } class AddFunctionToRoutine extends RoutineEvent { - final DeviceFunctionData function; - const AddFunctionToRoutine(this.function); + final List functions; + final String uniqueCustomId; + const AddFunctionToRoutine(this.functions, this.uniqueCustomId); @override - List get props => [function]; + List get props => [functions]; } class RemoveFunction extends RoutineEvent { diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart index 95aab557..ce6fea15 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart @@ -6,7 +6,7 @@ class RoutineState extends Equatable { final List> availableCards; final List scenes; final List automations; - final List selectedFunctions; + final Map> selectedFunctions; final bool isLoading; final String? errorMessage; @@ -16,7 +16,7 @@ class RoutineState extends Equatable { this.availableCards = const [], this.scenes = const [], this.automations = const [], - this.selectedFunctions = const [], + this.selectedFunctions = const {}, this.isLoading = false, this.errorMessage, }); @@ -26,7 +26,7 @@ class RoutineState extends Equatable { List>? thenItems, List? scenes, List? automations, - List? selectedFunctions, + Map>? selectedFunctions, bool? isLoading, String? errorMessage, }) { diff --git a/lib/pages/routiens/helper/ac_helper.dart b/lib/pages/routiens/helper/ac_helper.dart index 9ba02633..8b555b7a 100644 --- a/lib/pages/routiens/helper/ac_helper.dart +++ b/lib/pages/routiens/helper/ac_helper.dart @@ -18,6 +18,7 @@ class ACHelper { List functions, AllDevicesModel? device, List? deviceSelectedFunctions, + String uniqueCustomId, ) async { List acFunctions = functions.whereType().toList(); @@ -95,13 +96,14 @@ class ACHelper { onConfirm: state.addedFunctions.isNotEmpty ? () { /// add the functions to the routine bloc - for (var function in state.addedFunctions) { - context.read().add( - AddFunctionToRoutine( - function, - ), - ); - } + // for (var function in state.addedFunctions) { + context.read().add( + AddFunctionToRoutine( + state.addedFunctions, + uniqueCustomId, + ), + ); + //} // Return the device data to be added to the container Navigator.pop(context, { 'deviceId': functions.first.deviceId, @@ -229,11 +231,23 @@ class ACHelper { selectedFunctionData, ), const SizedBox(height: 20), - _buildTemperatureDisplay(context, initialValue, device, operationName, - selectedFunctionData, selectCode), + _buildTemperatureDisplay( + context, + initialValue, + device, + operationName, + selectedFunctionData, + selectCode, + ), const SizedBox(height: 20), - _buildTemperatureSlider(context, initialValue, device, operationName, - selectedFunctionData, selectCode), + _buildTemperatureSlider( + context, + initialValue, + device, + operationName, + selectedFunctionData, + selectCode, + ), ], ); } @@ -246,6 +260,7 @@ class ACHelper { AllDevicesModel? device, String operationName, DeviceFunctionData? selectedFunctionData, + // Function(String) onConditionChanged, ) { final conditions = ["<", "==", ">"]; @@ -282,12 +297,13 @@ class ACHelper { /// Build temperature display for AC functions dialog static Widget _buildTemperatureDisplay( - BuildContext context, - dynamic initialValue, - AllDevicesModel? device, - String operationName, - DeviceFunctionData? selectedFunctionData, - String selectCode) { + BuildContext context, + dynamic initialValue, + AllDevicesModel? device, + String operationName, + DeviceFunctionData? selectedFunctionData, + String selectCode, + ) { return Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), decoration: BoxDecoration( @@ -342,6 +358,7 @@ class ACHelper { required String operationName, required String selectCode, DeviceFunctionData? selectedFunctionData, + // required Function(dynamic) onValueChanged, }) { return ListView.builder( diff --git a/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart index 714feda3..fcb8e740 100644 --- a/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart @@ -39,24 +39,28 @@ class DeviceDialogHelper { List functions, ) async { final routineBloc = context.read(); - final deviceSelectedFunctions = routineBloc.state.selectedFunctions - .where((f) => f.entityId == data['deviceId']) - .toList(); + final deviceSelectedFunctions = + routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? []; switch (productType) { case 'AC': - return ACHelper.showACFunctionsDialog( - context, functions, data['device'], deviceSelectedFunctions); + return ACHelper.showACFunctionsDialog(context, functions, + data['device'], deviceSelectedFunctions, data['uniqueCustomId']); case '1G': - return OneGangSwitchHelper.showSwitchFunctionsDialog( - context, functions, data['device'], deviceSelectedFunctions); + return OneGangSwitchHelper.showSwitchFunctionsDialog(context, functions, + data['device'], deviceSelectedFunctions, data['uniqueCustomId']); case '2G': - return TwoGangSwitchHelper.showSwitchFunctionsDialog( - context, functions); + return TwoGangSwitchHelper.showSwitchFunctionsDialog(context, functions, + data['device'], deviceSelectedFunctions, data['uniqueCustomId']); case '3G': return ThreeGangSwitchHelper.showSwitchFunctionsDialog( - context, functions); + context, + functions, + data['device'], + deviceSelectedFunctions, + data['uniqueCustomId'], + ); default: return null; } diff --git a/lib/pages/routiens/helper/one_gang_switch_helper.dart b/lib/pages/routiens/helper/one_gang_switch_helper.dart index 6cb28424..c7d5d35a 100644 --- a/lib/pages/routiens/helper/one_gang_switch_helper.dart +++ b/lib/pages/routiens/helper/one_gang_switch_helper.dart @@ -4,11 +4,9 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart'; -import 'package:syncrow_web/pages/routiens/models/ac/ac_operational_value.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; -import 'package:syncrow_web/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -20,8 +18,10 @@ class OneGangSwitchHelper { List functions, AllDevicesModel? device, List? deviceSelectedFunctions, + String uniqueCustomId, ) async { - List acFunctions = functions.whereType().toList(); + List acFunctions = + functions.whereType().toList(); return showDialog?>( context: context, @@ -107,30 +107,6 @@ class OneGangSwitchHelper { operationName: selectedOperationName ?? '', ), ), - // ValueListenableBuilder( - // valueListenable: selectedFunctionNotifier, - // builder: (context, selectedFunction, _) { - // final selectedFn = - // switchFunctions.firstWhere( - // (f) => f.code == selectedFunction, - // ); - // return Expanded( - // child: selectedFn - // is OneGangCountdownFunction - // ? _buildCountDownSelector( - // context, - // selectedValueNotifier, - // selectedConditionNotifier, - // selectedConditionsNotifier, - // ) - // : _buildOperationalValuesList( - // context, - // selectedFn as BaseSwitchFunction, - // selectedValueNotifier, - // ), - // ); - // }, - // ), ], ), ), @@ -146,13 +122,20 @@ class OneGangSwitchHelper { onConfirm: state.addedFunctions.isNotEmpty ? () { /// add the functions to the routine bloc - for (var function in state.addedFunctions) { - context.read().add( - AddFunctionToRoutine( - function, - ), - ); - } + // for (var function in state.addedFunctions) { + // context.read().add( + // AddFunctionToRoutine( + // function, + // uniqueCustomId, + // ), + // ); + // } + context.read().add( + AddFunctionToRoutine( + state.addedFunctions, + uniqueCustomId, + ), + ); // Return the device data to be added to the container Navigator.pop(context, { 'deviceId': functions.first.deviceId, @@ -175,13 +158,13 @@ class OneGangSwitchHelper { required BuildContext context, required String selectedFunction, required DeviceFunctionData? selectedFunctionData, - required List acFunctions, + required List acFunctions, AllDevicesModel? device, required String operationName, }) { if (selectedFunction == 'countdown_1') { final initialValue = selectedFunctionData?.value ?? 200; - return _buildTemperatureSelector( + return _buildCountDownSelector( context: context, initialValue: initialValue, selectCode: selectedFunction, @@ -207,7 +190,7 @@ class OneGangSwitchHelper { ); } - static Widget _buildTemperatureSelector({ + static Widget _buildCountDownSelector({ required BuildContext context, required dynamic initialValue, required String? currentCondition, @@ -228,10 +211,10 @@ class OneGangSwitchHelper { selectedFunctionData, ), const SizedBox(height: 20), - _buildTemperatureDisplay(context, initialValue, device, operationName, + _buildCountDownDisplay(context, initialValue, device, operationName, selectedFunctionData, selectCode), const SizedBox(height: 20), - _buildTemperatureSlider(context, initialValue, device, operationName, + _buildCountDownSlider(context, initialValue, device, operationName, selectedFunctionData, selectCode), ], ); @@ -280,7 +263,7 @@ class OneGangSwitchHelper { } /// Build temperature display for AC functions dialog - static Widget _buildTemperatureDisplay( + static Widget _buildCountDownDisplay( BuildContext context, dynamic initialValue, AllDevicesModel? device, @@ -302,7 +285,7 @@ class OneGangSwitchHelper { ); } - static Widget _buildTemperatureSlider( + static Widget _buildCountDownSlider( BuildContext context, dynamic initialValue, AllDevicesModel? device, @@ -310,11 +293,22 @@ class OneGangSwitchHelper { DeviceFunctionData? selectedFunctionData, String selectCode, ) { + final operationalValues = SwitchOperationalValue( + icon: '', + description: "sec", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ); return Slider( value: (initialValue ?? 0).toDouble(), - min: 0, - max: 300, - divisions: 300, + min: operationalValues.minValue?.toDouble() ?? 0.0, + max: operationalValues.maxValue?.toDouble() ?? 0.0, + divisions: (((operationalValues.maxValue ?? 0) - + (operationalValues.minValue ?? 0)) / + (operationalValues.stepValue ?? 1)) + .round(), onChanged: (value) { context.read().add( AddFunction( @@ -334,13 +328,12 @@ class OneGangSwitchHelper { static Widget _buildOperationalValuesList({ required BuildContext context, - required List values, + required List values, required dynamic selectedValue, AllDevicesModel? device, required String operationName, required String selectCode, DeviceFunctionData? selectedFunctionData, - // required Function(dynamic) onValueChanged, }) { return ListView.builder( shrinkWrap: false, @@ -394,102 +387,4 @@ class OneGangSwitchHelper { }, ); } - - // static Widget _buildCountDownSelector( - // BuildContext context, - // ValueNotifier valueNotifier, - // ValueNotifier conditionNotifier, - // ValueNotifier> conditionsNotifier, - // ) { - // return ValueListenableBuilder( - // valueListenable: valueNotifier, - // builder: (context, value, _) { - // return Column( - // mainAxisAlignment: MainAxisAlignment.center, - // children: [ - // ValueListenableBuilder>( - // valueListenable: conditionsNotifier, - // builder: (context, selectedConditions, _) { - // return ToggleButtons( - // onPressed: (int index) { - // final newConditions = List.filled(3, false); - // newConditions[index] = true; - // conditionsNotifier.value = newConditions; - // conditionNotifier.value = index == 0 - // ? "<" - // : index == 1 - // ? "==" - // : ">"; - // }, - // borderRadius: const BorderRadius.all(Radius.circular(8)), - // selectedBorderColor: ColorsManager.primaryColorWithOpacity, - // selectedColor: Colors.white, - // fillColor: ColorsManager.primaryColorWithOpacity, - // color: ColorsManager.primaryColorWithOpacity, - // constraints: const BoxConstraints( - // minHeight: 40.0, - // minWidth: 40.0, - // ), - // isSelected: selectedConditions, - // children: const [Text("<"), Text("="), Text(">")], - // ); - // }, - // ), - // const SizedBox(height: 20), - // Text( - // '${value ?? 0} sec', - // style: Theme.of(context).textTheme.headlineMedium, - // ), - // const SizedBox(height: 20), - // Slider( - // value: (value ?? 0).toDouble(), - // min: 0, - // max: 300, - // divisions: 300, - // onChanged: (newValue) { - // valueNotifier.value = newValue.toInt(); - // }, - // ), - // ], - // ); - // }, - // ); - // } - - // static Widget _buildOperationalValuesList( - // BuildContext context, - // BaseSwitchFunction function, - // ValueNotifier valueNotifier, - // ) { - // final values = function.getOperationalValues(); - // return ValueListenableBuilder( - // valueListenable: valueNotifier, - // builder: (context, selectedValue, _) { - // return ListView.builder( - // itemCount: values.length, - // itemBuilder: (context, index) { - // final value = values[index]; - // return ListTile( - // leading: SvgPicture.asset( - // value.icon, - // width: 24, - // height: 24, - // ), - // title: Text( - // value.description, - // style: context.textTheme.bodyMedium, - // ), - // trailing: Radio( - // value: value.value, - // groupValue: selectedValue, - // onChanged: (newValue) { - // valueNotifier.value = newValue; - // }, - // ), - // ); - // }, - // ); - // }, - // ); - // } } diff --git a/lib/pages/routiens/helper/three_gang_switch_helper.dart b/lib/pages/routiens/helper/three_gang_switch_helper.dart index 502b302e..4a4c5e02 100644 --- a/lib/pages/routiens/helper/three_gang_switch_helper.dart +++ b/lib/pages/routiens/helper/three_gang_switch_helper.dart @@ -1,8 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; -import 'package:syncrow_web/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -10,64 +14,60 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; class ThreeGangSwitchHelper { static Future?> showSwitchFunctionsDialog( - BuildContext context, List> functions) async { - List> switchFunctions = functions - .where((f) => - f is ThreeGangSwitch1Function || - f is ThreeGangSwitch2Function || - f is ThreeGangSwitch3Function || - f is ThreeGangCountdown1Function || - f is ThreeGangCountdown2Function || - f is ThreeGangCountdown3Function) - .toList(); + BuildContext context, + List functions, + AllDevicesModel? device, + List? deviceSelectedFunctions, + String uniqueCustomId, + ) async { + List switchFunctions = + functions.whereType().toList(); - final selectedFunctionNotifier = ValueNotifier(null); - final selectedValueNotifier = ValueNotifier(null); - final selectedConditionNotifier = ValueNotifier('<'); - final selectedConditionsNotifier = - ValueNotifier>([true, false, false]); - - await showDialog( + return showDialog?>( context: context, builder: (BuildContext context) { - return ValueListenableBuilder( - valueListenable: selectedFunctionNotifier, - builder: (context, selectedFunction, _) { - return AlertDialog( + return BlocProvider( + create: (_) => FunctionBloc() + ..add(InitializeFunctions(deviceSelectedFunctions ?? [])), + child: AlertDialog( contentPadding: EdgeInsets.zero, - content: Container( - width: selectedFunction != null ? 600 : 300, - height: 450, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - ), - padding: const EdgeInsets.only(top: 20), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const DialogHeader('3 Gangs Light Switch Condition'), - Expanded( - child: Row( - children: [ - // Left side: Function list - Expanded( - child: ListView.separated( - itemCount: switchFunctions.length, - separatorBuilder: (_, __) => const Divider( - color: ColorsManager.dividerColor, - ), - itemBuilder: (context, index) { - final function = switchFunctions[index]; - return ValueListenableBuilder( - valueListenable: selectedFunctionNotifier, - builder: (context, selectedFunction, _) { - final isSelected = - selectedFunction == function.code; + content: BlocBuilder( + builder: (context, state) { + final selectedFunction = state.selectedFunction; + final selectedOperationName = state.selectedOperationName; + final selectedFunctionData = state.addedFunctions + .firstWhere((f) => f.functionCode == selectedFunction, + orElse: () => DeviceFunctionData( + entityId: '', + functionCode: selectedFunction ?? '', + operationName: '', + value: null, + )); + return Container( + width: selectedFunction != null ? 600 : 360, + height: 450, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const DialogHeader('3 Gangs Light Switch Condition'), + Expanded( + child: Row( + children: [ + // Left side: Function list + Expanded( + child: ListView.separated( + itemCount: switchFunctions.length, + separatorBuilder: (_, __) => const Divider( + color: ColorsManager.dividerColor, + ), + itemBuilder: (context, index) { + final function = switchFunctions[index]; return ListTile( - tileColor: isSelected - ? Colors.grey.shade100 - : null, leading: SvgPicture.asset( function.icon, width: 24, @@ -83,196 +83,307 @@ class ThreeGangSwitchHelper { color: ColorsManager.textGray, ), onTap: () { - selectedFunctionNotifier.value = - function.code; - selectedValueNotifier.value = function - is ThreeGangCountdown1Function || - function - is ThreeGangCountdown2Function || - function - is ThreeGangCountdown3Function - ? 0 - : null; + context + .read() + .add(SelectFunction( + functionCode: function.code, + operationName: + function.operationName, + )); }, ); }, - ); - }, - ), - ), - // Right side: Value selector - if (selectedFunction != null) - Expanded( - child: ValueListenableBuilder( - valueListenable: selectedValueNotifier, - builder: (context, selectedValue, _) { - final selectedFn = switchFunctions.firstWhere( - (f) => f.code == selectedFunction, - ); - return selectedFn - is ThreeGangCountdown1Function || - selectedFn - is ThreeGangCountdown2Function || - selectedFn - is ThreeGangCountdown3Function - ? _buildCountDownSelector( - context, - selectedValueNotifier, - selectedConditionNotifier, - selectedConditionsNotifier, - ) - : _buildOperationalValuesList( - context, - selectedFn as BaseSwitchFunction, - selectedValueNotifier, - ); - }, + ), ), - ), - ], - ), + // Right side: Value selector + if (selectedFunction != null) + Expanded( + child: _buildValueSelector( + context: context, + selectedFunction: selectedFunction, + selectedFunctionData: selectedFunctionData, + switchFunctions: switchFunctions, + device: device, + operationName: selectedOperationName ?? '', + ), + ), + ], + ), + ), + Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + DialogFooter( + onCancel: () { + Navigator.pop(context); + }, + onConfirm: state.addedFunctions.isNotEmpty + ? () { + /// add the functions to the routine bloc + // for (var function in state.addedFunctions) { + // context.read().add( + // AddFunctionToRoutine( + // function, + // uniqueCustomId, + // ), + // ); + // } + context.read().add( + AddFunctionToRoutine( + state.addedFunctions, + uniqueCustomId, + ), + ); + // Return the device data to be added to the container + Navigator.pop(context, { + 'deviceId': functions.first.deviceId, + }); + } + : null, + isConfirmEnabled: selectedFunction != null, + ), + ], ), - DialogFooter( - onCancel: () => Navigator.pop(context), - onConfirm: selectedFunctionNotifier.value != null && - selectedValueNotifier.value != null - ? () { - final selectedFn = switchFunctions.firstWhere( - (f) => f.code == selectedFunctionNotifier.value, - ); - final value = selectedValueNotifier.value; - final functionData = DeviceFunctionData( - entityId: selectedFn.deviceId, - functionCode: selectedFn.code, - operationName: selectedFn.operationName, - value: value, - condition: selectedConditionNotifier.value, - valueDescription: selectedFn - is ThreeGangCountdown1Function || - selectedFn - is ThreeGangCountdown2Function || - selectedFn - is ThreeGangCountdown3Function - ? '${value} sec' - : ((selectedFn as BaseSwitchFunction) - .getOperationalValues() - .firstWhere((v) => v.value == value) - .description), - ); - Navigator.pop( - context, {selectedFn.code: functionData}); - } - : null, - isConfirmEnabled: selectedFunctionNotifier.value != null, - ), - ], - ), + ); + }, ), - ); - }, - ); - }, - ).then((value) { - selectedFunctionNotifier.dispose(); - selectedValueNotifier.dispose(); - selectedConditionNotifier.dispose(); - selectedConditionsNotifier.dispose(); - return value; - }); - } - - static Widget _buildCountDownSelector( - BuildContext context, - ValueNotifier valueNotifier, - ValueNotifier conditionNotifier, - ValueNotifier> conditionsNotifier, - ) { - return ValueListenableBuilder( - valueListenable: valueNotifier, - builder: (context, value, _) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ValueListenableBuilder>( - valueListenable: conditionsNotifier, - builder: (context, selectedConditions, _) { - return ToggleButtons( - onPressed: (int index) { - final newConditions = List.filled(3, false); - newConditions[index] = true; - conditionsNotifier.value = newConditions; - conditionNotifier.value = index == 0 - ? "<" - : index == 1 - ? "==" - : ">"; - }, - borderRadius: const BorderRadius.all(Radius.circular(8)), - selectedBorderColor: ColorsManager.primaryColorWithOpacity, - selectedColor: Colors.white, - fillColor: ColorsManager.primaryColorWithOpacity, - color: ColorsManager.primaryColorWithOpacity, - constraints: const BoxConstraints( - minHeight: 40.0, - minWidth: 40.0, - ), - isSelected: selectedConditions, - children: const [Text("<"), Text("="), Text(">")], - ); - }, - ), - const SizedBox(height: 20), - Text( - '${value ?? 0} sec', - style: Theme.of(context).textTheme.headlineMedium, - ), - const SizedBox(height: 20), - Slider( - value: (value ?? 0).toDouble(), - min: 0, - max: 300, - divisions: 300, - onChanged: (newValue) { - valueNotifier.value = newValue.toInt(); - }, - ), - ], - ); + )); }, ); } - static Widget _buildOperationalValuesList( + static Widget _buildValueSelector({ + required BuildContext context, + required String selectedFunction, + required DeviceFunctionData? selectedFunctionData, + required List switchFunctions, + AllDevicesModel? device, + required String operationName, + }) { + if (selectedFunction == 'countdown_1' || + selectedFunction == 'countdown_2' || + selectedFunction == 'countdown_3') { + final initialValue = selectedFunctionData?.value ?? 200; + return _buildTemperatureSelector( + context: context, + initialValue: initialValue, + selectCode: selectedFunction, + currentCondition: selectedFunctionData?.condition, + device: device, + operationName: operationName, + selectedFunctionData: selectedFunctionData, + ); + } + + final selectedFn = + switchFunctions.firstWhere((f) => f.code == selectedFunction); + final values = selectedFn.getOperationalValues(); + + return _buildOperationalValuesList( + context: context, + values: values, + selectedValue: selectedFunctionData?.value, + device: device, + operationName: operationName, + selectCode: selectedFunction, + selectedFunctionData: selectedFunctionData, + ); + } + + static Widget _buildTemperatureSelector({ + required BuildContext context, + required dynamic initialValue, + required String? currentCondition, + required String selectCode, + AllDevicesModel? device, + required String operationName, + DeviceFunctionData? selectedFunctionData, + }) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildConditionToggle( + context, + currentCondition, + selectCode, + device, + operationName, + selectedFunctionData, + ), + const SizedBox(height: 20), + _buildCountDownDisplay(context, initialValue, device, operationName, + selectedFunctionData, selectCode), + const SizedBox(height: 20), + _buildCountDownSlider(context, initialValue, device, operationName, + selectedFunctionData, selectCode), + ], + ); + } + + /// Build condition toggle for AC functions dialog + static Widget _buildConditionToggle( BuildContext context, - BaseSwitchFunction function, - ValueNotifier valueNotifier, + String? currentCondition, + String selectCode, + AllDevicesModel? device, + String operationName, + DeviceFunctionData? selectedFunctionData, + // Function(String) onConditionChanged, ) { - final values = function.getOperationalValues(); - return ValueListenableBuilder( - valueListenable: valueNotifier, - builder: (context, selectedValue, _) { - return ListView.builder( - itemCount: values.length, - itemBuilder: (context, index) { - final value = values[index]; - return ListTile( - leading: SvgPicture.asset( - value.icon, - width: 24, - height: 24, - ), - title: Text( - value.description, - style: context.textTheme.bodyMedium, - ), - trailing: Radio( - value: value.value, - groupValue: selectedValue, - onChanged: (newValue) { - valueNotifier.value = newValue; - }, + final conditions = ["<", "==", ">"]; + + return ToggleButtons( + onPressed: (int index) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + condition: conditions[index], + value: selectedFunctionData?.value, + valueDescription: selectedFunctionData?.valueDescription, + ), ), ); + }, + borderRadius: const BorderRadius.all(Radius.circular(8)), + selectedBorderColor: ColorsManager.primaryColorWithOpacity, + selectedColor: Colors.white, + fillColor: ColorsManager.primaryColorWithOpacity, + color: ColorsManager.primaryColorWithOpacity, + constraints: const BoxConstraints( + minHeight: 40.0, + minWidth: 40.0, + ), + isSelected: + conditions.map((c) => c == (currentCondition ?? "==")).toList(), + children: conditions.map((c) => Text(c)).toList(), + ); + } + + /// Build temperature display for AC functions dialog + static Widget _buildCountDownDisplay( + BuildContext context, + dynamic initialValue, + AllDevicesModel? device, + String operationName, + DeviceFunctionData? selectedFunctionData, + String selectCode) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + '${initialValue ?? 0} sec', + style: context.textTheme.headlineMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), + ), + ); + } + + static Widget _buildCountDownSlider( + BuildContext context, + dynamic initialValue, + AllDevicesModel? device, + String operationName, + DeviceFunctionData? selectedFunctionData, + String selectCode, + ) { + final operationalValues = SwitchOperationalValue( + icon: '', + description: "sec", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ); + return Slider( + value: (initialValue ?? 0).toDouble(), + min: operationalValues.minValue?.toDouble() ?? 0.0, + max: operationalValues.maxValue?.toDouble() ?? 0.0, + divisions: (((operationalValues.maxValue ?? 0) - + (operationalValues.minValue ?? 0)) / + (operationalValues.stepValue ?? 1)) + .round(), + onChanged: (value) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: value, + condition: selectedFunctionData?.condition, + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); + }, + ); + } + + static Widget _buildOperationalValuesList({ + required BuildContext context, + required List values, + required dynamic selectedValue, + AllDevicesModel? device, + required String operationName, + required String selectCode, + DeviceFunctionData? selectedFunctionData, + }) { + return ListView.builder( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + final isSelected = selectedValue == value.value; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + placeholderBuilder: (BuildContext context) => Container( + width: 24, + height: 24, + color: Colors.transparent, + ), + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Icon( + isSelected + ? Icons.radio_button_checked + : Icons.radio_button_unchecked, + size: 24, + color: isSelected + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.textGray, + ), + onTap: () { + if (!isSelected) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: value.value, + condition: selectedFunctionData?.condition, + valueDescription: + selectedFunctionData?.valueDescription, + ), + ), + ); + } }, ); }, diff --git a/lib/pages/routiens/helper/two_gang_switch_helper.dart b/lib/pages/routiens/helper/two_gang_switch_helper.dart index 038ee345..3805882a 100644 --- a/lib/pages/routiens/helper/two_gang_switch_helper.dart +++ b/lib/pages/routiens/helper/two_gang_switch_helper.dart @@ -1,8 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; -import 'package:syncrow_web/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -10,62 +14,60 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; class TwoGangSwitchHelper { static Future?> showSwitchFunctionsDialog( - BuildContext context, List> functions) async { - List> switchFunctions = functions - .where((f) => - f is TwoGangSwitch1Function || - f is TwoGangSwitch2Function || - f is TwoGangCountdown1Function || - f is TwoGangCountdown2Function) - .toList(); + BuildContext context, + List functions, + AllDevicesModel? device, + List? deviceSelectedFunctions, + String uniqueCustomId, + ) async { + List switchFunctions = + functions.whereType().toList(); - final selectedFunctionNotifier = ValueNotifier(null); - final selectedValueNotifier = ValueNotifier(null); - final selectedConditionNotifier = ValueNotifier('<'); - final selectedConditionsNotifier = - ValueNotifier>([true, false, false]); - - await showDialog( + return showDialog?>( context: context, builder: (BuildContext context) { - return ValueListenableBuilder( - valueListenable: selectedFunctionNotifier, - builder: (context, selectedFunction, _) { - return AlertDialog( + return BlocProvider( + create: (_) => FunctionBloc() + ..add(InitializeFunctions(deviceSelectedFunctions ?? [])), + child: AlertDialog( contentPadding: EdgeInsets.zero, - content: Container( - width: selectedFunction != null ? 600 : 300, - height: 450, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - ), - padding: const EdgeInsets.only(top: 20), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const DialogHeader('2 Gangs Light Switch Condition'), - Expanded( - child: Row( - children: [ - // Left side: Function list - Expanded( - child: ListView.separated( - itemCount: switchFunctions.length, - separatorBuilder: (_, __) => const Divider( - color: ColorsManager.dividerColor, - ), - itemBuilder: (context, index) { - final function = switchFunctions[index]; - return ValueListenableBuilder( - valueListenable: selectedFunctionNotifier, - builder: (context, selectedFunction, _) { - final isSelected = - selectedFunction == function.code; + content: BlocBuilder( + builder: (context, state) { + final selectedFunction = state.selectedFunction; + final selectedOperationName = state.selectedOperationName; + final selectedFunctionData = state.addedFunctions + .firstWhere((f) => f.functionCode == selectedFunction, + orElse: () => DeviceFunctionData( + entityId: '', + functionCode: selectedFunction ?? '', + operationName: '', + value: null, + )); + return Container( + width: selectedFunction != null ? 600 : 360, + height: 450, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const DialogHeader('2 Gangs Light Switch Condition'), + Expanded( + child: Row( + children: [ + // Left side: Function list + Expanded( + child: ListView.separated( + itemCount: switchFunctions.length, + separatorBuilder: (_, __) => const Divider( + color: ColorsManager.dividerColor, + ), + itemBuilder: (context, index) { + final function = switchFunctions[index]; return ListTile( - tileColor: isSelected - ? Colors.grey.shade100 - : null, leading: SvgPicture.asset( function.icon, width: 24, @@ -81,191 +83,307 @@ class TwoGangSwitchHelper { color: ColorsManager.textGray, ), onTap: () { - selectedFunctionNotifier.value = - function.code; - selectedValueNotifier.value = function - is TwoGangCountdown1Function || - function - is TwoGangCountdown2Function - ? 0 - : null; + context + .read() + .add(SelectFunction( + functionCode: function.code, + operationName: + function.operationName, + )); }, ); }, - ); - }, - ), - ), - // Right side: Value selector - if (selectedFunction != null) - Expanded( - child: ValueListenableBuilder( - valueListenable: selectedValueNotifier, - builder: (context, selectedValue, _) { - final selectedFn = switchFunctions.firstWhere( - (f) => f.code == selectedFunction, - ); - - if (selectedFn is TwoGangCountdown1Function || - selectedFn is TwoGangCountdown2Function) { - return _buildCountDownSelector( - context, - selectedValueNotifier, - selectedConditionNotifier, - selectedConditionsNotifier, - ); - } - - return _buildOperationalValuesList( - context, - selectedFn as BaseSwitchFunction, - selectedValueNotifier, - ); - }, + ), ), - ), - ], - ), + // Right side: Value selector + if (selectedFunction != null) + Expanded( + child: _buildValueSelector( + context: context, + selectedFunction: selectedFunction, + selectedFunctionData: selectedFunctionData, + switchFunctions: switchFunctions, + device: device, + operationName: selectedOperationName ?? '', + ), + ), + ], + ), + ), + Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + DialogFooter( + onCancel: () { + Navigator.pop(context); + }, + onConfirm: state.addedFunctions.isNotEmpty + ? () { + /// add the functions to the routine bloc + // for (var function in state.addedFunctions) { + // context.read().add( + // AddFunctionToRoutine( + // function, + // uniqueCustomId + // ), + // ); + // } + context.read().add( + AddFunctionToRoutine( + state.addedFunctions, + uniqueCustomId, + ), + ); + // Return the device data to be added to the container + Navigator.pop(context, { + 'deviceId': functions.first.deviceId, + }); + } + : null, + isConfirmEnabled: selectedFunction != null, + ), + ], ), - DialogFooter( - onCancel: () => Navigator.pop(context), - onConfirm: selectedFunction != null && - selectedValueNotifier.value != null - ? () { - final selectedFn = switchFunctions.firstWhere( - (f) => f.code == selectedFunction, - ); - final value = selectedValueNotifier.value; - final functionData = DeviceFunctionData( - entityId: selectedFn.deviceId, - functionCode: selectedFn.code, - operationName: selectedFn.operationName, - value: value, - condition: selectedConditionNotifier.value, - valueDescription: selectedFn - is TwoGangCountdown1Function || - selectedFn is TwoGangCountdown2Function - ? '${value} sec' - : ((selectedFn as BaseSwitchFunction) - .getOperationalValues() - .firstWhere((v) => v.value == value) - .description), - ); - Navigator.pop( - context, {selectedFn.code: functionData}); - } - : null, - isConfirmEnabled: selectedFunction != null, - ), - ], - ), - ), - ); - }, - ); - }, - ).then((value) { - selectedFunctionNotifier.dispose(); - selectedValueNotifier.dispose(); - selectedConditionNotifier.dispose(); - selectedConditionsNotifier.dispose(); - return value; - }); - } - - static Widget _buildOperationalValuesList( - BuildContext context, - BaseSwitchFunction function, - ValueNotifier valueNotifier, - ) { - final values = function.getOperationalValues(); - return ValueListenableBuilder( - valueListenable: valueNotifier, - builder: (context, selectedValue, _) { - return ListView.builder( - itemCount: values.length, - itemBuilder: (context, index) { - final value = values[index]; - return ListTile( - leading: SvgPicture.asset( - value.icon, - width: 24, - height: 24, - ), - title: Text( - value.description, - style: context.textTheme.bodyMedium, - ), - trailing: Radio( - value: value.value, - groupValue: selectedValue, - onChanged: (newValue) { - valueNotifier.value = newValue; + ); }, ), - ); - }, - ); + )); }, ); } - static Widget _buildCountDownSelector( + static Widget _buildValueSelector({ + required BuildContext context, + required String selectedFunction, + required DeviceFunctionData? selectedFunctionData, + required List switchFunctions, + AllDevicesModel? device, + required String operationName, + }) { + if (selectedFunction == 'countdown_1' || + selectedFunction == 'countdown_2') { + final initialValue = selectedFunctionData?.value ?? 200; + return _buildTemperatureSelector( + context: context, + initialValue: initialValue, + selectCode: selectedFunction, + currentCondition: selectedFunctionData?.condition, + device: device, + operationName: operationName, + selectedFunctionData: selectedFunctionData, + ); + } + + final selectedFn = + switchFunctions.firstWhere((f) => f.code == selectedFunction); + final values = selectedFn.getOperationalValues(); + + return _buildOperationalValuesList( + context: context, + values: values, + selectedValue: selectedFunctionData?.value, + device: device, + operationName: operationName, + selectCode: selectedFunction, + selectedFunctionData: selectedFunctionData, + ); + } + + static Widget _buildTemperatureSelector({ + required BuildContext context, + required dynamic initialValue, + required String? currentCondition, + required String selectCode, + AllDevicesModel? device, + required String operationName, + DeviceFunctionData? selectedFunctionData, + }) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildConditionToggle( + context, + currentCondition, + selectCode, + device, + operationName, + selectedFunctionData, + ), + const SizedBox(height: 20), + _buildCountDownDisplay(context, initialValue, device, operationName, + selectedFunctionData, selectCode), + const SizedBox(height: 20), + _buildCountDownSlider(context, initialValue, device, operationName, + selectedFunctionData, selectCode), + ], + ); + } + + /// Build condition toggle for AC functions dialog + static Widget _buildConditionToggle( BuildContext context, - ValueNotifier valueNotifier, - ValueNotifier conditionNotifier, - ValueNotifier> conditionsNotifier, + String? currentCondition, + String selectCode, + AllDevicesModel? device, + String operationName, + DeviceFunctionData? selectedFunctionData, + // Function(String) onConditionChanged, ) { - return ValueListenableBuilder( - valueListenable: valueNotifier, - builder: (context, value, _) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ValueListenableBuilder>( - valueListenable: conditionsNotifier, - builder: (context, selectedConditions, _) { - return ToggleButtons( - onPressed: (int index) { - final newConditions = List.filled(3, false); - newConditions[index] = true; - conditionsNotifier.value = newConditions; - conditionNotifier.value = index == 0 - ? "<" - : index == 1 - ? "==" - : ">"; - }, - borderRadius: const BorderRadius.all(Radius.circular(8)), - selectedBorderColor: ColorsManager.primaryColorWithOpacity, - selectedColor: Colors.white, - fillColor: ColorsManager.primaryColorWithOpacity, - color: ColorsManager.primaryColorWithOpacity, - constraints: const BoxConstraints( - minHeight: 40.0, - minWidth: 40.0, - ), - isSelected: selectedConditions, - children: const [Text("<"), Text("="), Text(">")], - ); - }, + final conditions = ["<", "==", ">"]; + + return ToggleButtons( + onPressed: (int index) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + condition: conditions[index], + value: selectedFunctionData?.value, + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); + }, + borderRadius: const BorderRadius.all(Radius.circular(8)), + selectedBorderColor: ColorsManager.primaryColorWithOpacity, + selectedColor: Colors.white, + fillColor: ColorsManager.primaryColorWithOpacity, + color: ColorsManager.primaryColorWithOpacity, + constraints: const BoxConstraints( + minHeight: 40.0, + minWidth: 40.0, + ), + isSelected: + conditions.map((c) => c == (currentCondition ?? "==")).toList(), + children: conditions.map((c) => Text(c)).toList(), + ); + } + + /// Build temperature display for AC functions dialog + static Widget _buildCountDownDisplay( + BuildContext context, + dynamic initialValue, + AllDevicesModel? device, + String operationName, + DeviceFunctionData? selectedFunctionData, + String selectCode) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + '${initialValue ?? 0} sec', + style: context.textTheme.headlineMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), + ), + ); + } + + static Widget _buildCountDownSlider( + BuildContext context, + dynamic initialValue, + AllDevicesModel? device, + String operationName, + DeviceFunctionData? selectedFunctionData, + String selectCode, + ) { + final operationalValues = SwitchOperationalValue( + icon: '', + description: "sec", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ); + return Slider( + value: (initialValue ?? 0).toDouble(), + min: operationalValues.minValue?.toDouble() ?? 0.0, + max: operationalValues.maxValue?.toDouble() ?? 0.0, + divisions: (((operationalValues.maxValue ?? 0) - + (operationalValues.minValue ?? 0)) / + (operationalValues.stepValue ?? 1)) + .round(), + onChanged: (value) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: value, + condition: selectedFunctionData?.condition, + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); + }, + ); + } + + static Widget _buildOperationalValuesList({ + required BuildContext context, + required List values, + required dynamic selectedValue, + AllDevicesModel? device, + required String operationName, + required String selectCode, + DeviceFunctionData? selectedFunctionData, + }) { + return ListView.builder( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + final isSelected = selectedValue == value.value; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + placeholderBuilder: (BuildContext context) => Container( + width: 24, + height: 24, + color: Colors.transparent, ), - const SizedBox(height: 20), - Text( - '${value ?? 0} sec', - style: Theme.of(context).textTheme.headlineMedium, - ), - const SizedBox(height: 20), - Slider( - value: (value ?? 0).toDouble(), - min: 0, - max: 300, - divisions: 300, - onChanged: (newValue) { - valueNotifier.value = newValue.toInt(); - }, - ), - ], + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Icon( + isSelected + ? Icons.radio_button_checked + : Icons.radio_button_unchecked, + size: 24, + color: isSelected + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.textGray, + ), + onTap: () { + if (!isSelected) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: value.value, + condition: selectedFunctionData?.condition, + valueDescription: + selectedFunctionData?.valueDescription, + ), + ), + ); + } + }, ); }, ); diff --git a/lib/pages/routiens/models/device_functions.dart b/lib/pages/routiens/models/device_functions.dart index a1f5725e..59b63a4f 100644 --- a/lib/pages/routiens/models/device_functions.dart +++ b/lib/pages/routiens/models/device_functions.dart @@ -1,5 +1,3 @@ -import 'package:flutter/material.dart'; - abstract class DeviceFunction { final String deviceId; final String deviceName; @@ -24,7 +22,6 @@ class DeviceFunctionData { final dynamic value; final String? condition; final String? valueDescription; - final UniqueKey uniqueKey; DeviceFunctionData({ required this.entityId, @@ -34,7 +31,7 @@ class DeviceFunctionData { required this.value, this.condition, this.valueDescription, - }) : uniqueKey = UniqueKey(); + }); Map toJson() { return { diff --git a/lib/pages/routiens/models/routine_item.dart b/lib/pages/routiens/models/routine_item.dart index 33920634..08263d53 100644 --- a/lib/pages/routiens/models/routine_item.dart +++ b/lib/pages/routiens/models/routine_item.dart @@ -1,29 +1,30 @@ -import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +// import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; -class RoutineItem { - final AllDevicesModel device; - final String? function; - final dynamic value; +// class RoutineItem { +// final AllDevicesModel device; +// final String? function; +// final dynamic value; - RoutineItem({ - required this.device, - this.function, - this.value, - }); +// RoutineItem({ +// required this.device, +// this.function, +// this.value, +// }); - Map toMap() { - return { - 'device': device, - 'function': function, - 'value': value, - }; - } +// Map toMap() { +// return { +// 'device': device, +// 'function': function, +// 'value': value, +// }; +// } - factory RoutineItem.fromMap(Map map) { - return RoutineItem( - device: map['device'] as AllDevicesModel, - function: map['function'], - value: map['value'], - ); - } -} +// factory RoutineItem.fromMap(Map map) { +// return RoutineItem( +// device: map['device'] as AllDevicesModel, +// function: map['function'], +// value: map['value'], +// ); +// } +// } +// : uniqueCustomId = uniqueCustomId ?? const Uuid().v4() diff --git a/lib/pages/routiens/models/routine_model.dart b/lib/pages/routiens/models/routine_model.dart index 2db2247f..8035b044 100644 --- a/lib/pages/routiens/models/routine_model.dart +++ b/lib/pages/routiens/models/routine_model.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - class ScenesModel { final String id; final String name; @@ -7,11 +5,13 @@ class ScenesModel { final String type; final String? icon; - ScenesModel({required this.id, required this.name, required this.status, required this.type, this.icon}); - - factory ScenesModel.fromRawJson(String str) => ScenesModel.fromJson(json.decode(str)); - - String toRawJson() => json.encode(toJson()); + ScenesModel({ + required this.id, + required this.name, + required this.status, + required this.type, + this.icon, + }); factory ScenesModel.fromJson(Map json) => ScenesModel( id: json["id"], diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index 197ea0d2..2b25b9c4 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -22,9 +22,8 @@ class DraggableCard extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - final deviceFunctions = state.selectedFunctions - .where((f) => f.entityId == deviceData['deviceId']) - .toList(); + final deviceFunctions = + state.selectedFunctions[deviceData['uniqueCustomId']] ?? []; return Draggable>( data: deviceData, @@ -45,7 +44,7 @@ class DraggableCard extends StatelessWidget { color: ColorsManager.whiteColors, child: Container( padding: const EdgeInsets.all(8), - width: 90, + width: 110, height: deviceFunctions.isEmpty ? 123 : null, child: Column( mainAxisSize: MainAxisSize.min, diff --git a/lib/pages/routiens/widgets/routine_devices.dart b/lib/pages/routiens/widgets/routine_devices.dart index 20d47007..67d1dc85 100644 --- a/lib/pages/routiens/widgets/routine_devices.dart +++ b/lib/pages/routiens/widgets/routine_devices.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; +import 'package:uuid/uuid.dart'; class RoutineDevices extends StatelessWidget { const RoutineDevices({super.key}); diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index 9c868f09..82a5feea 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -5,6 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; +import 'package:uuid/uuid.dart'; class ThenContainer extends StatelessWidget { const ThenContainer({super.key}); @@ -15,34 +16,38 @@ class ThenContainer extends StatelessWidget { builder: (context, state) { return DragTarget>( builder: (context, candidateData, rejectedData) { - return Container( - padding: const EdgeInsets.all(16), - width: double.infinity, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('THEN', - style: - TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), - const SizedBox(height: 16), - Wrap( - spacing: 8, - runSpacing: 8, - children: state.thenItems - .map((item) => DraggableCard( - // key: Key(item['key']!), - imagePath: item['imagePath']!, - title: item['title']!, - deviceData: item, - )) - .toList(), - ), - ], + return SingleChildScrollView( + child: Container( + padding: const EdgeInsets.all(16), + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('THEN', + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold)), + const SizedBox(height: 16), + Wrap( + spacing: 8, + runSpacing: 8, + children: state.thenItems + .map((item) => DraggableCard( + // key: Key(item['key']!), + imagePath: item['imagePath']!, + title: item['title']!, + deviceData: item, + )) + .toList(), + ), + ], + ), ), ); }, onWillAccept: (data) => data != null, onAccept: (data) async { + final uniqueCustomId = const Uuid().v4(); + data['uniqueCustomId'] = uniqueCustomId; final result = await DeviceDialogHelper.showDeviceDialog(context, data); // if (result != null) { diff --git a/pubspec.lock b/pubspec.lock index 3e12d6c5..cfd8b310 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -57,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" cupertino_icons: dependency: "direct main" description: @@ -137,6 +145,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" fl_chart: dependency: "direct main" description: @@ -533,6 +549,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -589,6 +613,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" vector_graphics: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c8f42ea5..3aa81860 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,6 +51,7 @@ dependencies: flutter_dotenv: ^5.1.0 fl_chart: ^0.69.0 time_picker_spinner: ^1.0.0 + uuid: ^4.4.0 dev_dependencies: flutter_test: From 1f49b9bc496d9ab68f99c4533506330bd3d890cb Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Sun, 24 Nov 2024 01:12:28 +0300 Subject: [PATCH 16/40] Added tab to run to if container --- .../bloc/routine_bloc/routine_bloc.dart | 24 ++++---- .../bloc/routine_bloc/routine_event.dart | 5 +- lib/pages/routiens/widgets/dragable_card.dart | 8 +-- lib/pages/routiens/widgets/if_container.dart | 59 ++++++++++++------- 4 files changed, 54 insertions(+), 42 deletions(-) diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 2b711f39..babd820a 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -11,6 +11,9 @@ part 'routine_state.dart'; const spaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6'; class RoutineBloc extends Bloc { + bool isAutomation = false; + bool isTabToRun = false; + RoutineBloc() : super(const RoutineState()) { on(_onAddToIfContainer); on(_onAddToThenContainer); @@ -23,17 +26,15 @@ class RoutineBloc extends Bloc { void _onAddToIfContainer(AddToIfContainer event, Emitter emit) { // if (!_isDuplicate(state.ifItems, event.item)) { - final updatedIfItems = List>.from(state.ifItems) - ..add(event.item); + final updatedIfItems = List>.from(state.ifItems)..add(event.item); + isTabToRun = event.isTabToRun; emit(state.copyWith(ifItems: updatedIfItems)); // } } - void _onAddToThenContainer( - AddToThenContainer event, Emitter emit) { + void _onAddToThenContainer(AddToThenContainer event, Emitter emit) { // if (!_isDuplicate(state.thenItems, event.item)) { - final updatedThenItems = List>.from(state.thenItems) - ..add(event.item); + final updatedThenItems = List>.from(state.thenItems)..add(event.item); emit(state.copyWith(thenItems: updatedThenItems)); // } } @@ -47,9 +48,8 @@ class RoutineBloc extends Bloc { void _onRemoveFunction(RemoveFunction event, Emitter emit) { final functions = List.from(state.selectedFunctions) - ..removeWhere((f) => - f.functionCode == event.function.functionCode && - f.value == event.function.value); + ..removeWhere( + (f) => f.functionCode == event.function.functionCode && f.value == event.function.value); emit(state.copyWith(selectedFunctions: functions)); } @@ -64,8 +64,7 @@ class RoutineBloc extends Bloc { // item['title'] == newItem['title']); // } - Future _onLoadScenes( - LoadScenes event, Emitter emit) async { + Future _onLoadScenes(LoadScenes event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { @@ -82,8 +81,7 @@ class RoutineBloc extends Bloc { } } - Future _onLoadAutomation( - LoadAutomation event, Emitter emit) async { + Future _onLoadAutomation(LoadAutomation event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart index 3321af5e..8ac1efd7 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart @@ -9,11 +9,12 @@ abstract class RoutineEvent extends Equatable { class AddToIfContainer extends RoutineEvent { final Map item; + final bool isTabToRun; - const AddToIfContainer(this.item); + const AddToIfContainer(this.item, this.isTabToRun); @override - List get props => [item]; + List get props => [item, isTabToRun]; } class AddToThenContainer extends RoutineEvent { diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index 197ea0d2..fa4f7f7c 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -22,9 +22,8 @@ class DraggableCard extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - final deviceFunctions = state.selectedFunctions - .where((f) => f.entityId == deviceData['deviceId']) - .toList(); + final deviceFunctions = + state.selectedFunctions.where((f) => f.entityId == deviceData['deviceId']).toList(); return Draggable>( data: deviceData, @@ -39,8 +38,7 @@ class DraggableCard extends StatelessWidget { ); } - Widget _buildCardContent( - BuildContext context, List deviceFunctions) { + Widget _buildCardContent(BuildContext context, List deviceFunctions) { return Card( color: ColorsManager.whiteColors, child: Container( diff --git a/lib/pages/routiens/widgets/if_container.dart b/lib/pages/routiens/widgets/if_container.dart index 779453f1..a7afa245 100644 --- a/lib/pages/routiens/widgets/if_container.dart +++ b/lib/pages/routiens/widgets/if_container.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; class IfContainer extends StatelessWidget { const IfContainer({super.key}); @@ -19,35 +20,49 @@ class IfContainer extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('IF', - style: - TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const Text('IF', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 16), - Wrap( - spacing: 8, - runSpacing: 8, - children: state.ifItems - .map((item) => DraggableCard( - key: Key(item['key']!), - imagePath: item['imagePath']!, - title: item['title']!, - deviceData: item, - )) - .toList(), - ), + if (context.read().isTabToRun) + const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + DraggableCard( + imagePath: Assets.tabToRun, + title: 'Tab to run', + deviceData: {}, + ), + ], + ), + if (!context.read().isTabToRun) + Wrap( + spacing: 8, + runSpacing: 8, + children: state.ifItems + .map((item) => DraggableCard( + // key: Key(item['key']!), + imagePath: item['imagePath'] ?? '', + title: item['title'] ?? 'Tab to run', + deviceData: item, + )) + .toList(), + ), ], ), ); }, onWillAccept: (data) => data != null, onAccept: (data) async { - final result = - await DeviceDialogHelper.showDeviceDialog(context, data); - if (result != null) { - context.read().add(AddToIfContainer(result)); - } else if (!['AC', '1G', '2G', '3G'] - .contains(data['productType'])) { - context.read().add(AddToIfContainer(data)); + if (!context.read().isTabToRun) { + if (data['deviceId'] == 'tab_to_run') { + context.read().add(AddToIfContainer(data, true)); + } else { + final result = await DeviceDialogHelper.showDeviceDialog(context, data); + if (result != null) { + context.read().add(AddToIfContainer(result, false)); + } else if (!['AC', '1G', '2G', '3G'].contains(data['productType'])) { + context.read().add(AddToIfContainer(data, false)); + } + } } }, ); From 5a9729fe1046d1f069602ca5017e811f777d51fe Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 24 Nov 2024 09:07:01 +0300 Subject: [PATCH 17/40] push add delay device --- .../all_devices/models/devices_model.dart | 1 + .../bloc/routine_bloc/routine_bloc.dart | 34 ++++----- .../bloc/routine_bloc/routine_event.dart | 2 +- lib/pages/routiens/helper/delay_helper.dart | 76 +++++++++++++++++++ .../models/delay/delay_fucntions.dart | 28 +++++++ .../conditions_routines_devices_view.dart | 1 + lib/pages/routiens/widgets/dialog_header.dart | 4 + lib/pages/routiens/widgets/dragable_card.dart | 16 ++-- .../routiens/widgets/routine_devices.dart | 2 +- .../routiens/widgets/then_container.dart | 44 ++++++----- 10 files changed, 165 insertions(+), 43 deletions(-) create mode 100644 lib/pages/routiens/helper/delay_helper.dart create mode 100644 lib/pages/routiens/models/delay/delay_fucntions.dart diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index b4f72287..b7e4f010 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -277,6 +277,7 @@ SOS ThreeGangCountdown3Function( deviceId: uuid ?? '', deviceName: name ?? ''), ]; + default: return []; } diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 44e2d84b..197da85f 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -25,23 +25,23 @@ class RoutineBloc extends Bloc { } void _onAddToIfContainer(AddToIfContainer event, Emitter emit) { - // if (!_isDuplicate(state.ifItems, event.item)) { - final updatedIfItems = List>.from(state.ifItems)..add(event.item); + final updatedIfItems = List>.from(state.ifItems) + ..add(event.item); isTabToRun = event.isTabToRun; emit(state.copyWith(ifItems: updatedIfItems)); - // } } - void _onAddToThenContainer(AddToThenContainer event, Emitter emit) { - // if (!_isDuplicate(state.thenItems, event.item)) { - final updatedThenItems = List>.from(state.thenItems)..add(event.item); + void _onAddToThenContainer( + AddToThenContainer event, Emitter emit) { + final updatedThenItems = List>.from(state.thenItems) + ..add(event.item); emit(state.copyWith(thenItems: updatedThenItems)); - // } } - void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter emit) { - debugPrint(event.uniqueCustomId.toString()); - debugPrint(event.functions.toString()); + void _onAddFunctionsToRoutine( + AddFunctionToRoutine event, Emitter emit) { + debugPrint('Adding functions to routine: ${event.functions}'); + debugPrint('Unique Custom ID: ${event.uniqueCustomId}'); final currentSelectedFunctions = Map>.from(state.selectedFunctions); @@ -52,6 +52,7 @@ class RoutineBloc extends Bloc { } emit(state.copyWith(selectedFunctions: currentSelectedFunctions)); + debugPrint('Updated selected functions: $currentSelectedFunctions'); } // void _onRemoveFunction(RemoveFunction event, Emitter emit) { @@ -66,14 +67,8 @@ class RoutineBloc extends Bloc { // emit(state.copyWith(selectedFunctions: [])); // } - // bool _isDuplicate( - // List> items, Map newItem) { - // return items.any((item) => - // item['imagePath'] == newItem['imagePath'] && - // item['title'] == newItem['title']); - // } - - Future _onLoadScenes(LoadScenes event, Emitter emit) async { + Future _onLoadScenes( + LoadScenes event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { @@ -90,7 +85,8 @@ class RoutineBloc extends Bloc { } } - Future _onLoadAutomation(LoadAutomation event, Emitter emit) async { + Future _onLoadAutomation( + LoadAutomation event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart index 37c1e564..9e7c6f16 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart @@ -49,7 +49,7 @@ class AddFunctionToRoutine extends RoutineEvent { final String uniqueCustomId; const AddFunctionToRoutine(this.functions, this.uniqueCustomId); @override - List get props => [functions]; + List get props => [functions, uniqueCustomId]; } class RemoveFunction extends RoutineEvent { diff --git a/lib/pages/routiens/helper/delay_helper.dart b/lib/pages/routiens/helper/delay_helper.dart new file mode 100644 index 00000000..8ecff897 --- /dev/null +++ b/lib/pages/routiens/helper/delay_helper.dart @@ -0,0 +1,76 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DelayHelper { + static Future?> showDelayPickerDialog( + BuildContext context, String uniqueCustomId) async { + int hours = 0; + int minutes = 0; + + return showDialog?>( + context: context, + builder: (BuildContext context) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: Container( + width: 600, + height: 300, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const DialogHeader('Select Delay Duration'), + Expanded( + child: CupertinoTimerPicker( + mode: CupertinoTimerPickerMode.hm, + initialTimerDuration: + Duration(hours: hours, minutes: minutes), + onTimerDurationChanged: (Duration newDuration) { + hours = newDuration.inHours; + minutes = newDuration.inMinutes % 60; + }, + ), + ), + DialogFooter( + onCancel: () { + Navigator.of(context).pop(); + }, + onConfirm: () { + int totalSeconds = (hours * 3600) + (minutes * 60); + context.read().add(AddFunctionToRoutine( + [ + DeviceFunctionData( + entityId: 'delay', + functionCode: 'delay', + operationName: 'Delay', + value: totalSeconds, + ) + ], + uniqueCustomId, + )); + + Navigator.pop(context, { + 'deviceId': 'delay', + 'value': totalSeconds, + }); + }, + isConfirmEnabled: true, + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/pages/routiens/models/delay/delay_fucntions.dart b/lib/pages/routiens/models/delay/delay_fucntions.dart new file mode 100644 index 00000000..ff04251a --- /dev/null +++ b/lib/pages/routiens/models/delay/delay_fucntions.dart @@ -0,0 +1,28 @@ +import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class DelayFunction extends BaseSwitchFunction { + DelayFunction({required super.deviceId, required super.deviceName}) + : super( + code: 'delay', + operationName: 'Delay', + icon: Assets.delay, + ); + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: '', + description: "Duration in seconds", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ), + ]; + + int convertToSeconds(int hours, int minutes) { + return (hours * 3600) + (minutes * 60); + } +} diff --git a/lib/pages/routiens/widgets/conditions_routines_devices_view.dart b/lib/pages/routiens/widgets/conditions_routines_devices_view.dart index 7bda1d51..89b1f3f0 100644 --- a/lib/pages/routiens/widgets/conditions_routines_devices_view.dart +++ b/lib/pages/routiens/widgets/conditions_routines_devices_view.dart @@ -91,6 +91,7 @@ class ConditionsRoutinesDevicesView extends StatelessWidget { 'deviceId': 'delay', 'type': 'action', 'name': 'Delay the action', + 'uniqueCustomId': '', }, ), ], diff --git a/lib/pages/routiens/widgets/dialog_header.dart b/lib/pages/routiens/widgets/dialog_header.dart index 6701b5b0..4fe1f0b1 100644 --- a/lib/pages/routiens/widgets/dialog_header.dart +++ b/lib/pages/routiens/widgets/dialog_header.dart @@ -9,7 +9,11 @@ class DialogHeader extends StatelessWidget { @override Widget build(BuildContext context) { return Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ + const SizedBox( + height: 10, + ), Text( title, style: Theme.of(context).textTheme.bodyMedium!.copyWith( diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index f3d8b355..02c802bc 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -10,38 +10,44 @@ class DraggableCard extends StatelessWidget { final String imagePath; final String title; final Map deviceData; + final EdgeInsetsGeometry? padding; const DraggableCard({ super.key, required this.imagePath, required this.title, required this.deviceData, + this.padding, }); @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - final deviceFunctions = state.selectedFunctions[deviceData['uniqueCustomId']] ?? []; + final deviceFunctions = + state.selectedFunctions[deviceData['uniqueCustomId']] ?? []; return Draggable>( data: deviceData, feedback: Transform.rotate( angle: -0.1, - child: _buildCardContent(context, deviceFunctions), + child: + _buildCardContent(context, deviceFunctions, padding: padding), ), childWhenDragging: _buildGreyContainer(), - child: _buildCardContent(context, deviceFunctions), + child: _buildCardContent(context, deviceFunctions, padding: padding), ); }, ); } - Widget _buildCardContent(BuildContext context, List deviceFunctions) { + Widget _buildCardContent( + BuildContext context, List deviceFunctions, + {EdgeInsetsGeometry? padding}) { return Card( color: ColorsManager.whiteColors, child: Container( - padding: const EdgeInsets.all(8), + padding: padding ?? const EdgeInsets.all(16), width: 110, height: deviceFunctions.isEmpty ? 123 : null, child: Column( diff --git a/lib/pages/routiens/widgets/routine_devices.dart b/lib/pages/routiens/widgets/routine_devices.dart index 67d1dc85..02c1b4fa 100644 --- a/lib/pages/routiens/widgets/routine_devices.dart +++ b/lib/pages/routiens/widgets/routine_devices.dart @@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; -import 'package:uuid/uuid.dart'; class RoutineDevices extends StatelessWidget { const RoutineDevices({super.key}); @@ -41,6 +40,7 @@ class RoutineDevices extends StatelessWidget { 'deviceId': device.uuid, 'productType': device.productType, 'functions': device.functions, + 'uniqueCustomId': '', }, ); }).toList(), diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index 82a5feea..5d71e405 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -3,8 +3,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/helper/delay_helper.dart'; import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:uuid/uuid.dart'; class ThenContainer extends StatelessWidget { @@ -36,6 +38,8 @@ class ThenContainer extends StatelessWidget { imagePath: item['imagePath']!, title: item['title']!, deviceData: item, + padding: EdgeInsets.symmetric( + horizontal: 4, vertical: 8), )) .toList(), ), @@ -47,26 +51,32 @@ class ThenContainer extends StatelessWidget { onWillAccept: (data) => data != null, onAccept: (data) async { final uniqueCustomId = const Uuid().v4(); - data['uniqueCustomId'] = uniqueCustomId; - final result = - await DeviceDialogHelper.showDeviceDialog(context, data); - // if (result != null) { - // for (var function in routineBloc.state.selectedFunctions) { - // routineBloc.add(AddToThenContainer( - // { - // 'item': function, - // 'imagePath': data['imagePath'], - // 'title': data['name'], - // } - // )); - // } - // } + final mutableData = Map.from(data); + mutableData['uniqueCustomId'] = uniqueCustomId; + + if (mutableData['deviceId'] == 'delay') { + final result = await DelayHelper.showDelayPickerDialog( + context, mutableData['uniqueCustomId']); + + if (result != null) { + context.read().add(AddToThenContainer({ + ...mutableData, + 'imagePath': Assets.delay, + 'title': 'Delay', + })); + } + return; + } + + final result = + await DeviceDialogHelper.showDeviceDialog(context, mutableData); + if (result != null) { - context.read().add(AddToThenContainer(data)); + context.read().add(AddToThenContainer(mutableData)); } else if (!['AC', '1G', '2G', '3G'] - .contains(data['productType'])) { - context.read().add(AddToThenContainer(data)); + .contains(mutableData['productType'])) { + context.read().add(AddToThenContainer(mutableData)); } }, ); From 87c47a74ce465150478bc8d0d28c1ab17c1737da Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 24 Nov 2024 11:17:54 +0300 Subject: [PATCH 18/40] push calling create scene --- .../common/text_field/custom_text_field.dart | 18 +- .../view/device_managment_page.dart | 89 ++++--- .../widgets/device_search_filters.dart | 10 +- .../bloc/routine_bloc/routine_bloc.dart | 103 +++++++- .../bloc/routine_bloc/routine_event.dart | 20 ++ .../bloc/routine_bloc/routine_state.dart | 10 + .../routiens/helper/save_routine_helper.dart | 132 ++++++++++ lib/pages/routiens/helper/setting_helper.dart | 191 ++++++++++----- .../create_scene/create_scene_model.dart | 230 ++++++++++++++++++ .../widgets/routine_search_and_buttons.dart | 30 ++- .../widgets/search_bar_condition_title.dart | 5 +- lib/services/routines_api.dart | 33 +-- lib/utils/constants/api_const.dart | 19 +- 13 files changed, 759 insertions(+), 131 deletions(-) create mode 100644 lib/pages/routiens/helper/save_routine_helper.dart create mode 100644 lib/pages/routiens/models/create_scene/create_scene_model.dart diff --git a/lib/pages/common/text_field/custom_text_field.dart b/lib/pages/common/text_field/custom_text_field.dart index 23f033b6..250ff110 100644 --- a/lib/pages/common/text_field/custom_text_field.dart +++ b/lib/pages/common/text_field/custom_text_field.dart @@ -18,6 +18,7 @@ class StatefulTextField extends StatefulWidget { this.padding, this.icon, this.hintColor, + required this.onChanged, }); final String title; @@ -32,6 +33,7 @@ class StatefulTextField extends StatefulWidget { final double? padding; final IconData? icon; final Color? hintColor; + final Function(String)? onChanged; @override State createState() => _StatefulTextFieldState(); @@ -59,6 +61,7 @@ class _StatefulTextFieldState extends State { padding: widget.padding, icon: widget.icon, hintColor: widget.hintColor, + onChanged: widget.onChanged, ); } } @@ -78,6 +81,7 @@ class CustomTextField extends StatelessWidget { this.padding, this.icon, this.hintColor, + required this.onChanged, }); final String title; @@ -92,6 +96,7 @@ class CustomTextField extends StatelessWidget { final double? padding; final IconData? icon; final Color? hintColor; + final Function(String)? onChanged; @override Widget build(BuildContext context) { @@ -120,14 +125,21 @@ class CustomTextField extends StatelessWidget { style: const TextStyle(color: Colors.black), decoration: InputDecoration( hintText: hintText, - hintStyle: TextStyle(fontSize: 12, color: hintColor ?? ColorsManager.blackColor), - contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: padding ?? 10), + hintStyle: TextStyle( + fontSize: 12, color: hintColor ?? ColorsManager.blackColor), + contentPadding: EdgeInsets.symmetric( + horizontal: 12, vertical: padding ?? 10), border: InputBorder.none, - suffixIcon: icon != null ? Icon(icon, color: ColorsManager.greyColor) : null, + suffixIcon: icon != null + ? Icon(icon, color: ColorsManager.greyColor) + : null, ), onFieldSubmitted: (_) { onSubmittedFun!(); }, + onChanged: (value) { + onChanged!(value); + }, ), ), ), diff --git a/lib/pages/device_managment/all_devices/view/device_managment_page.dart b/lib/pages/device_managment/all_devices/view/device_managment_page.dart index e0661b50..ffc57131 100644 --- a/lib/pages/device_managment/all_devices/view/device_managment_page.dart +++ b/lib/pages/device_managment/all_devices/view/device_managment_page.dart @@ -2,8 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart'; +import 'package:syncrow_web/pages/routiens/view/routines_view.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; @@ -20,7 +22,8 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { create: (context) => DeviceManagementBloc()..add(FetchDevices()), ), BlocProvider( - create: (context) => SwitchTabsBloc()..add(const TriggerSwitchTabsEvent(false)), + create: (context) => + SwitchTabsBloc()..add(const TriggerSwitchTabsEvent(false)), ), ], child: WebScaffold( @@ -30,7 +33,8 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { style: Theme.of(context).textTheme.headlineLarge, ), ), - centerBody: BlocBuilder(builder: (context, state) { + centerBody: BlocBuilder( + builder: (context, state) { return Row( mainAxisSize: MainAxisSize.min, children: [ @@ -39,16 +43,21 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { backgroundColor: null, ), onPressed: () { - context.read().add(const TriggerSwitchTabsEvent(false)); + context + .read() + .add(const TriggerSwitchTabsEvent(false)); }, child: Text( 'Devices', style: context.textTheme.titleMedium?.copyWith( - color: state is SelectedTabState && state.selectedTab == false - ? ColorsManager.whiteColors - : ColorsManager.grayColor, - fontWeight: - (state is SelectedTabState) && state.selectedTab == false ? FontWeight.w700 : FontWeight.w400, + color: + state is SelectedTabState && state.selectedTab == false + ? ColorsManager.whiteColors + : ColorsManager.grayColor, + fontWeight: (state is SelectedTabState) && + state.selectedTab == false + ? FontWeight.w700 + : FontWeight.w400, ), ), ), @@ -57,16 +66,21 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { backgroundColor: null, ), onPressed: () { - context.read().add(const TriggerSwitchTabsEvent(true)); + context + .read() + .add(const TriggerSwitchTabsEvent(true)); }, child: Text( 'Routines', style: context.textTheme.titleMedium?.copyWith( - color: (state is SelectedTabState) && state.selectedTab == true - ? ColorsManager.whiteColors - : ColorsManager.grayColor, + color: + (state is SelectedTabState) && state.selectedTab == true + ? ColorsManager.whiteColors + : ColorsManager.grayColor, fontWeight: - (state is SelectedTabState) && state.selectedTab == true ? FontWeight.w700 : FontWeight.w400, + (state is SelectedTabState) && state.selectedTab == true + ? FontWeight.w700 + : FontWeight.w400, ), ), ), @@ -74,30 +88,31 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { ); }), rightBody: const NavigateHomeGridView(), - scaffoldBody: CreateNewRoutineView(), - // BlocBuilder(builder: (context, state) { - // if (state is SelectedTabState && state.selectedTab) { - // return const RoutinesView(); - // } - // if (state is ShowCreateRoutineState && state.showCreateRoutine) { - // return const CreateNewRoutineView(); - // } - // - // return BlocBuilder( - // builder: (context, deviceState) { - // if (deviceState is DeviceManagementLoading) { - // return const Center(child: CircularProgressIndicator()); - // } else if (deviceState is DeviceManagementLoaded || deviceState is DeviceManagementFiltered) { - // final devices = - // (deviceState as dynamic).devices ?? (deviceState as DeviceManagementFiltered).filteredDevices; - // - // return DeviceManagementBody(devices: devices); - // } else { - // return const Center(child: Text('Error fetching Devices')); - // } - // }, - // ); - // }), + scaffoldBody: BlocBuilder( + builder: (context, state) { + if (state is SelectedTabState && state.selectedTab) { + return const RoutinesView(); + } + if (state is ShowCreateRoutineState && state.showCreateRoutine) { + return const CreateNewRoutineView(); + } + + return BlocBuilder( + builder: (context, deviceState) { + if (deviceState is DeviceManagementLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (deviceState is DeviceManagementLoaded || + deviceState is DeviceManagementFiltered) { + final devices = (deviceState as dynamic).devices ?? + (deviceState as DeviceManagementFiltered).filteredDevices; + + return DeviceManagementBody(devices: devices); + } else { + return const Center(child: Text('Error fetching Devices')); + } + }, + ); + }), ), ); } diff --git a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart index d9e47aa6..e3bec220 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart @@ -12,7 +12,8 @@ class DeviceSearchFilters extends StatefulWidget { State createState() => _DeviceSearchFiltersState(); } -class _DeviceSearchFiltersState extends State with HelperResponsiveLayout { +class _DeviceSearchFiltersState extends State + with HelperResponsiveLayout { final TextEditingController communityController = TextEditingController(); final TextEditingController unitNameController = TextEditingController(); final TextEditingController productNameController = TextEditingController(); @@ -34,7 +35,8 @@ class _DeviceSearchFiltersState extends State with HelperRe const SizedBox(width: 20), _buildSearchField("Space Name", unitNameController, 200), const SizedBox(width: 20), - _buildSearchField("Device Name / Product Name", productNameController, 300), + _buildSearchField( + "Device Name / Product Name", productNameController, 300), const SizedBox(width: 20), _buildSearchResetButtons(), ], @@ -59,7 +61,8 @@ class _DeviceSearchFiltersState extends State with HelperRe ); } - Widget _buildSearchField(String title, TextEditingController controller, double width) { + Widget _buildSearchField( + String title, TextEditingController controller, double width) { return Container( child: StatefulTextField( title: title, @@ -73,6 +76,7 @@ class _DeviceSearchFiltersState extends State with HelperRe community: communityController.text, searchField: true)); }, + onChanged: (p0) {}, ), ); } diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 197da85f..1490b12d 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -1,6 +1,9 @@ +import 'dart:async'; + import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/routiens/models/create_scene/create_scene_model.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/routine_model.dart'; import 'package:syncrow_web/services/routines_api.dart'; @@ -20,6 +23,9 @@ class RoutineBloc extends Bloc { on(_onLoadScenes); on(_onLoadAutomation); on(_onAddFunctionsToRoutine); + on(_onSearchRoutines); + on(_onAddSelectedIcon); + on(_onCreateScene); // on(_onRemoveFunction); // on(_onClearFunctions); } @@ -40,8 +46,6 @@ class RoutineBloc extends Bloc { void _onAddFunctionsToRoutine( AddFunctionToRoutine event, Emitter emit) { - debugPrint('Adding functions to routine: ${event.functions}'); - debugPrint('Unique Custom ID: ${event.uniqueCustomId}'); final currentSelectedFunctions = Map>.from(state.selectedFunctions); @@ -52,7 +56,6 @@ class RoutineBloc extends Bloc { } emit(state.copyWith(selectedFunctions: currentSelectedFunctions)); - debugPrint('Updated selected functions: $currentSelectedFunctions'); } // void _onRemoveFunction(RemoveFunction event, Emitter emit) { @@ -80,7 +83,7 @@ class RoutineBloc extends Bloc { } catch (e) { emit(state.copyWith( isLoading: false, - errorMessage: 'Something went wrong', + errorMessage: 'Failed to load scenes', )); } } @@ -98,7 +101,97 @@ class RoutineBloc extends Bloc { } catch (e) { emit(state.copyWith( isLoading: false, - errorMessage: 'Something went wrong', + errorMessage: 'Failed to load automations', + )); + } + } + + FutureOr _onSearchRoutines( + SearchRoutines event, Emitter emit) { + emit(state.copyWith(routineName: event.query)); + } + + FutureOr _onAddSelectedIcon( + AddSelectedIcon event, Emitter emit) { + emit(state.copyWith(selectedIcon: event.icon)); + } + + bool _isFirstActionDelay(List> actions) { + if (actions.isEmpty) return false; + return actions.first['deviceId'] == 'delay'; + } + + Future _onCreateScene( + CreateSceneEvent event, Emitter emit) async { + try { + // Check if first action is delay + if (_isFirstActionDelay(state.thenItems)) { + emit(state.copyWith( + errorMessage: 'Cannot have delay as the first action', + isLoading: false, + )); + return; + } + + emit(state.copyWith(isLoading: true)); + + final actions = state.thenItems + .map((item) { + final functions = + state.selectedFunctions[item['uniqueCustomId']] ?? []; + if (functions.isEmpty) return null; + + final function = functions.first; + if (item['deviceId'] == 'delay') { + return CreateSceneAction( + entityId: function.entityId, + actionExecutor: 'delay', + executorProperty: CreateSceneExecutorProperty( + functionCode: '', + functionValue: '', + delaySeconds: function.value, + ), + ); + } + + return CreateSceneAction( + entityId: function.entityId, + actionExecutor: 'device_issue', + executorProperty: CreateSceneExecutorProperty( + functionCode: function.functionCode.toString(), + functionValue: function.value, + delaySeconds: 0, + ), + ); + }) + .whereType() + .toList(); + + final createSceneModel = CreateSceneModel( + spaceUuid: spaceId, + iconId: state.selectedIcon ?? '', + showInDevice: true, + sceneName: state.routineName ?? '', + decisionExpr: 'and', + actions: actions, + ); + + final result = await SceneApi.createScene(createSceneModel); + if (result['success']) { + emit(state.copyWith( + isLoading: false, + errorMessage: null, + )); + } else { + emit(state.copyWith( + isLoading: false, + errorMessage: result['message'], + )); + } + } catch (e) { + emit(state.copyWith( + isLoading: false, + errorMessage: e.toString(), )); } } diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart index 9e7c6f16..6168748d 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart @@ -59,4 +59,24 @@ class RemoveFunction extends RoutineEvent { List get props => [function]; } +class SearchRoutines extends RoutineEvent { + final String query; + const SearchRoutines(this.query); + @override + List get props => [query]; +} + +class AddSelectedIcon extends RoutineEvent { + final String icon; + const AddSelectedIcon(this.icon); + @override + List get props => [icon]; +} + +class CreateSceneEvent extends RoutineEvent { + const CreateSceneEvent(); + @override + List get props => []; +} + class ClearFunctions extends RoutineEvent {} diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart index ce6fea15..911992dc 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart @@ -9,6 +9,8 @@ class RoutineState extends Equatable { final Map> selectedFunctions; final bool isLoading; final String? errorMessage; + final String? routineName; + final String? selectedIcon; const RoutineState({ this.ifItems = const [], @@ -19,6 +21,8 @@ class RoutineState extends Equatable { this.selectedFunctions = const {}, this.isLoading = false, this.errorMessage, + this.routineName, + this.selectedIcon, }); RoutineState copyWith({ @@ -29,6 +33,8 @@ class RoutineState extends Equatable { Map>? selectedFunctions, bool? isLoading, String? errorMessage, + String? routineName, + String? selectedIcon, }) { return RoutineState( ifItems: ifItems ?? this.ifItems, @@ -38,6 +44,8 @@ class RoutineState extends Equatable { selectedFunctions: selectedFunctions ?? this.selectedFunctions, isLoading: isLoading ?? this.isLoading, errorMessage: errorMessage ?? this.errorMessage, + routineName: routineName ?? this.routineName, + selectedIcon: selectedIcon ?? this.selectedIcon, ); } @@ -50,5 +58,7 @@ class RoutineState extends Equatable { selectedFunctions, isLoading, errorMessage, + routineName, + selectedIcon, ]; } diff --git a/lib/pages/routiens/helper/save_routine_helper.dart b/lib/pages/routiens/helper/save_routine_helper.dart new file mode 100644 index 00000000..69329992 --- /dev/null +++ b/lib/pages/routiens/helper/save_routine_helper.dart @@ -0,0 +1,132 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class SaveRoutineHelper { + static Future showSaveRoutineDialog(BuildContext context) async { + return showDialog( + context: context, + builder: (BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: Container( + width: 600, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + DialogHeader('Create a scene: ${state.routineName ?? ""}'), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Left side - IF + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'IF:', + style: TextStyle( + fontSize: 16, + ), + ), + const SizedBox(height: 8), + if (context.read().isTabToRun) + ListTile( + leading: SvgPicture.asset( + Assets.tabToRun, + width: 24, + height: 24, + ), + title: const Text('Tab to run'), + ), + ], + ), + ), + const SizedBox(width: 16), + // Right side - THEN items + + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'THEN:', + style: TextStyle( + fontSize: 16, + ), + ), + const SizedBox(height: 8), + ...state.thenItems.map((item) { + final functions = state.selectedFunctions[ + item['uniqueCustomId']] ?? + []; + return ListTile( + leading: SvgPicture.asset( + item['imagePath'], + width: 22, + height: 22, + ), + title: Text(item['title'], + style: const TextStyle(fontSize: 14)), + subtitle: Wrap( + children: functions + .map((f) => Text( + '${f.operationName}: ${f.value}, ', + style: const TextStyle( + color: + ColorsManager.grayColor, + fontSize: 8), + overflow: TextOverflow.ellipsis, + maxLines: 3, + )) + .toList(), + ), + ); + }), + ], + ), + ), + ], + ), + ), + if (state.errorMessage != null) + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + state.errorMessage!, + style: const TextStyle(color: Colors.red), + ), + ), + DialogFooter( + onCancel: () => Navigator.pop(context), + onConfirm: () { + context + .read() + .add(const CreateSceneEvent()); + Navigator.pop(context); + }, + isConfirmEnabled: true, + ), + ], + ), + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/pages/routiens/helper/setting_helper.dart b/lib/pages/routiens/helper/setting_helper.dart index ae8a1bc8..2c0cee77 100644 --- a/lib/pages/routiens/helper/setting_helper.dart +++ b/lib/pages/routiens/helper/setting_helper.dart @@ -10,13 +10,16 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:flutter/cupertino.dart'; class SettingHelper { - static Future?> showSettingDialog( - {required BuildContext context, String? iconId, required bool isAutomation}) async { - return showDialog?>( + static Future showSettingDialog( + {required BuildContext context, + String? iconId, + required bool isAutomation}) async { + return showDialog( context: context, builder: (BuildContext context) { return BlocProvider( - create: (_) => SettingBloc()..add(InitialEvent(selectedIcon: iconId ?? '')), + create: (_) => + SettingBloc()..add(InitialEvent(selectedIcon: iconId ?? '')), child: AlertDialog( contentPadding: EdgeInsets.zero, content: BlocBuilder( @@ -29,7 +32,9 @@ class SettingHelper { } return Container( width: context.read().isExpanded ? 800 : 400, - height: context.read().isExpanded && isAutomation ? 500 : 300, + height: context.read().isExpanded && isAutomation + ? 500 + : 300, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), @@ -52,13 +57,18 @@ class SettingHelper { children: [ Container( padding: const EdgeInsets.only( - top: 10, left: 10, right: 10, bottom: 10), + top: 10, + left: 10, + right: 10, + bottom: 10), child: Column( children: [ InkWell( onTap: () {}, child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, children: [ Text( 'Validity', @@ -66,13 +76,18 @@ class SettingHelper { .textTheme .bodyMedium! .copyWith( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w400, + color: ColorsManager + .textPrimaryColor, + fontWeight: + FontWeight + .w400, fontSize: 14), ), const Icon( - Icons.arrow_forward_ios_outlined, - color: ColorsManager.textGray, + Icons + .arrow_forward_ios_outlined, + color: ColorsManager + .textGray, size: 15, ) ], @@ -89,14 +104,18 @@ class SettingHelper { ), InkWell( onTap: () { - BlocProvider.of(context).add( - FetchIcons( + BlocProvider.of( + context) + .add(FetchIcons( expanded: !context - .read() + .read< + SettingBloc>() .isExpanded)); }, child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, children: [ Text( 'Effective Period', @@ -104,13 +123,18 @@ class SettingHelper { .textTheme .bodyMedium! .copyWith( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w400, + color: ColorsManager + .textPrimaryColor, + fontWeight: + FontWeight + .w400, fontSize: 14), ), const Icon( - Icons.arrow_forward_ios_outlined, - color: ColorsManager.textGray, + Icons + .arrow_forward_ios_outlined, + color: ColorsManager + .textGray, size: 15, ) ], @@ -126,7 +150,9 @@ class SettingHelper { height: 5, ), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, children: [ Text( 'Executed by', @@ -134,8 +160,10 @@ class SettingHelper { .textTheme .bodyMedium! .copyWith( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w400, + color: ColorsManager + .textPrimaryColor, + fontWeight: + FontWeight.w400, fontSize: 14), ), Text('Cloud', @@ -143,8 +171,12 @@ class SettingHelper { .textTheme .bodyMedium! .copyWith( - color: ColorsManager.textGray, - fontWeight: FontWeight.w400, + color: + ColorsManager + .textGray, + fontWeight: + FontWeight + .w400, fontSize: 14)), ], ), @@ -157,19 +189,26 @@ class SettingHelper { children: [ Container( padding: const EdgeInsets.only( - top: 10, left: 10, right: 10, bottom: 10), + top: 10, + left: 10, + right: 10, + bottom: 10), child: Column( children: [ InkWell( onTap: () { - BlocProvider.of(context).add( - FetchIcons( + BlocProvider.of( + context) + .add(FetchIcons( expanded: !context - .read() + .read< + SettingBloc>() .isExpanded)); }, child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, children: [ Text( 'Icons', @@ -177,13 +216,18 @@ class SettingHelper { .textTheme .bodyMedium! .copyWith( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w400, + color: ColorsManager + .textPrimaryColor, + fontWeight: + FontWeight + .w400, fontSize: 14), ), const Icon( - Icons.arrow_forward_ios_outlined, - color: ColorsManager.textGray, + Icons + .arrow_forward_ios_outlined, + color: ColorsManager + .textGray, size: 15, ) ], @@ -199,7 +243,9 @@ class SettingHelper { height: 5, ), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, children: [ Text( 'Show on devices page', @@ -207,17 +253,21 @@ class SettingHelper { .textTheme .bodyMedium! .copyWith( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w400, + color: ColorsManager + .textPrimaryColor, + fontWeight: + FontWeight.w400, fontSize: 14), ), Row( - mainAxisAlignment: MainAxisAlignment.end, + mainAxisAlignment: + MainAxisAlignment.end, children: [ Container( height: 30, width: 1, - color: ColorsManager.graysColor, + color: ColorsManager + .graysColor, ), Transform.scale( scale: .8, @@ -241,7 +291,9 @@ class SettingHelper { height: 5, ), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, children: [ Text( 'Executed by', @@ -249,8 +301,10 @@ class SettingHelper { .textTheme .bodyMedium! .copyWith( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w400, + color: ColorsManager + .textPrimaryColor, + fontWeight: + FontWeight.w400, fontSize: 14), ), Text('Cloud', @@ -258,8 +312,12 @@ class SettingHelper { .textTheme .bodyMedium! .copyWith( - color: ColorsManager.textGray, - fontWeight: FontWeight.w400, + color: + ColorsManager + .textGray, + fontWeight: + FontWeight + .w400, fontSize: 14)), ], ), @@ -268,12 +326,14 @@ class SettingHelper { ], ), ), - if (context.read().isExpanded && !isAutomation) + if (context.read().isExpanded && + !isAutomation) SizedBox( width: 400, height: 150, child: state is LoadingState - ? const Center(child: CircularProgressIndicator()) + ? const Center( + child: CircularProgressIndicator()) : GridView.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( @@ -290,7 +350,8 @@ class SettingHelper { height: 35, child: InkWell( onTap: () { - BlocProvider.of(context) + BlocProvider.of( + context) .add(SelectIcon( iconId: iconModel.uuid, )); @@ -299,12 +360,16 @@ class SettingHelper { child: SizedBox( child: ClipOval( child: Container( - padding: const EdgeInsets.all(1), + padding: + const EdgeInsets.all(1), decoration: BoxDecoration( border: Border.all( - color: selectedIcon == iconModel.uuid - ? ColorsManager.primaryColorWithOpacity - : Colors.transparent, + color: selectedIcon == + iconModel.uuid + ? ColorsManager + .primaryColorWithOpacity + : Colors + .transparent, width: 2, ), shape: BoxShape.circle, @@ -319,8 +384,12 @@ class SettingHelper { ); }, )), - if (context.read().isExpanded && isAutomation) - const SizedBox(height: 350, width: 400, child: EffectivePeriodView()) + if (context.read().isExpanded && + isAutomation) + const SizedBox( + height: 350, + width: 400, + child: EffectivePeriodView()) ], ), Container( @@ -344,14 +413,20 @@ class SettingHelper { alignment: AlignmentDirectional.center, child: Text( 'Cancel', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( color: ColorsManager.textGray, ), ), ), ), ), - Container(width: 1, height: 50, color: ColorsManager.greyColor), + Container( + width: 1, + height: 50, + color: ColorsManager.greyColor), Expanded( child: InkWell( onTap: () { @@ -361,8 +436,12 @@ class SettingHelper { alignment: AlignmentDirectional.center, child: Text( 'Confirm', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: ColorsManager + .primaryColorWithOpacity, ), ), ), diff --git a/lib/pages/routiens/models/create_scene/create_scene_model.dart b/lib/pages/routiens/models/create_scene/create_scene_model.dart new file mode 100644 index 00000000..c669aa9a --- /dev/null +++ b/lib/pages/routiens/models/create_scene/create_scene_model.dart @@ -0,0 +1,230 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; + +class CreateSceneModel { + String spaceUuid; + String iconId; + bool showInDevice; + String sceneName; + String decisionExpr; + List actions; + + CreateSceneModel({ + required this.spaceUuid, + required this.iconId, + required this.showInDevice, + required this.sceneName, + required this.decisionExpr, + required this.actions, + }); + + CreateSceneModel copyWith({ + String? spaceUuid, + String? iconId, + bool? showInDevice, + String? sceneName, + String? decisionExpr, + List? actions, + bool? showInHomePage, + }) { + return CreateSceneModel( + spaceUuid: spaceUuid ?? this.spaceUuid, + iconId: iconId ?? this.iconId, + showInDevice: showInDevice ?? this.showInDevice, + sceneName: sceneName ?? this.sceneName, + decisionExpr: decisionExpr ?? this.decisionExpr, + actions: actions ?? this.actions, + ); + } + + Map toMap([String? sceneId]) { + return { + if (sceneId == null) 'spaceUuid': spaceUuid, + if (iconId.isNotEmpty) 'iconUuid': iconId, + 'showInHomePage': showInDevice, + 'sceneName': sceneName, + 'decisionExpr': decisionExpr, + 'actions': actions.map((x) => x.toMap()).toList(), + }; + } + + factory CreateSceneModel.fromMap(Map map) { + return CreateSceneModel( + spaceUuid: map['spaceUuid'] ?? '', + showInDevice: map['showInHomePage'] ?? false, + iconId: map['iconUuid'] ?? '', + sceneName: map['sceneName'] ?? '', + decisionExpr: map['decisionExpr'] ?? '', + actions: List.from( + map['actions']?.map((x) => CreateSceneAction.fromMap(x))), + ); + } + + String toJson([String? sceneId]) => json.encode(toMap(sceneId)); + + factory CreateSceneModel.fromJson(String source) => + CreateSceneModel.fromMap(json.decode(source)); + + @override + String toString() { + return 'CreateSceneModel(unitUuid: $spaceUuid, sceneName: $sceneName, decisionExpr: $decisionExpr, actions: $actions)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is CreateSceneModel && + other.spaceUuid == spaceUuid && + other.iconId == iconId && + other.showInDevice == showInDevice && + other.sceneName == sceneName && + other.decisionExpr == decisionExpr && + listEquals(other.actions, actions); + } + + @override + int get hashCode { + return spaceUuid.hashCode ^ + sceneName.hashCode ^ + decisionExpr.hashCode ^ + actions.hashCode; + } +} + +class CreateSceneAction { + String entityId; + String actionExecutor; + CreateSceneExecutorProperty? executorProperty; + + CreateSceneAction({ + required this.entityId, + required this.actionExecutor, + required this.executorProperty, + }); + + CreateSceneAction copyWith({ + String? entityId, + String? actionExecutor, + CreateSceneExecutorProperty? executorProperty, + }) { + return CreateSceneAction( + entityId: entityId ?? this.entityId, + actionExecutor: actionExecutor ?? this.actionExecutor, + executorProperty: executorProperty ?? this.executorProperty, + ); + } + + Map toMap() { + if (executorProperty != null) { + return { + 'entityId': entityId, + 'actionExecutor': actionExecutor, + 'executorProperty': executorProperty?.toMap(actionExecutor), + }; + } else { + return { + 'entityId': entityId, + 'actionExecutor': actionExecutor, + }; + } + } + + factory CreateSceneAction.fromMap(Map map) { + return CreateSceneAction( + entityId: map['entityId'] ?? '', + actionExecutor: map['actionExecutor'] ?? '', + executorProperty: + CreateSceneExecutorProperty.fromMap(map['executorProperty']), + ); + } + + String toJson() => json.encode(toMap()); + + factory CreateSceneAction.fromJson(String source) => + CreateSceneAction.fromMap(json.decode(source)); + + @override + String toString() => + 'CreateSceneAction(entityId: $entityId, actionExecutor: $actionExecutor, executorProperty: $executorProperty)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is CreateSceneAction && + other.entityId == entityId && + other.actionExecutor == actionExecutor && + other.executorProperty == executorProperty; + } + + @override + int get hashCode => + entityId.hashCode ^ actionExecutor.hashCode ^ executorProperty.hashCode; +} + +class CreateSceneExecutorProperty { + String functionCode; + dynamic functionValue; + int delaySeconds; + + CreateSceneExecutorProperty({ + required this.functionCode, + required this.functionValue, + required this.delaySeconds, + }); + + CreateSceneExecutorProperty copyWith({ + String? functionCode, + dynamic functionValue, + int? delaySeconds, + }) { + return CreateSceneExecutorProperty( + functionCode: functionCode ?? this.functionCode, + functionValue: functionValue ?? this.functionValue, + delaySeconds: delaySeconds ?? this.delaySeconds, + ); + } + + Map toMap(String actionExecutor) { + final map = {}; + if (functionCode.isNotEmpty) map['functionCode'] = functionCode; + if (functionValue != null) map['functionValue'] = functionValue; + if (actionExecutor == 'delay' && delaySeconds > 0) { + map['delaySeconds'] = delaySeconds; + } + return map; + } + + factory CreateSceneExecutorProperty.fromMap(Map map) { + return CreateSceneExecutorProperty( + functionCode: map['functionCode'] ?? '', + functionValue: map['functionValue'] ?? '', + delaySeconds: map['delaySeconds']?.toInt() ?? 0, + ); + } + + String toJson(String actionExecutor) => json.encode(toMap(actionExecutor)); + + factory CreateSceneExecutorProperty.fromJson(String source) => + CreateSceneExecutorProperty.fromMap(json.decode(source)); + + @override + String toString() => + 'CreateSceneExecutorProperty(functionCode: $functionCode, functionValue: $functionValue, delaySeconds: $delaySeconds)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is CreateSceneExecutorProperty && + other.functionCode == functionCode && + other.functionValue == functionValue && + other.delaySeconds == delaySeconds; + } + + @override + int get hashCode => + functionCode.hashCode ^ functionValue.hashCode ^ delaySeconds.hashCode; +} diff --git a/lib/pages/routiens/widgets/routine_search_and_buttons.dart b/lib/pages/routiens/widgets/routine_search_and_buttons.dart index 598905e1..66f2a3a0 100644 --- a/lib/pages/routiens/widgets/routine_search_and_buttons.dart +++ b/lib/pages/routiens/widgets/routine_search_and_buttons.dart @@ -1,6 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/main.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/helper/save_routine_helper.dart'; import 'package:syncrow_web/pages/routiens/helper/setting_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -28,7 +32,9 @@ class RoutineSearchAndButtons extends StatelessWidget { children: [ ConstrainedBox( constraints: BoxConstraints( - maxWidth: constraints.maxWidth > 700 ? 450 : constraints.maxWidth - 32), + maxWidth: constraints.maxWidth > 700 + ? 450 + : constraints.maxWidth - 32), child: StatefulTextField( title: 'Routine Name', height: 40, @@ -38,6 +44,11 @@ class RoutineSearchAndButtons extends StatelessWidget { elevation: 0, borderRadius: 15, width: 450, + onChanged: (value) { + context + .read() + .add(SearchRoutines(value)); + }, ), ), (constraints.maxWidth <= 1000) @@ -48,8 +59,17 @@ class RoutineSearchAndButtons extends StatelessWidget { child: Center( child: DefaultButton( onPressed: () async { - final result = await SettingHelper.showSettingDialog( - context: context, isAutomation: true); + final result = + await SettingHelper.showSettingDialog( + context: context, + isAutomation: context + .read() + .isAutomation); + if (result != null) { + context + .read() + .add(AddSelectedIcon(result)); + } }, borderRadius: 15, elevation: 0, @@ -100,7 +120,9 @@ class RoutineSearchAndButtons extends StatelessWidget { width: 200, child: Center( child: DefaultButton( - onPressed: () {}, + onPressed: () { + SaveRoutineHelper.showSaveRoutineDialog(context); + }, borderRadius: 15, elevation: 0, backgroundColor: ColorsManager.primaryColor, diff --git a/lib/pages/routiens/widgets/search_bar_condition_title.dart b/lib/pages/routiens/widgets/search_bar_condition_title.dart index 40d6b575..e36ba873 100644 --- a/lib/pages/routiens/widgets/search_bar_condition_title.dart +++ b/lib/pages/routiens/widgets/search_bar_condition_title.dart @@ -4,7 +4,8 @@ import 'package:syncrow_web/pages/routiens/widgets/routines_title_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class ConditionTitleAndSearchBar extends StatelessWidget with HelperResponsiveLayout { +class ConditionTitleAndSearchBar extends StatelessWidget + with HelperResponsiveLayout { const ConditionTitleAndSearchBar({ super.key, }); @@ -33,6 +34,7 @@ class ConditionTitleAndSearchBar extends StatelessWidget with HelperResponsiveLa borderRadius: BorderRadius.circular(15), ), controller: TextEditingController(), + onChanged: (value) {}, ), ], ) @@ -55,6 +57,7 @@ class ConditionTitleAndSearchBar extends StatelessWidget with HelperResponsiveLa borderRadius: BorderRadius.circular(15), ), controller: TextEditingController(), + onChanged: (value) {}, ), ], ); diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index 7b001751..b137982d 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -1,3 +1,4 @@ +import 'package:syncrow_web/pages/routiens/models/create_scene/create_scene_model.dart'; import 'package:syncrow_web/pages/routiens/models/icon_model.dart'; import 'package:syncrow_web/pages/routiens/models/routine_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; @@ -7,22 +8,22 @@ class SceneApi { static final HTTPService _httpService = HTTPService(); // //create scene -// static Future> createScene( -// CreateSceneModel createSceneModel) async { -// try { -// final response = await _httpService.post( -// path: ApiEndpoints.createScene, -// body: createSceneModel.toMap(), -// showServerMessage: false, -// expectedResponseModel: (json) { -// return json; -// }, -// ); -// return response; -// } catch (e) { -// rethrow; -// } -// } + static Future> createScene( + CreateSceneModel createSceneModel) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.createScene, + body: createSceneModel.toMap(), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } // // // create automation // static Future> createAutomation( diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index c7f7bccb..a5decc3b 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -11,12 +11,14 @@ abstract class ApiEndpoints { static const String visitorPassword = '/visitor-password'; static const String getDevices = '/visitor-password/devices'; - static const String sendOnlineOneTime = '/visitor-password/temporary-password/online/one-time'; + static const String sendOnlineOneTime = + '/visitor-password/temporary-password/online/one-time'; static const String sendOnlineMultipleTime = '/visitor-password/temporary-password/online/multiple-time'; //offline Password - static const String sendOffLineOneTime = '/visitor-password/temporary-password/offline/one-time'; + static const String sendOffLineOneTime = + '/visitor-password/temporary-password/offline/one-time'; static const String sendOffLineMultipleTime = '/visitor-password/temporary-password/offline/multiple-time'; @@ -38,13 +40,18 @@ abstract class ApiEndpoints { '/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}'; static const String scheduleByDeviceId = '/schedule/{deviceUuid}'; - static const String getScheduleByDeviceId = '/schedule/{deviceUuid}?category={category}'; - static const String deleteScheduleByDeviceId = '/schedule/{deviceUuid}/{scheduleUuid}'; - static const String updateScheduleByDeviceId = '/schedule/enable/{deviceUuid}'; + static const String getScheduleByDeviceId = + '/schedule/{deviceUuid}?category={category}'; + static const String deleteScheduleByDeviceId = + '/schedule/{deviceUuid}/{scheduleUuid}'; + static const String updateScheduleByDeviceId = + '/schedule/enable/{deviceUuid}'; static const String factoryReset = '/device/factory/reset/{deviceUuid}'; - static const String powerClamp = '/device/{powerClampUuid}/power-clamp/status'; + static const String powerClamp = + '/device/{powerClampUuid}/power-clamp/status'; static const String getSpaceScenes = '/scene/tap-to-run/{unitUuid}'; static const String getSpaceAutomation = '/automation/{unitUuid}'; static const String getIconScene = '/scene/icon'; + static const String createScene = '/scene/tap-to-run'; } From 0c555cda83574eceac44b779a09095bd3d7c8ee9 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 24 Nov 2024 23:07:03 +0300 Subject: [PATCH 19/40] push tab to run and handling automation --- .../common/text_field/custom_text_field.dart | 16 ++ .../view/device_managment_page.dart | 47 +++--- .../bloc/routine_bloc/routine_bloc.dart | 73 +++++--- .../bloc/routine_bloc/routine_event.dart | 8 + .../bloc/routine_bloc/routine_state.dart | 12 ++ lib/pages/routiens/helper/ac_helper.dart | 3 +- lib/pages/routiens/helper/delay_helper.dart | 2 - lib/pages/routiens/helper/setting_helper.dart | 10 +- lib/pages/routiens/widgets/dragable_card.dart | 156 +++++++++++------- lib/pages/routiens/widgets/if_container.dart | 79 +++++++-- .../widgets/routine_search_and_buttons.dart | 7 +- .../routiens/widgets/then_container.dart | 32 ++-- lib/services/routines_api.dart | 4 + 13 files changed, 295 insertions(+), 154 deletions(-) diff --git a/lib/pages/common/text_field/custom_text_field.dart b/lib/pages/common/text_field/custom_text_field.dart index 250ff110..3ba71cd1 100644 --- a/lib/pages/common/text_field/custom_text_field.dart +++ b/lib/pages/common/text_field/custom_text_field.dart @@ -19,6 +19,7 @@ class StatefulTextField extends StatefulWidget { this.icon, this.hintColor, required this.onChanged, + this.isRequired, }); final String title; @@ -34,6 +35,7 @@ class StatefulTextField extends StatefulWidget { final IconData? icon; final Color? hintColor; final Function(String)? onChanged; + final bool? isRequired; @override State createState() => _StatefulTextFieldState(); @@ -62,6 +64,7 @@ class _StatefulTextFieldState extends State { icon: widget.icon, hintColor: widget.hintColor, onChanged: widget.onChanged, + isRequired: widget.isRequired, ); } } @@ -82,6 +85,7 @@ class CustomTextField extends StatelessWidget { this.icon, this.hintColor, required this.onChanged, + this.isRequired, }); final String title; @@ -97,6 +101,7 @@ class CustomTextField extends StatelessWidget { final IconData? icon; final Color? hintColor; final Function(String)? onChanged; + final bool? isRequired; @override Widget build(BuildContext context) { @@ -123,6 +128,7 @@ class CustomTextField extends StatelessWidget { child: TextFormField( controller: controller, style: const TextStyle(color: Colors.black), + decoration: InputDecoration( hintText: hintText, hintStyle: TextStyle( @@ -140,6 +146,16 @@ class CustomTextField extends StatelessWidget { onChanged: (value) { onChanged!(value); }, + + /// required validator + validator: (value) { + if (isRequired == true) { + if (value == null || value.isEmpty) { + return 'This field is required'; + } + } + return null; + }, ), ), ), diff --git a/lib/pages/device_managment/all_devices/view/device_managment_page.dart b/lib/pages/device_managment/all_devices/view/device_managment_page.dart index ffc57131..4044c056 100644 --- a/lib/pages/device_managment/all_devices/view/device_managment_page.dart +++ b/lib/pages/device_managment/all_devices/view/device_managment_page.dart @@ -88,31 +88,32 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { ); }), rightBody: const NavigateHomeGridView(), - scaffoldBody: BlocBuilder( - builder: (context, state) { - if (state is SelectedTabState && state.selectedTab) { - return const RoutinesView(); - } - if (state is ShowCreateRoutineState && state.showCreateRoutine) { - return const CreateNewRoutineView(); - } + scaffoldBody: CreateNewRoutineView(), + // BlocBuilder( + // builder: (context, state) { + // if (state is SelectedTabState && state.selectedTab) { + // return const RoutinesView(); + // } + // if (state is ShowCreateRoutineState && state.showCreateRoutine) { + // return const CreateNewRoutineView(); + // } - return BlocBuilder( - builder: (context, deviceState) { - if (deviceState is DeviceManagementLoading) { - return const Center(child: CircularProgressIndicator()); - } else if (deviceState is DeviceManagementLoaded || - deviceState is DeviceManagementFiltered) { - final devices = (deviceState as dynamic).devices ?? - (deviceState as DeviceManagementFiltered).filteredDevices; + // return BlocBuilder( + // builder: (context, deviceState) { + // if (deviceState is DeviceManagementLoading) { + // return const Center(child: CircularProgressIndicator()); + // } else if (deviceState is DeviceManagementLoaded || + // deviceState is DeviceManagementFiltered) { + // final devices = (deviceState as dynamic).devices ?? + // (deviceState as DeviceManagementFiltered).filteredDevices; - return DeviceManagementBody(devices: devices); - } else { - return const Center(child: Text('Error fetching Devices')); - } - }, - ); - }), + // return DeviceManagementBody(devices: devices); + // } else { + // return const Center(child: Text('Error fetching Devices')); + // } + // }, + // ); + // }), ), ); } diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 1490b12d..35a522b6 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -26,6 +26,7 @@ class RoutineBloc extends Bloc { on(_onSearchRoutines); on(_onAddSelectedIcon); on(_onCreateScene); + on(_onRemoveDragCard); // on(_onRemoveFunction); // on(_onClearFunctions); } @@ -33,7 +34,13 @@ class RoutineBloc extends Bloc { void _onAddToIfContainer(AddToIfContainer event, Emitter emit) { final updatedIfItems = List>.from(state.ifItems) ..add(event.item); - isTabToRun = event.isTabToRun; + if (event.isTabToRun) { + isTabToRun = true; + isAutomation = false; + } else { + isTabToRun = false; + isAutomation = true; + } emit(state.copyWith(ifItems: updatedIfItems)); } @@ -46,30 +53,27 @@ class RoutineBloc extends Bloc { void _onAddFunctionsToRoutine( AddFunctionToRoutine event, Emitter emit) { - final currentSelectedFunctions = - Map>.from(state.selectedFunctions); + try { + if (event.functions.isEmpty) return; - if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) { - currentSelectedFunctions[event.uniqueCustomId]!.addAll(event.functions); - } else { - currentSelectedFunctions[event.uniqueCustomId] = event.functions; + final currentSelectedFunctions = + Map>.from(state.selectedFunctions); + + if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) { + currentSelectedFunctions[event.uniqueCustomId] = + List.from(currentSelectedFunctions[event.uniqueCustomId]!) + ..addAll(event.functions); + } else { + currentSelectedFunctions[event.uniqueCustomId] = + List.from(event.functions); + } + + emit(state.copyWith(selectedFunctions: currentSelectedFunctions)); + } catch (e) { + debugPrint('Error adding functions: $e'); } - - emit(state.copyWith(selectedFunctions: currentSelectedFunctions)); } - // void _onRemoveFunction(RemoveFunction event, Emitter emit) { - // final functions = List.from(state.selectedFunctions) - // ..removeWhere((f) => - // f.functionCode == event.function.functionCode && - // f.value == event.function.value); - // emit(state.copyWith(selectedFunctions: functions)); - // } - - // void _onClearFunctions(ClearFunctions event, Emitter emit) { - // emit(state.copyWith(selectedFunctions: [])); - // } - Future _onLoadScenes( LoadScenes event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); @@ -83,7 +87,9 @@ class RoutineBloc extends Bloc { } catch (e) { emit(state.copyWith( isLoading: false, - errorMessage: 'Failed to load scenes', + loadScenesErrorMessage: 'Failed to load scenes', + errorMessage: '', + loadAutomationErrorMessage: '', )); } } @@ -101,7 +107,9 @@ class RoutineBloc extends Bloc { } catch (e) { emit(state.copyWith( isLoading: false, - errorMessage: 'Failed to load automations', + loadAutomationErrorMessage: 'Failed to load automations', + errorMessage: '', + loadScenesErrorMessage: '', )); } } @@ -178,10 +186,7 @@ class RoutineBloc extends Bloc { final result = await SceneApi.createScene(createSceneModel); if (result['success']) { - emit(state.copyWith( - isLoading: false, - errorMessage: null, - )); + emit(const RoutineState()); } else { emit(state.copyWith( isLoading: false, @@ -195,4 +200,18 @@ class RoutineBloc extends Bloc { )); } } + + FutureOr _onRemoveDragCard( + RemoveDragCard event, Emitter emit) { + if (event.isFromThen) { + /// remove element from thenItems at specific index + final thenItems = List>.from(state.thenItems); + thenItems.removeAt(event.index); + emit(state.copyWith(thenItems: thenItems)); + } else { + final ifItems = List>.from(state.ifItems); + ifItems.removeAt(event.index); + emit(state.copyWith(ifItems: ifItems)); + } + } } diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart index 6168748d..9e154cf4 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart @@ -79,4 +79,12 @@ class CreateSceneEvent extends RoutineEvent { List get props => []; } +class RemoveDragCard extends RoutineEvent { + final int index; + final bool isFromThen; + const RemoveDragCard({required this.index, required this.isFromThen}); + @override + List get props => [index]; +} + class ClearFunctions extends RoutineEvent {} diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart index 911992dc..784cd148 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart @@ -9,6 +9,8 @@ class RoutineState extends Equatable { final Map> selectedFunctions; final bool isLoading; final String? errorMessage; + final String? loadScenesErrorMessage; + final String? loadAutomationErrorMessage; final String? routineName; final String? selectedIcon; @@ -23,6 +25,8 @@ class RoutineState extends Equatable { this.errorMessage, this.routineName, this.selectedIcon, + this.loadScenesErrorMessage, + this.loadAutomationErrorMessage, }); RoutineState copyWith({ @@ -35,6 +39,8 @@ class RoutineState extends Equatable { String? errorMessage, String? routineName, String? selectedIcon, + String? loadAutomationErrorMessage, + String? loadScenesErrorMessage, }) { return RoutineState( ifItems: ifItems ?? this.ifItems, @@ -46,6 +52,10 @@ class RoutineState extends Equatable { errorMessage: errorMessage ?? this.errorMessage, routineName: routineName ?? this.routineName, selectedIcon: selectedIcon ?? this.selectedIcon, + loadScenesErrorMessage: + loadScenesErrorMessage ?? this.loadScenesErrorMessage, + loadAutomationErrorMessage: + loadAutomationErrorMessage ?? this.loadAutomationErrorMessage, ); } @@ -60,5 +70,7 @@ class RoutineState extends Equatable { errorMessage, routineName, selectedIcon, + loadScenesErrorMessage, + loadAutomationErrorMessage, ]; } diff --git a/lib/pages/routiens/helper/ac_helper.dart b/lib/pages/routiens/helper/ac_helper.dart index 8b555b7a..10606b69 100644 --- a/lib/pages/routiens/helper/ac_helper.dart +++ b/lib/pages/routiens/helper/ac_helper.dart @@ -96,14 +96,13 @@ class ACHelper { onConfirm: state.addedFunctions.isNotEmpty ? () { /// add the functions to the routine bloc - // for (var function in state.addedFunctions) { context.read().add( AddFunctionToRoutine( state.addedFunctions, uniqueCustomId, ), ); - //} + // Return the device data to be added to the container Navigator.pop(context, { 'deviceId': functions.first.deviceId, diff --git a/lib/pages/routiens/helper/delay_helper.dart b/lib/pages/routiens/helper/delay_helper.dart index 8ecff897..1191ac58 100644 --- a/lib/pages/routiens/helper/delay_helper.dart +++ b/lib/pages/routiens/helper/delay_helper.dart @@ -1,12 +1,10 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; class DelayHelper { static Future?> showDelayPickerDialog( diff --git a/lib/pages/routiens/helper/setting_helper.dart b/lib/pages/routiens/helper/setting_helper.dart index 2c0cee77..621a5219 100644 --- a/lib/pages/routiens/helper/setting_helper.dart +++ b/lib/pages/routiens/helper/setting_helper.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_event.dart'; import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_state.dart'; @@ -10,13 +11,14 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:flutter/cupertino.dart'; class SettingHelper { - static Future showSettingDialog( - {required BuildContext context, - String? iconId, - required bool isAutomation}) async { + static Future showSettingDialog({ + required BuildContext context, + String? iconId, + }) async { return showDialog( context: context, builder: (BuildContext context) { + final isAutomation = context.read().isAutomation; return BlocProvider( create: (_) => SettingBloc()..add(InitialEvent(selectedIcon: iconId ?? '')), diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index 02c802bc..ae66ce02 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -11,6 +11,9 @@ class DraggableCard extends StatelessWidget { final String title; final Map deviceData; final EdgeInsetsGeometry? padding; + final void Function()? onRemove; + final bool? isFromThen; + final bool? isFromIf; const DraggableCard({ super.key, @@ -18,6 +21,9 @@ class DraggableCard extends StatelessWidget { required this.title, required this.deviceData, this.padding, + this.onRemove, + this.isFromThen, + this.isFromIf, }); @override @@ -44,75 +50,97 @@ class DraggableCard extends StatelessWidget { Widget _buildCardContent( BuildContext context, List deviceFunctions, {EdgeInsetsGeometry? padding}) { - return Card( - color: ColorsManager.whiteColors, - child: Container( - padding: padding ?? const EdgeInsets.all(16), - width: 110, - height: deviceFunctions.isEmpty ? 123 : null, - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Column( + return Stack( + children: [ + Card( + color: ColorsManager.whiteColors, + child: Container( + padding: padding ?? const EdgeInsets.all(16), + width: 110, + height: deviceFunctions.isEmpty ? 123 : null, + child: Column( + mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - Container( - height: 50, - width: 50, - decoration: BoxDecoration( - color: ColorsManager.CircleImageBackground, - borderRadius: BorderRadius.circular(90), - border: Border.all( - color: ColorsManager.graysColor, - ), - ), - padding: const EdgeInsets.all(8), - child: imagePath.contains('.svg') - ? SvgPicture.asset( - imagePath, - ) - : Image.network(imagePath), - ), - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 3), - child: Text( - title, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 2, - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.blackColor, - fontSize: 12, - ), - ), - ), - ], - ), - if (deviceFunctions.isNotEmpty) - // const Divider(height: 1), - ...deviceFunctions.map((function) => Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: Text( - '${function.operationName}: ${function.value}', - style: context.textTheme.bodySmall?.copyWith( - fontSize: 9, - color: ColorsManager.textGray, - height: 1.2, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + height: 50, + width: 50, + decoration: BoxDecoration( + color: ColorsManager.CircleImageBackground, + borderRadius: BorderRadius.circular(90), + border: Border.all( + color: ColorsManager.graysColor, ), ), - ], - )), - ], + padding: const EdgeInsets.all(8), + child: imagePath.contains('.svg') + ? SvgPicture.asset( + imagePath, + ) + : Image.network(imagePath), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 3), + child: Text( + title, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontSize: 12, + ), + ), + ), + ], + ), + if (deviceFunctions.isNotEmpty) + // const Divider(height: 1), + ...deviceFunctions.map((function) => Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Text( + '${function.operationName}: ${function.value}', + style: context.textTheme.bodySmall?.copyWith( + fontSize: 9, + color: ColorsManager.textGray, + height: 1.2, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], + )), + ], + ), + ), ), - ), + Positioned( + top: -4, + right: -6, + child: Visibility( + visible: (isFromIf ?? false) || (isFromThen ?? false), + child: IconButton( + onPressed: onRemove == null + ? null + : () { + onRemove!(); + }, + icon: const Icon( + Icons.close, + color: ColorsManager.boxColor, + ), + ), + ), + ), + ], ); } diff --git a/lib/pages/routiens/widgets/if_container.dart b/lib/pages/routiens/widgets/if_container.dart index a7afa245..cfb1b27c 100644 --- a/lib/pages/routiens/widgets/if_container.dart +++ b/lib/pages/routiens/widgets/if_container.dart @@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:uuid/uuid.dart'; class IfContainer extends StatelessWidget { const IfContainer({super.key}); @@ -20,7 +21,9 @@ class IfContainer extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('IF', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const Text('IF', + style: + TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 16), if (context.read().isTabToRun) const Row( @@ -37,14 +40,23 @@ class IfContainer extends StatelessWidget { Wrap( spacing: 8, runSpacing: 8, - children: state.ifItems - .map((item) => DraggableCard( - // key: Key(item['key']!), - imagePath: item['imagePath'] ?? '', - title: item['title'] ?? 'Tab to run', - deviceData: item, - )) - .toList(), + children: List.generate( + state.ifItems.length, + (index) => DraggableCard( + imagePath: + state.ifItems[index]['imagePath'] ?? '', + title: state.ifItems[index]['title'] ?? '', + deviceData: state.ifItems[index], + padding: const EdgeInsets.symmetric( + horizontal: 4, vertical: 8), + isFromThen: false, + isFromIf: true, + onRemove: () { + context.read().add( + RemoveDragCard( + index: index, isFromThen: false)); + }, + )), ), ], ), @@ -52,15 +64,52 @@ class IfContainer extends StatelessWidget { }, onWillAccept: (data) => data != null, onAccept: (data) async { + // final uniqueCustomId = const Uuid().v4(); + + // final mutableData = Map.from(data); + // mutableData['uniqueCustomId'] = uniqueCustomId; + + // if (!context.read().isTabToRun) { + // if (data['deviceId'] == 'tab_to_run') { + // context.read().add(AddToIfContainer(data, true)); + // } else { + // final result = + // await DeviceDialogHelper.showDeviceDialog(context, mutableData); + // if (result != null) { + // context + // .read() + // .add(AddToIfContainer(mutableData, false)); + // } else if (!['AC', '1G', '2G', '3G'] + // .contains(data['productType'])) { + // context + // .read() + // .add(AddToIfContainer(mutableData, false)); + // } + // } + //} + final uniqueCustomId = const Uuid().v4(); + + final mutableData = Map.from(data); + mutableData['uniqueCustomId'] = uniqueCustomId; + if (!context.read().isTabToRun) { - if (data['deviceId'] == 'tab_to_run') { - context.read().add(AddToIfContainer(data, true)); + if (mutableData['deviceId'] == 'tab_to_run') { + context + .read() + .add(AddToIfContainer(mutableData, true)); } else { - final result = await DeviceDialogHelper.showDeviceDialog(context, data); + final result = await DeviceDialogHelper.showDeviceDialog( + context, mutableData); + if (result != null) { - context.read().add(AddToIfContainer(result, false)); - } else if (!['AC', '1G', '2G', '3G'].contains(data['productType'])) { - context.read().add(AddToIfContainer(data, false)); + context + .read() + .add(AddToIfContainer(mutableData, false)); + } else if (!['AC', '1G', '2G', '3G'] + .contains(mutableData['productType'])) { + context + .read() + .add(AddToIfContainer(mutableData, false)); } } } diff --git a/lib/pages/routiens/widgets/routine_search_and_buttons.dart b/lib/pages/routiens/widgets/routine_search_and_buttons.dart index 66f2a3a0..fa0e2128 100644 --- a/lib/pages/routiens/widgets/routine_search_and_buttons.dart +++ b/lib/pages/routiens/widgets/routine_search_and_buttons.dart @@ -43,6 +43,7 @@ class RoutineSearchAndButtons extends StatelessWidget { boxDecoration: containerWhiteDecoration, elevation: 0, borderRadius: 15, + isRequired: true, width: 450, onChanged: (value) { context @@ -61,10 +62,8 @@ class RoutineSearchAndButtons extends StatelessWidget { onPressed: () async { final result = await SettingHelper.showSettingDialog( - context: context, - isAutomation: context - .read() - .isAutomation); + context: context, + ); if (result != null) { context .read() diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index 5d71e405..d6c9500f 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -30,19 +30,25 @@ class ThenContainer extends StatelessWidget { fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 16), Wrap( - spacing: 8, - runSpacing: 8, - children: state.thenItems - .map((item) => DraggableCard( - // key: Key(item['key']!), - imagePath: item['imagePath']!, - title: item['title']!, - deviceData: item, - padding: EdgeInsets.symmetric( - horizontal: 4, vertical: 8), - )) - .toList(), - ), + spacing: 8, + runSpacing: 8, + children: List.generate( + state.thenItems.length, + (index) => DraggableCard( + imagePath: + state.thenItems[index]['imagePath'] ?? '', + title: state.thenItems[index]['title'] ?? '', + deviceData: state.thenItems[index], + padding: const EdgeInsets.symmetric( + horizontal: 4, vertical: 8), + isFromThen: true, + isFromIf: false, + onRemove: () { + context.read().add( + RemoveDragCard( + index: index, isFromThen: true)); + }, + ))), ], ), ), diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index b137982d..abf5099e 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/routiens/models/create_scene/create_scene_model.dart'; import 'package:syncrow_web/pages/routiens/models/icon_model.dart'; import 'package:syncrow_web/pages/routiens/models/routine_model.dart'; @@ -11,6 +12,7 @@ class SceneApi { static Future> createScene( CreateSceneModel createSceneModel) async { try { + debugPrint('create scene model: ${createSceneModel.toMap()}'); final response = await _httpService.post( path: ApiEndpoints.createScene, body: createSceneModel.toMap(), @@ -19,8 +21,10 @@ class SceneApi { return json; }, ); + debugPrint('create scene response: $response'); return response; } catch (e) { + debugPrint(e.toString()); rethrow; } } From 30db9cfc2a5eac77af526b90c7cbbd745a829807 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Mon, 25 Nov 2024 01:47:17 +0300 Subject: [PATCH 20/40] Added routines search field functionlity --- .../bloc/routine_bloc/routine_bloc.dart | 43 ++++------ .../bloc/routine_bloc/routine_state.dart | 82 +++++++++---------- .../routiens/widgets/routine_devices.dart | 49 +++++++---- .../widgets/routine_search_and_buttons.dart | 16 +--- .../widgets/scenes_and_automations.dart | 38 ++++++--- .../widgets/search_bar_condition_title.dart | 19 ++++- 6 files changed, 138 insertions(+), 109 deletions(-) diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 35a522b6..88414196 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -32,8 +32,7 @@ class RoutineBloc extends Bloc { } void _onAddToIfContainer(AddToIfContainer event, Emitter emit) { - final updatedIfItems = List>.from(state.ifItems) - ..add(event.item); + final updatedIfItems = List>.from(state.ifItems)..add(event.item); if (event.isTabToRun) { isTabToRun = true; isAutomation = false; @@ -44,15 +43,12 @@ class RoutineBloc extends Bloc { emit(state.copyWith(ifItems: updatedIfItems)); } - void _onAddToThenContainer( - AddToThenContainer event, Emitter emit) { - final updatedThenItems = List>.from(state.thenItems) - ..add(event.item); + void _onAddToThenContainer(AddToThenContainer event, Emitter emit) { + final updatedThenItems = List>.from(state.thenItems)..add(event.item); emit(state.copyWith(thenItems: updatedThenItems)); } - void _onAddFunctionsToRoutine( - AddFunctionToRoutine event, Emitter emit) { + void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter emit) { try { if (event.functions.isEmpty) return; @@ -61,11 +57,9 @@ class RoutineBloc extends Bloc { if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) { currentSelectedFunctions[event.uniqueCustomId] = - List.from(currentSelectedFunctions[event.uniqueCustomId]!) - ..addAll(event.functions); + List.from(currentSelectedFunctions[event.uniqueCustomId]!)..addAll(event.functions); } else { - currentSelectedFunctions[event.uniqueCustomId] = - List.from(event.functions); + currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions); } emit(state.copyWith(selectedFunctions: currentSelectedFunctions)); @@ -74,8 +68,7 @@ class RoutineBloc extends Bloc { } } - Future _onLoadScenes( - LoadScenes event, Emitter emit) async { + Future _onLoadScenes(LoadScenes event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { @@ -94,8 +87,7 @@ class RoutineBloc extends Bloc { } } - Future _onLoadAutomation( - LoadAutomation event, Emitter emit) async { + Future _onLoadAutomation(LoadAutomation event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { @@ -114,13 +106,13 @@ class RoutineBloc extends Bloc { } } - FutureOr _onSearchRoutines( - SearchRoutines event, Emitter emit) { - emit(state.copyWith(routineName: event.query)); + FutureOr _onSearchRoutines(SearchRoutines event, Emitter emit) async { + emit(state.copyWith(isLoading: true, errorMessage: null)); + await Future.delayed(const Duration(seconds: 1)); + emit(state.copyWith(searchText: event.query)); } - FutureOr _onAddSelectedIcon( - AddSelectedIcon event, Emitter emit) { + FutureOr _onAddSelectedIcon(AddSelectedIcon event, Emitter emit) { emit(state.copyWith(selectedIcon: event.icon)); } @@ -129,8 +121,7 @@ class RoutineBloc extends Bloc { return actions.first['deviceId'] == 'delay'; } - Future _onCreateScene( - CreateSceneEvent event, Emitter emit) async { + Future _onCreateScene(CreateSceneEvent event, Emitter emit) async { try { // Check if first action is delay if (_isFirstActionDelay(state.thenItems)) { @@ -145,8 +136,7 @@ class RoutineBloc extends Bloc { final actions = state.thenItems .map((item) { - final functions = - state.selectedFunctions[item['uniqueCustomId']] ?? []; + final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; if (functions.isEmpty) return null; final function = functions.first; @@ -201,8 +191,7 @@ class RoutineBloc extends Bloc { } } - FutureOr _onRemoveDragCard( - RemoveDragCard event, Emitter emit) { + FutureOr _onRemoveDragCard(RemoveDragCard event, Emitter emit) { if (event.isFromThen) { /// remove element from thenItems at specific index final thenItems = List>.from(state.thenItems); diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart index 784cd148..f057c2c0 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart @@ -13,50 +13,49 @@ class RoutineState extends Equatable { final String? loadAutomationErrorMessage; final String? routineName; final String? selectedIcon; + final String? searchText; - const RoutineState({ - this.ifItems = const [], - this.thenItems = const [], - this.availableCards = const [], - this.scenes = const [], - this.automations = const [], - this.selectedFunctions = const {}, - this.isLoading = false, - this.errorMessage, - this.routineName, - this.selectedIcon, - this.loadScenesErrorMessage, - this.loadAutomationErrorMessage, - }); + const RoutineState( + {this.ifItems = const [], + this.thenItems = const [], + this.availableCards = const [], + this.scenes = const [], + this.automations = const [], + this.selectedFunctions = const {}, + this.isLoading = false, + this.errorMessage, + this.routineName, + this.selectedIcon, + this.loadScenesErrorMessage, + this.loadAutomationErrorMessage, + this.searchText}); - RoutineState copyWith({ - List>? ifItems, - List>? thenItems, - List? scenes, - List? automations, - Map>? selectedFunctions, - bool? isLoading, - String? errorMessage, - String? routineName, - String? selectedIcon, - String? loadAutomationErrorMessage, - String? loadScenesErrorMessage, - }) { + RoutineState copyWith( + {List>? ifItems, + List>? thenItems, + List? scenes, + List? automations, + Map>? selectedFunctions, + bool? isLoading, + String? errorMessage, + String? routineName, + String? selectedIcon, + String? loadAutomationErrorMessage, + String? loadScenesErrorMessage, + String? searchText}) { return RoutineState( - ifItems: ifItems ?? this.ifItems, - thenItems: thenItems ?? this.thenItems, - scenes: scenes ?? this.scenes, - automations: automations ?? this.automations, - selectedFunctions: selectedFunctions ?? this.selectedFunctions, - isLoading: isLoading ?? this.isLoading, - errorMessage: errorMessage ?? this.errorMessage, - routineName: routineName ?? this.routineName, - selectedIcon: selectedIcon ?? this.selectedIcon, - loadScenesErrorMessage: - loadScenesErrorMessage ?? this.loadScenesErrorMessage, - loadAutomationErrorMessage: - loadAutomationErrorMessage ?? this.loadAutomationErrorMessage, - ); + ifItems: ifItems ?? this.ifItems, + thenItems: thenItems ?? this.thenItems, + scenes: scenes ?? this.scenes, + automations: automations ?? this.automations, + selectedFunctions: selectedFunctions ?? this.selectedFunctions, + isLoading: isLoading ?? this.isLoading, + errorMessage: errorMessage ?? this.errorMessage, + routineName: routineName ?? this.routineName, + selectedIcon: selectedIcon ?? this.selectedIcon, + loadScenesErrorMessage: loadScenesErrorMessage ?? this.loadScenesErrorMessage, + loadAutomationErrorMessage: loadAutomationErrorMessage ?? this.loadAutomationErrorMessage, + searchText: searchText ?? this.searchText); } @override @@ -72,5 +71,6 @@ class RoutineState extends Equatable { selectedIcon, loadScenesErrorMessage, loadAutomationErrorMessage, + searchText ]; } diff --git a/lib/pages/routiens/widgets/routine_devices.dart b/lib/pages/routiens/widgets/routine_devices.dart index 02c1b4fa..dbdf71f4 100644 --- a/lib/pages/routiens/widgets/routine_devices.dart +++ b/lib/pages/routiens/widgets/routine_devices.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; @@ -14,7 +15,7 @@ class RoutineDevices extends StatelessWidget { child: BlocBuilder( builder: (context, state) { if (state is DeviceManagementLoaded) { - final deviceList = state.devices + List deviceList = state.devices .where((device) => device.productType == 'AC' || device.productType == '1G' || @@ -30,19 +31,39 @@ class RoutineDevices extends StatelessWidget { runSpacing: 10, children: deviceList.asMap().entries.map((entry) { final device = entry.value; - return DraggableCard( - imagePath: device.getDefaultIcon(device.productType), - title: device.name ?? '', - deviceData: { - 'device': device, - 'imagePath': device.getDefaultIcon(device.productType), - 'title': device.name ?? '', - 'deviceId': device.uuid, - 'productType': device.productType, - 'functions': device.functions, - 'uniqueCustomId': '', - }, - ); + if (routineState.searchText != null && routineState.searchText!.isNotEmpty) { + return device.name! + .toLowerCase() + .contains(routineState.searchText!.toLowerCase()) + ? DraggableCard( + imagePath: device.getDefaultIcon(device.productType), + title: device.name ?? '', + deviceData: { + 'device': device, + 'imagePath': device.getDefaultIcon(device.productType), + 'title': device.name ?? '', + 'deviceId': device.uuid, + 'productType': device.productType, + 'functions': device.functions, + 'uniqueCustomId': '', + }, + ) + : Container(); + } else { + return DraggableCard( + imagePath: device.getDefaultIcon(device.productType), + title: device.name ?? '', + deviceData: { + 'device': device, + 'imagePath': device.getDefaultIcon(device.productType), + 'title': device.name ?? '', + 'deviceId': device.uuid, + 'productType': device.productType, + 'functions': device.functions, + 'uniqueCustomId': '', + }, + ); + } }).toList(), ); }, diff --git a/lib/pages/routiens/widgets/routine_search_and_buttons.dart b/lib/pages/routiens/widgets/routine_search_and_buttons.dart index fa0e2128..41a47c17 100644 --- a/lib/pages/routiens/widgets/routine_search_and_buttons.dart +++ b/lib/pages/routiens/widgets/routine_search_and_buttons.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/main.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; @@ -32,9 +31,7 @@ class RoutineSearchAndButtons extends StatelessWidget { children: [ ConstrainedBox( constraints: BoxConstraints( - maxWidth: constraints.maxWidth > 700 - ? 450 - : constraints.maxWidth - 32), + maxWidth: constraints.maxWidth > 700 ? 450 : constraints.maxWidth - 32), child: StatefulTextField( title: 'Routine Name', height: 40, @@ -46,9 +43,7 @@ class RoutineSearchAndButtons extends StatelessWidget { isRequired: true, width: 450, onChanged: (value) { - context - .read() - .add(SearchRoutines(value)); + // context.read().add(SearchRoutines(value)); }, ), ), @@ -60,14 +55,11 @@ class RoutineSearchAndButtons extends StatelessWidget { child: Center( child: DefaultButton( onPressed: () async { - final result = - await SettingHelper.showSettingDialog( + final result = await SettingHelper.showSettingDialog( context: context, ); if (result != null) { - context - .read() - .add(AddSelectedIcon(result)); + context.read().add(AddSelectedIcon(result)); } }, borderRadius: 15, diff --git a/lib/pages/routiens/widgets/scenes_and_automations.dart b/lib/pages/routiens/widgets/scenes_and_automations.dart index a74634ba..633f9a82 100644 --- a/lib/pages/routiens/widgets/scenes_and_automations.dart +++ b/lib/pages/routiens/widgets/scenes_and_automations.dart @@ -33,17 +33,33 @@ class _ScenesAndAutomationsState extends State { runSpacing: 10, children: scenes.asMap().entries.map((entry) { final scene = entry.value; - return DraggableCard( - imagePath: Assets.logo, - title: scene.name, - deviceData: { - 'deviceId': scene.id, - 'name': scene.name, - 'status': scene.status, - 'type': scene.type, - 'icon': scene.icon, - }, - ); + if (state.searchText != null && state.searchText!.isNotEmpty) { + return scene.name.toLowerCase().contains(state.searchText!.toLowerCase()) + ? DraggableCard( + imagePath: Assets.logo, + title: scene.name, + deviceData: { + 'deviceId': scene.id, + 'name': scene.name, + 'status': scene.status, + 'type': scene.type, + 'icon': scene.icon, + }, + ) + : Container(); + } else { + return DraggableCard( + imagePath: Assets.logo, + title: scene.name, + deviceData: { + 'deviceId': scene.id, + 'name': scene.name, + 'status': scene.status, + 'type': scene.type, + 'icon': scene.icon, + }, + ); + } }).toList(), ); } diff --git a/lib/pages/routiens/widgets/search_bar_condition_title.dart b/lib/pages/routiens/widgets/search_bar_condition_title.dart index e36ba873..c009f040 100644 --- a/lib/pages/routiens/widgets/search_bar_condition_title.dart +++ b/lib/pages/routiens/widgets/search_bar_condition_title.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/widgets/routines_title_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class ConditionTitleAndSearchBar extends StatelessWidget - with HelperResponsiveLayout { +class ConditionTitleAndSearchBar extends StatelessWidget with HelperResponsiveLayout { const ConditionTitleAndSearchBar({ super.key, }); @@ -34,7 +35,12 @@ class ConditionTitleAndSearchBar extends StatelessWidget borderRadius: BorderRadius.circular(15), ), controller: TextEditingController(), - onChanged: (value) {}, + // onSubmitted: (value) { + // context.read().add(SearchRoutines(value)); + // }, + onChanged: (value) { + context.read().add(SearchRoutines(value)); + }, ), ], ) @@ -57,7 +63,12 @@ class ConditionTitleAndSearchBar extends StatelessWidget borderRadius: BorderRadius.circular(15), ), controller: TextEditingController(), - onChanged: (value) {}, + // onSubmitted: (value) { + // context.read().add(SearchRoutines(value)); + // }, + onChanged: (value) { + context.read().add(SearchRoutines(value)); + }, ), ], ); From 19694bec986111f5359006c4ef59bf8fbf81b9e7 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 25 Nov 2024 10:08:50 +0300 Subject: [PATCH 21/40] push design and missing view and folder structure --- assets/icons/routine/automation.svg | 23 ++ .../common/text_field/custom_text_field.dart | 26 ++- .../dialog_helper/device_dialog_helper.dart | 8 +- .../helper/duration_format_helper.dart | 15 ++ lib/pages/routiens/models/ac/ac_function.dart | 4 +- lib/pages/routiens/models/routine_model.dart | 10 +- .../routiens/view/effective_period_view.dart | 2 +- .../conditions_routines_devices_view.dart | 26 +-- lib/pages/routiens/widgets/dragable_card.dart | 23 +- lib/pages/routiens/widgets/period_option.dart | 18 +- .../routine_dialogs/ac_dialog.dart} | 4 +- .../routine_dialogs/delay_dialog.dart} | 0 .../effictive_period_dialog.dart} | 0 .../one_gang_switch_dialog.dart} | 3 +- .../routine_dialogs/setting_dialog.dart} | 0 .../three_gang_switch_dialog.dart} | 3 +- .../two_gang_switch_dialog.dart} | 3 +- .../widgets/routine_search_and_buttons.dart | 13 +- .../widgets/scenes_and_automations.dart | 6 +- .../routiens/widgets/then_container.dart | 2 +- lib/services/routines_api.dart | 2 +- lib/utils/constants/assets.dart | 203 ++++++++++++------ pubspec.lock | 32 +++ 23 files changed, 310 insertions(+), 116 deletions(-) create mode 100644 assets/icons/routine/automation.svg create mode 100644 lib/pages/routiens/helper/duration_format_helper.dart rename lib/pages/routiens/{helper/ac_helper.dart => widgets/routine_dialogs/ac_dialog.dart} (99%) rename lib/pages/routiens/{helper/delay_helper.dart => widgets/routine_dialogs/delay_dialog.dart} (100%) rename lib/pages/routiens/{helper/effictive_period_helper.dart => widgets/routine_dialogs/effictive_period_dialog.dart} (100%) rename lib/pages/routiens/{helper/one_gang_switch_helper.dart => widgets/routine_dialogs/one_gang_switch_dialog.dart} (99%) rename lib/pages/routiens/{helper/setting_helper.dart => widgets/routine_dialogs/setting_dialog.dart} (100%) rename lib/pages/routiens/{helper/three_gang_switch_helper.dart => widgets/routine_dialogs/three_gang_switch_dialog.dart} (99%) rename lib/pages/routiens/{helper/two_gang_switch_helper.dart => widgets/routine_dialogs/two_gang_switch_dialog.dart} (99%) diff --git a/assets/icons/routine/automation.svg b/assets/icons/routine/automation.svg new file mode 100644 index 00000000..a67aadaf --- /dev/null +++ b/assets/icons/routine/automation.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/pages/common/text_field/custom_text_field.dart b/lib/pages/common/text_field/custom_text_field.dart index 3ba71cd1..bb574e74 100644 --- a/lib/pages/common/text_field/custom_text_field.dart +++ b/lib/pages/common/text_field/custom_text_field.dart @@ -109,13 +109,25 @@ class CustomTextField extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - Text( - title, - style: context.textTheme.bodyMedium!.copyWith( - fontSize: 13, - fontWeight: FontWeight.w600, - color: const Color(0xff000000), - ), + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Visibility( + visible: isRequired == true, + child: Text('* ', + style: context.textTheme.bodyMedium! + .copyWith(color: Colors.red, fontSize: 13)), + ), + Text( + title, + style: context.textTheme.bodyMedium!.copyWith( + fontSize: 13, + fontWeight: FontWeight.w600, + color: const Color(0xff000000), + ), + ), + ], ), const SizedBox(height: 8), Material( diff --git a/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart index fcb8e740..33cfffee 100644 --- a/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/helper/ac_helper.dart'; -import 'package:syncrow_web/pages/routiens/helper/one_gang_switch_helper.dart'; -import 'package:syncrow_web/pages/routiens/helper/three_gang_switch_helper.dart'; -import 'package:syncrow_web/pages/routiens/helper/two_gang_switch_helper.dart'; +import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/ac_dialog.dart'; +import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/one_gang_switch_dialog.dart'; +import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/three_gang_switch_dialog.dart'; +import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/two_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; class DeviceDialogHelper { diff --git a/lib/pages/routiens/helper/duration_format_helper.dart b/lib/pages/routiens/helper/duration_format_helper.dart new file mode 100644 index 00000000..1f22c9b2 --- /dev/null +++ b/lib/pages/routiens/helper/duration_format_helper.dart @@ -0,0 +1,15 @@ +class DurationFormatMixin { + static String formatDuration(int seconds) { + if (seconds >= 3600) { + final hours = (seconds / 3600).floor(); + final remainingMinutes = ((seconds % 3600) / 60).floor(); + final remainingSeconds = seconds % 60; + return '$hours h ${remainingMinutes}m ${remainingSeconds}s'; + } else if (seconds >= 60) { + final minutes = (seconds / 60).floor(); + final remainingSeconds = seconds % 60; + return '$minutes m ${remainingSeconds}s'; + } + return '${seconds}s'; + } +} diff --git a/lib/pages/routiens/models/ac/ac_function.dart b/lib/pages/routiens/models/ac/ac_function.dart index e5fa78ba..8feccda7 100644 --- a/lib/pages/routiens/models/ac/ac_function.dart +++ b/lib/pages/routiens/models/ac/ac_function.dart @@ -73,9 +73,9 @@ class TempSetFunction extends ACFunction { final int step; TempSetFunction({required super.deviceId, required super.deviceName}) - : min = 200, + : min = 160, max = 300, - step = 5, + step = 1, super( code: 'temp_set', operationName: 'Set Temperature', diff --git a/lib/pages/routiens/models/routine_model.dart b/lib/pages/routiens/models/routine_model.dart index 8035b044..e2075579 100644 --- a/lib/pages/routiens/models/routine_model.dart +++ b/lib/pages/routiens/models/routine_model.dart @@ -1,3 +1,5 @@ +import 'package:syncrow_web/utils/constants/assets.dart'; + class ScenesModel { final String id; final String name; @@ -13,12 +15,16 @@ class ScenesModel { this.icon, }); - factory ScenesModel.fromJson(Map json) => ScenesModel( + factory ScenesModel.fromJson(Map json, + {bool? isAutomation}) => + ScenesModel( id: json["id"], name: json["name"] ?? '', status: json["status"] ?? '', type: json["type"] ?? '', - icon: json["icon"] as String?, + icon: (isAutomation ?? false) + ? Assets.automation + : json["icon"] as String?, ); Map toJson() => { diff --git a/lib/pages/routiens/view/effective_period_view.dart b/lib/pages/routiens/view/effective_period_view.dart index 5bcd9ff0..d9d9bc2f 100644 --- a/lib/pages/routiens/view/effective_period_view.dart +++ b/lib/pages/routiens/view/effective_period_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart'; -import 'package:syncrow_web/pages/routiens/helper/effictive_period_helper.dart'; +import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/effictive_period_dialog.dart'; import 'package:syncrow_web/pages/routiens/widgets/period_option.dart'; import 'package:syncrow_web/pages/routiens/widgets/repeat_days.dart'; import 'package:syncrow_web/utils/color_manager.dart'; diff --git a/lib/pages/routiens/widgets/conditions_routines_devices_view.dart b/lib/pages/routiens/widgets/conditions_routines_devices_view.dart index 89b1f3f0..5cc31bf3 100644 --- a/lib/pages/routiens/widgets/conditions_routines_devices_view.dart +++ b/lib/pages/routiens/widgets/conditions_routines_devices_view.dart @@ -16,7 +16,7 @@ class ConditionsRoutinesDevicesView extends StatelessWidget { return BlocBuilder( builder: (context, state) { return const Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), + padding: EdgeInsets.symmetric(horizontal: 8.0), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -65,13 +65,13 @@ class ConditionsRoutinesDevicesView extends StatelessWidget { ), ], ), - const SizedBox(height: 10), - const TitleRoutine( + SizedBox(height: 10), + TitleRoutine( title: 'Conditions', subtitle: '(THEN)', ), - const SizedBox(height: 10), - const Wrap( + SizedBox(height: 10), + Wrap( spacing: 10, runSpacing: 10, children: [ @@ -96,20 +96,20 @@ class ConditionsRoutinesDevicesView extends StatelessWidget { ), ], ), - const SizedBox(height: 10), - const TitleRoutine( + SizedBox(height: 10), + TitleRoutine( title: 'Routines', subtitle: '(THEN)', ), - const SizedBox(height: 10), - const ScenesAndAutomations(), - const SizedBox(height: 10), - const TitleRoutine( + SizedBox(height: 10), + ScenesAndAutomations(), + SizedBox(height: 10), + TitleRoutine( title: 'Devices', subtitle: '', ), - const SizedBox(height: 10), - const RoutineDevices(), + SizedBox(height: 10), + RoutineDevices(), ], ), ), diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index ae66ce02..db8ad1c9 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -106,7 +106,7 @@ class DraggableCard extends StatelessWidget { children: [ Expanded( child: Text( - '${function.operationName}: ${function.value}', + '${function.operationName}: ${_formatFunctionValue(function)}', style: context.textTheme.bodySmall?.copyWith( fontSize: 9, color: ColorsManager.textGray, @@ -144,6 +144,27 @@ class DraggableCard extends StatelessWidget { ); } + String _formatFunctionValue(DeviceFunctionData function) { + if (function.functionCode == 'temp_set' || + function.functionCode == 'temp_current') { + return '${(function.value / 10).toStringAsFixed(0)}°C'; + } else if (function.functionCode.contains('countdown')) { + final seconds = function.value.toInt(); + if (seconds >= 3600) { + final hours = (seconds / 3600).floor(); + final remainingMinutes = ((seconds % 3600) / 60).floor(); + final remainingSeconds = seconds % 60; + return '$hours h ${remainingMinutes}m ${remainingSeconds}s'; + } else if (seconds >= 60) { + final minutes = (seconds / 60).floor(); + final remainingSeconds = seconds % 60; + return '$minutes m ${remainingSeconds}s'; + } + return '${seconds}s'; + } + return function.value.toString(); + } + Widget _buildGreyContainer() { return Container( height: 123, diff --git a/lib/pages/routiens/widgets/period_option.dart b/lib/pages/routiens/widgets/period_option.dart index acb69231..8a89e2f9 100644 --- a/lib/pages/routiens/widgets/period_option.dart +++ b/lib/pages/routiens/widgets/period_option.dart @@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart'; import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart'; -import 'package:syncrow_web/pages/routiens/helper/effictive_period_helper.dart'; +import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/effictive_period_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/app_enum.dart'; @@ -21,9 +21,12 @@ class PeriodOptions extends StatelessWidget { builder: (context, state) { return Column( children: [ - _buildRadioOption(context, EnumEffectivePeriodOptions.allDay, '24 Hours'), - _buildRadioOption(context, EnumEffectivePeriodOptions.daytime, 'Sunrise to Sunset'), - _buildRadioOption(context, EnumEffectivePeriodOptions.night, 'Sunset to Sunrise'), + _buildRadioOption( + context, EnumEffectivePeriodOptions.allDay, '24 Hours'), + _buildRadioOption(context, EnumEffectivePeriodOptions.daytime, + 'Sunrise to Sunset'), + _buildRadioOption( + context, EnumEffectivePeriodOptions.night, 'Sunset to Sunrise'), ListTile( contentPadding: EdgeInsets.zero, onTap: () => showCustomTimePicker(context), @@ -34,7 +37,8 @@ class PeriodOptions extends StatelessWidget { fontWeight: FontWeight.w400, fontSize: 14), ), - subtitle: state.customStartTime != null && state.customEndTime != null + subtitle: state.customStartTime != null && + state.customEndTime != null ? Text( '${"${state.customStartTime}"} - ${"${state.customEndTime}"}', style: Theme.of(context).textTheme.bodyMedium!.copyWith( @@ -79,7 +83,9 @@ class PeriodOptions extends StatelessWidget { subtitle: Text( subtitle, style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: ColorsManager.textPrimaryColor, fontWeight: FontWeight.w400, fontSize: 10), + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 10), ), trailing: Radio( value: value, diff --git a/lib/pages/routiens/helper/ac_helper.dart b/lib/pages/routiens/widgets/routine_dialogs/ac_dialog.dart similarity index 99% rename from lib/pages/routiens/helper/ac_helper.dart rename to lib/pages/routiens/widgets/routine_dialogs/ac_dialog.dart index 10606b69..e8b87a16 100644 --- a/lib/pages/routiens/helper/ac_helper.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/ac_dialog.dart @@ -327,11 +327,11 @@ class ACHelper { String selectCode, ) { return Slider( - value: initialValue is int ? initialValue.toDouble() : 200.0, + value: initialValue is int ? initialValue.toDouble() : 160.0, min: 160, max: 300, divisions: 14, - label: '${((initialValue ?? 200) / 10).toInt()}°C', + label: '${((initialValue ?? 160) / 10).toInt()}°C', onChanged: (value) { context.read().add( AddFunction( diff --git a/lib/pages/routiens/helper/delay_helper.dart b/lib/pages/routiens/widgets/routine_dialogs/delay_dialog.dart similarity index 100% rename from lib/pages/routiens/helper/delay_helper.dart rename to lib/pages/routiens/widgets/routine_dialogs/delay_dialog.dart diff --git a/lib/pages/routiens/helper/effictive_period_helper.dart b/lib/pages/routiens/widgets/routine_dialogs/effictive_period_dialog.dart similarity index 100% rename from lib/pages/routiens/helper/effictive_period_helper.dart rename to lib/pages/routiens/widgets/routine_dialogs/effictive_period_dialog.dart diff --git a/lib/pages/routiens/helper/one_gang_switch_helper.dart b/lib/pages/routiens/widgets/routine_dialogs/one_gang_switch_dialog.dart similarity index 99% rename from lib/pages/routiens/helper/one_gang_switch_helper.dart rename to lib/pages/routiens/widgets/routine_dialogs/one_gang_switch_dialog.dart index c7d5d35a..ad25c657 100644 --- a/lib/pages/routiens/helper/one_gang_switch_helper.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/one_gang_switch_dialog.dart @@ -4,6 +4,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/helper/duration_format_helper.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; @@ -277,7 +278,7 @@ class OneGangSwitchHelper { borderRadius: BorderRadius.circular(10), ), child: Text( - '${initialValue ?? 0} sec', + DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0), style: context.textTheme.headlineMedium!.copyWith( color: ColorsManager.primaryColorWithOpacity, ), diff --git a/lib/pages/routiens/helper/setting_helper.dart b/lib/pages/routiens/widgets/routine_dialogs/setting_dialog.dart similarity index 100% rename from lib/pages/routiens/helper/setting_helper.dart rename to lib/pages/routiens/widgets/routine_dialogs/setting_dialog.dart diff --git a/lib/pages/routiens/helper/three_gang_switch_helper.dart b/lib/pages/routiens/widgets/routine_dialogs/three_gang_switch_dialog.dart similarity index 99% rename from lib/pages/routiens/helper/three_gang_switch_helper.dart rename to lib/pages/routiens/widgets/routine_dialogs/three_gang_switch_dialog.dart index 4a4c5e02..4f0d85a7 100644 --- a/lib/pages/routiens/helper/three_gang_switch_helper.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/three_gang_switch_dialog.dart @@ -4,6 +4,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/helper/duration_format_helper.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; @@ -279,7 +280,7 @@ class ThreeGangSwitchHelper { borderRadius: BorderRadius.circular(10), ), child: Text( - '${initialValue ?? 0} sec', + DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0), style: context.textTheme.headlineMedium!.copyWith( color: ColorsManager.primaryColorWithOpacity, ), diff --git a/lib/pages/routiens/helper/two_gang_switch_helper.dart b/lib/pages/routiens/widgets/routine_dialogs/two_gang_switch_dialog.dart similarity index 99% rename from lib/pages/routiens/helper/two_gang_switch_helper.dart rename to lib/pages/routiens/widgets/routine_dialogs/two_gang_switch_dialog.dart index 3805882a..8282bc7b 100644 --- a/lib/pages/routiens/helper/two_gang_switch_helper.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/two_gang_switch_dialog.dart @@ -4,6 +4,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/helper/duration_format_helper.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; @@ -278,7 +279,7 @@ class TwoGangSwitchHelper { borderRadius: BorderRadius.circular(10), ), child: Text( - '${initialValue ?? 0} sec', + DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0), style: context.textTheme.headlineMedium!.copyWith( color: ColorsManager.primaryColorWithOpacity, ), diff --git a/lib/pages/routiens/widgets/routine_search_and_buttons.dart b/lib/pages/routiens/widgets/routine_search_and_buttons.dart index 41a47c17..01352bf8 100644 --- a/lib/pages/routiens/widgets/routine_search_and_buttons.dart +++ b/lib/pages/routiens/widgets/routine_search_and_buttons.dart @@ -4,7 +4,7 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/helper/save_routine_helper.dart'; -import 'package:syncrow_web/pages/routiens/helper/setting_helper.dart'; +import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/setting_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -31,7 +31,9 @@ class RoutineSearchAndButtons extends StatelessWidget { children: [ ConstrainedBox( constraints: BoxConstraints( - maxWidth: constraints.maxWidth > 700 ? 450 : constraints.maxWidth - 32), + maxWidth: constraints.maxWidth > 700 + ? 450 + : constraints.maxWidth - 32), child: StatefulTextField( title: 'Routine Name', height: 40, @@ -55,11 +57,14 @@ class RoutineSearchAndButtons extends StatelessWidget { child: Center( child: DefaultButton( onPressed: () async { - final result = await SettingHelper.showSettingDialog( + final result = + await SettingHelper.showSettingDialog( context: context, ); if (result != null) { - context.read().add(AddSelectedIcon(result)); + context + .read() + .add(AddSelectedIcon(result)); } }, borderRadius: 15, diff --git a/lib/pages/routiens/widgets/scenes_and_automations.dart b/lib/pages/routiens/widgets/scenes_and_automations.dart index 633f9a82..7a26979c 100644 --- a/lib/pages/routiens/widgets/scenes_and_automations.dart +++ b/lib/pages/routiens/widgets/scenes_and_automations.dart @@ -34,7 +34,9 @@ class _ScenesAndAutomationsState extends State { children: scenes.asMap().entries.map((entry) { final scene = entry.value; if (state.searchText != null && state.searchText!.isNotEmpty) { - return scene.name.toLowerCase().contains(state.searchText!.toLowerCase()) + return scene.name + .toLowerCase() + .contains(state.searchText!.toLowerCase()) ? DraggableCard( imagePath: Assets.logo, title: scene.name, @@ -49,7 +51,7 @@ class _ScenesAndAutomationsState extends State { : Container(); } else { return DraggableCard( - imagePath: Assets.logo, + imagePath: scene.icon ?? Assets.loginLogo, title: scene.name, deviceData: { 'deviceId': scene.id, diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index d6c9500f..2bc65b0e 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/helper/delay_helper.dart'; +import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/delay_dialog.dart'; import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index abf5099e..0ce106b2 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -93,7 +93,7 @@ class SceneApi { expectedResponseModel: (json) { List scenes = []; for (var scene in json) { - scenes.add(ScenesModel.fromJson(scene)); + scenes.add(ScenesModel.fromJson(scene, isAutomation: true)); } return scenes; }, diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 6c542017..cffaf472 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -13,10 +13,12 @@ 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 spaseManagementIcon = + "assets/images/spase_management_icon.svg"; static const String devicesIcon = "assets/images/devices_icon.svg"; static const String moveinIcon = "assets/images/movein_icon.svg"; static const String constructionIcon = "assets/images/construction_icon.svg"; @@ -29,13 +31,15 @@ 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"; static const String switchAlarmSound = "assets/icons/switch_alarm_sound.svg"; static const String resetOff = "assets/icons/reset_off.svg"; - static const String sensitivityOperationIcon = "assets/icons/sesitivity_operation_icon.svg"; + static const String sensitivityOperationIcon = + "assets/icons/sesitivity_operation_icon.svg"; static const String motionDetection = "assets/icons/motion_detection.svg"; static const String freezing = "assets/icons/freezing.svg"; static const String indicator = "assets/icons/indicator.svg"; @@ -56,35 +60,56 @@ class Assets { static const String celsiusDegrees = "assets/icons/celsius_degrees.svg"; static const String masterState = "assets/icons/master_state.svg"; static const String acPower = "assets/icons/ac_power.svg"; - static const String farDetectionFunction = "assets/icons/far_detection_function.svg"; + static const String farDetectionFunction = + "assets/icons/far_detection_function.svg"; static const String nobodyTime = "assets/icons/nobody_time.svg"; // Automation functions - static const String tempPasswordUnlock = "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 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 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 residualElectricity = "assets/icons/automation_functions/residual_electricity.svg"; - static const String hijackAlarm = "assets/icons/automation_functions/hijack_alarm.svg"; - static const String passwordUnlock = "assets/icons/automation_functions/password_unlock.svg"; - static const String remoteUnlockRequest = "assets/icons/automation_functions/remote_unlock_req.svg"; - static const String cardUnlock = "assets/icons/automation_functions/card_unlock.svg"; + static const String tempPasswordUnlock = + "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 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 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 residualElectricity = + "assets/icons/automation_functions/residual_electricity.svg"; + static const String hijackAlarm = + "assets/icons/automation_functions/hijack_alarm.svg"; + static const String passwordUnlock = + "assets/icons/automation_functions/password_unlock.svg"; + static const String remoteUnlockRequest = + "assets/icons/automation_functions/remote_unlock_req.svg"; + static const String cardUnlock = + "assets/icons/automation_functions/card_unlock.svg"; static const String motion = "assets/icons/automation_functions/motion.svg"; - static const String fingerprintUnlock = "assets/icons/automation_functions/fingerprint_unlock.svg"; + static const String fingerprintUnlock = + "assets/icons/automation_functions/fingerprint_unlock.svg"; // 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 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 lightPulp = "assets/icons/light_pulb.svg"; static const String acDevice = "assets/icons/ac_device.svg"; @@ -158,7 +183,8 @@ 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'; @@ -212,57 +238,100 @@ class Assets { static const String delay = 'assets/icons/routine/delay.svg'; // Assets for functions_icons - static const String assetsSensitivityFunction = "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 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 assetsAcCooling = "assets/icons/functions_icons/ac_cooling.svg"; - static const String assetsAcHeating = "assets/icons/functions_icons/ac_heating.svg"; - static const String assetsCelsiusDegrees = "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 assetsAcFanMiddle = "assets/icons/functions_icons/ac_fan_middle.svg"; - static const String assetsAcFanHigh = "assets/icons/functions_icons/ac_fan_high.svg"; - static const String assetsAcFanAuto = "assets/icons/functions_icons/ac_fan_auto.svg"; - static const String assetsSceneChildLock = "assets/icons/functions_icons/scene_child_lock.svg"; - static const String assetsSceneChildUnlock = "assets/icons/functions_icons/scene_child_unlock.svg"; - static const String assetsSceneRefresh = "assets/icons/functions_icons/scene_refresh.svg"; - static const String assetsLightCountdown = "assets/icons/functions_icons/light_countdown.svg"; - static const String assetsFarDetection = "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 assetsMotionDetection = "assets/icons/functions_icons/motion_detection.svg"; - static const String assetsMotionlessDetection = "assets/icons/functions_icons/motionless_detection.svg"; - static const String assetsNobodyTime = "assets/icons/functions_icons/nobody_time.svg"; - static const String assetsFactoryReset = "assets/icons/functions_icons/factory_reset.svg"; - static const String assetsMasterState = "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 assetsSensitivityFunction = + "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 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 assetsAcCooling = + "assets/icons/functions_icons/ac_cooling.svg"; + static const String assetsAcHeating = + "assets/icons/functions_icons/ac_heating.svg"; + static const String assetsCelsiusDegrees = + "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 assetsAcFanMiddle = + "assets/icons/functions_icons/ac_fan_middle.svg"; + static const String assetsAcFanHigh = + "assets/icons/functions_icons/ac_fan_high.svg"; + static const String assetsAcFanAuto = + "assets/icons/functions_icons/ac_fan_auto.svg"; + static const String assetsSceneChildLock = + "assets/icons/functions_icons/scene_child_lock.svg"; + static const String assetsSceneChildUnlock = + "assets/icons/functions_icons/scene_child_unlock.svg"; + static const String assetsSceneRefresh = + "assets/icons/functions_icons/scene_refresh.svg"; + static const String assetsLightCountdown = + "assets/icons/functions_icons/light_countdown.svg"; + static const String assetsFarDetection = + "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 assetsMotionDetection = + "assets/icons/functions_icons/motion_detection.svg"; + static const String assetsMotionlessDetection = + "assets/icons/functions_icons/motionless_detection.svg"; + static const String assetsNobodyTime = + "assets/icons/functions_icons/nobody_time.svg"; + static const String assetsFactoryReset = + "assets/icons/functions_icons/factory_reset.svg"; + static const String assetsMasterState = + "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"; // Assets for automation_functions - static const String assetsCardUnlock = "assets/icons/functions_icons/automation_functions/card_unlock.svg"; - static const String assetsDoorbell = "assets/icons/functions_icons/automation_functions/doorbell.svg"; + static const String assetsCardUnlock = + "assets/icons/functions_icons/automation_functions/card_unlock.svg"; + static const String assetsDoorbell = + "assets/icons/functions_icons/automation_functions/doorbell.svg"; static const String assetsDoorlockNormalOpen = "assets/icons/functions_icons/automation_functions/doorlock_normal_open.svg"; - static const String assetsDoubleLock = "assets/icons/functions_icons/automation_functions/double_lock.svg"; + static const String assetsDoubleLock = + "assets/icons/functions_icons/automation_functions/double_lock.svg"; static const String assetsFingerprintUnlock = "assets/icons/functions_icons/automation_functions/fingerprint_unlock.svg"; - static const String assetsHijackAlarm = "assets/icons/functions_icons/automation_functions/hijack_alarm.svg"; - static const String assetsLockAlarm = "assets/icons/functions_icons/automation_functions/lock_alarm.svg"; - static const String assetsPasswordUnlock = "assets/icons/functions_icons/automation_functions/password_unlock.svg"; - static const String assetsRemoteUnlockReq = "assets/icons/functions_icons/automation_functions/remote_unlock_req.svg"; + static const String assetsHijackAlarm = + "assets/icons/functions_icons/automation_functions/hijack_alarm.svg"; + static const String assetsLockAlarm = + "assets/icons/functions_icons/automation_functions/lock_alarm.svg"; + static const String assetsPasswordUnlock = + "assets/icons/functions_icons/automation_functions/password_unlock.svg"; + static const String assetsRemoteUnlockReq = + "assets/icons/functions_icons/automation_functions/remote_unlock_req.svg"; static const String assetsRemoteUnlockViaApp = "assets/icons/functions_icons/automation_functions/remote_unlock_via_app.svg"; static const String assetsResidualElectricity = "assets/icons/functions_icons/automation_functions/residual_electricity.svg"; static const String assetsTempPasswordUnlock = "assets/icons/functions_icons/automation_functions/temp_password_unlock.svg"; - static const String assetsSelfTestResult = "assets/icons/functions_icons/automation_functions/self_test_result.svg"; - static const String assetsPresence = "assets/icons/functions_icons/automation_functions/presence.svg"; - static const String assetsMotion = "assets/icons/functions_icons/automation_functions/motion.svg"; - static const String assetsCurrentTemp = "assets/icons/functions_icons/automation_functions/current_temp.svg"; - static const String assetsPresenceState = "assets/icons/functions_icons/automation_functions/presence_state.svg"; + static const String assetsSelfTestResult = + "assets/icons/functions_icons/automation_functions/self_test_result.svg"; + static const String assetsPresence = + "assets/icons/functions_icons/automation_functions/presence.svg"; + static const String assetsMotion = + "assets/icons/functions_icons/automation_functions/motion.svg"; + static const String assetsCurrentTemp = + "assets/icons/functions_icons/automation_functions/current_temp.svg"; + static const String assetsPresenceState = + "assets/icons/functions_icons/automation_functions/presence_state.svg"; + //assets/icons/routine/automation.svg + static const String automation = 'assets/icons/routine/automation.svg'; } diff --git a/pubspec.lock b/pubspec.lock index 3e12d6c5..cfd8b310 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -57,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" cupertino_icons: dependency: "direct main" description: @@ -137,6 +145,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" fl_chart: dependency: "direct main" description: @@ -533,6 +549,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -589,6 +613,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" vector_graphics: dependency: transitive description: From 8b7642971418dc4b9846e88eae567c79a2c3c316 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 25 Nov 2024 10:15:31 +0300 Subject: [PATCH 22/40] push devices managment screen --- .../view/device_managment_page.dart | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/lib/pages/device_managment/all_devices/view/device_managment_page.dart b/lib/pages/device_managment/all_devices/view/device_managment_page.dart index 4044c056..ffc57131 100644 --- a/lib/pages/device_managment/all_devices/view/device_managment_page.dart +++ b/lib/pages/device_managment/all_devices/view/device_managment_page.dart @@ -88,32 +88,31 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { ); }), rightBody: const NavigateHomeGridView(), - scaffoldBody: CreateNewRoutineView(), - // BlocBuilder( - // builder: (context, state) { - // if (state is SelectedTabState && state.selectedTab) { - // return const RoutinesView(); - // } - // if (state is ShowCreateRoutineState && state.showCreateRoutine) { - // return const CreateNewRoutineView(); - // } + scaffoldBody: BlocBuilder( + builder: (context, state) { + if (state is SelectedTabState && state.selectedTab) { + return const RoutinesView(); + } + if (state is ShowCreateRoutineState && state.showCreateRoutine) { + return const CreateNewRoutineView(); + } - // return BlocBuilder( - // builder: (context, deviceState) { - // if (deviceState is DeviceManagementLoading) { - // return const Center(child: CircularProgressIndicator()); - // } else if (deviceState is DeviceManagementLoaded || - // deviceState is DeviceManagementFiltered) { - // final devices = (deviceState as dynamic).devices ?? - // (deviceState as DeviceManagementFiltered).filteredDevices; + return BlocBuilder( + builder: (context, deviceState) { + if (deviceState is DeviceManagementLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (deviceState is DeviceManagementLoaded || + deviceState is DeviceManagementFiltered) { + final devices = (deviceState as dynamic).devices ?? + (deviceState as DeviceManagementFiltered).filteredDevices; - // return DeviceManagementBody(devices: devices); - // } else { - // return const Center(child: Text('Error fetching Devices')); - // } - // }, - // ); - // }), + return DeviceManagementBody(devices: devices); + } else { + return const Center(child: Text('Error fetching Devices')); + } + }, + ); + }), ), ); } From bbe00c0372f75ea591f472bb2c3ccd72d9fd81e6 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 25 Nov 2024 10:23:26 +0300 Subject: [PATCH 23/40] push 24 hour coundown --- .../widgets/routine_dialogs/one_gang_switch_dialog.dart | 2 +- .../widgets/routine_dialogs/three_gang_switch_dialog.dart | 2 +- .../widgets/routine_dialogs/two_gang_switch_dialog.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/routiens/widgets/routine_dialogs/one_gang_switch_dialog.dart b/lib/pages/routiens/widgets/routine_dialogs/one_gang_switch_dialog.dart index ad25c657..00f936d6 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/one_gang_switch_dialog.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/one_gang_switch_dialog.dart @@ -299,7 +299,7 @@ class OneGangSwitchHelper { description: "sec", value: 0.0, minValue: 0, - maxValue: 43200, + maxValue: 86400, stepValue: 1, ); return Slider( diff --git a/lib/pages/routiens/widgets/routine_dialogs/three_gang_switch_dialog.dart b/lib/pages/routiens/widgets/routine_dialogs/three_gang_switch_dialog.dart index 4f0d85a7..ae704ded 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/three_gang_switch_dialog.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/three_gang_switch_dialog.dart @@ -301,7 +301,7 @@ class ThreeGangSwitchHelper { description: "sec", value: 0.0, minValue: 0, - maxValue: 43200, + maxValue: 86400, stepValue: 1, ); return Slider( diff --git a/lib/pages/routiens/widgets/routine_dialogs/two_gang_switch_dialog.dart b/lib/pages/routiens/widgets/routine_dialogs/two_gang_switch_dialog.dart index 8282bc7b..d017103e 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/two_gang_switch_dialog.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/two_gang_switch_dialog.dart @@ -300,7 +300,7 @@ class TwoGangSwitchHelper { description: "sec", value: 0.0, minValue: 0, - maxValue: 43200, + maxValue: 86400, stepValue: 1, ); return Slider( From c84cfd75e42a585ae52021096ac4b40627b0352f Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Tue, 26 Nov 2024 00:43:35 +0300 Subject: [PATCH 24/40] Implemented update card functionlity --- .../bloc/routine_bloc/routine_bloc.dart | 15 +++- .../widgets/routine_dialogs/delay_dialog.dart | 19 ++++- .../widgets/scenes_and_automations.dart | 6 +- .../routiens/widgets/then_container.dart | 78 ++++++++++++------- 4 files changed, 82 insertions(+), 36 deletions(-) diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 88414196..053ad934 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -44,8 +44,19 @@ class RoutineBloc extends Bloc { } void _onAddToThenContainer(AddToThenContainer event, Emitter emit) { - final updatedThenItems = List>.from(state.thenItems)..add(event.item); - emit(state.copyWith(thenItems: updatedThenItems)); + final currentItems = List>.from(state.thenItems); + + // Find the index of the item in teh current itemsList + int index = + currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); + // Replace the map if the index is valid + if (index != -1) { + currentItems[index] = event.item; + } else { + currentItems.add(event.item); + } + + emit(state.copyWith(thenItems: currentItems)); } void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter emit) { diff --git a/lib/pages/routiens/widgets/routine_dialogs/delay_dialog.dart b/lib/pages/routiens/widgets/routine_dialogs/delay_dialog.dart index 1191ac58..6195a931 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/delay_dialog.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/delay_dialog.dart @@ -8,13 +8,25 @@ import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; class DelayHelper { static Future?> showDelayPickerDialog( - BuildContext context, String uniqueCustomId) async { + BuildContext context, Map data) async { int hours = 0; int minutes = 0; return showDialog?>( context: context, builder: (BuildContext context) { + final routineBloc = context.read(); + int totalSec = 0; + + final selectedFunctionData = + routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? []; + + if (selectedFunctionData.isNotEmpty) { + totalSec = selectedFunctionData[0].value; + // Convert seconds to hours and minutes + hours = totalSec ~/ 3600; + minutes = (totalSec % 3600) ~/ 60; + } return AlertDialog( contentPadding: EdgeInsets.zero, content: Container( @@ -31,8 +43,7 @@ class DelayHelper { Expanded( child: CupertinoTimerPicker( mode: CupertinoTimerPickerMode.hm, - initialTimerDuration: - Duration(hours: hours, minutes: minutes), + initialTimerDuration: Duration(hours: hours, minutes: minutes), onTimerDurationChanged: (Duration newDuration) { hours = newDuration.inHours; minutes = newDuration.inMinutes % 60; @@ -54,7 +65,7 @@ class DelayHelper { value: totalSeconds, ) ], - uniqueCustomId, + data['uniqueCustomId'], )); Navigator.pop(context, { diff --git a/lib/pages/routiens/widgets/scenes_and_automations.dart b/lib/pages/routiens/widgets/scenes_and_automations.dart index 7a26979c..746795af 100644 --- a/lib/pages/routiens/widgets/scenes_and_automations.dart +++ b/lib/pages/routiens/widgets/scenes_and_automations.dart @@ -34,11 +34,9 @@ class _ScenesAndAutomationsState extends State { children: scenes.asMap().entries.map((entry) { final scene = entry.value; if (state.searchText != null && state.searchText!.isNotEmpty) { - return scene.name - .toLowerCase() - .contains(state.searchText!.toLowerCase()) + return scene.name.toLowerCase().contains(state.searchText!.toLowerCase()) ? DraggableCard( - imagePath: Assets.logo, + imagePath: scene.icon ?? Assets.loginLogo, title: scene.name, deviceData: { 'deviceId': scene.id, diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index 2bc65b0e..477236fe 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -25,45 +25,73 @@ class ThenContainer extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('THEN', - style: TextStyle( - fontSize: 18, fontWeight: FontWeight.bold)), + const Text('THEN', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 16), Wrap( spacing: 8, runSpacing: 8, children: List.generate( state.thenItems.length, - (index) => DraggableCard( - imagePath: - state.thenItems[index]['imagePath'] ?? '', - title: state.thenItems[index]['title'] ?? '', - deviceData: state.thenItems[index], - padding: const EdgeInsets.symmetric( - horizontal: 4, vertical: 8), - isFromThen: true, - isFromIf: false, - onRemove: () { - context.read().add( - RemoveDragCard( - index: index, isFromThen: true)); + (index) => GestureDetector( + onTap: () async { + if (state.thenItems[index]['deviceId'] == 'delay') { + final result = await DelayHelper.showDelayPickerDialog( + context, state.thenItems[index]); + + if (result != null) { + context.read().add(AddToThenContainer({ + ...state.thenItems[index], + 'imagePath': Assets.delay, + 'title': 'Delay', + })); + } + return; + } + + final result = await DeviceDialogHelper.showDeviceDialog( + context, state.thenItems[index]); + + if (result != null) { + context + .read() + .add(AddToThenContainer(state.thenItems[index])); + } else if (!['AC', '1G', '2G', '3G'] + .contains(state.thenItems[index]['productType'])) { + context + .read() + .add(AddToThenContainer(state.thenItems[index])); + } }, + child: DraggableCard( + imagePath: state.thenItems[index]['imagePath'] ?? '', + title: state.thenItems[index]['title'] ?? '', + deviceData: state.thenItems[index], + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), + isFromThen: true, + isFromIf: false, + onRemove: () { + context + .read() + .add(RemoveDragCard(index: index, isFromThen: true)); + }, + ), ))), ], ), ), ); }, - onWillAccept: (data) => data != null, - onAccept: (data) async { + // onMove: (details) { + // uniqueCustomId = const Uuid().v4(); + // }, + // onWillAccept: (data) => data != null, + onAcceptWithDetails: (data) async { final uniqueCustomId = const Uuid().v4(); - - final mutableData = Map.from(data); + final mutableData = Map.from(data.data); mutableData['uniqueCustomId'] = uniqueCustomId; if (mutableData['deviceId'] == 'delay') { - final result = await DelayHelper.showDelayPickerDialog( - context, mutableData['uniqueCustomId']); + final result = await DelayHelper.showDelayPickerDialog(context, mutableData); if (result != null) { context.read().add(AddToThenContainer({ @@ -75,13 +103,11 @@ class ThenContainer extends StatelessWidget { return; } - final result = - await DeviceDialogHelper.showDeviceDialog(context, mutableData); + final result = await DeviceDialogHelper.showDeviceDialog(context, mutableData); if (result != null) { context.read().add(AddToThenContainer(mutableData)); - } else if (!['AC', '1G', '2G', '3G'] - .contains(mutableData['productType'])) { + } else if (!['AC', '1G', '2G', '3G'].contains(mutableData['productType'])) { context.read().add(AddToThenContainer(mutableData)); } }, From 31278db4379a4cd4260c9d9a12f5f42a8daec0b4 Mon Sep 17 00:00:00 2001 From: ashraf_personal Date: Tue, 26 Nov 2024 00:45:16 +0300 Subject: [PATCH 25/40] working on automation --- .../bloc/routine_bloc/routine_bloc.dart | 60 ++++++--- .../bloc/routine_bloc/routine_event.dart | 7 ++ .../bloc/routine_bloc/routine_state.dart | 57 ++++++--- .../routiens/helper/save_routine_helper.dart | 2 +- lib/pages/routiens/widgets/if_container.dart | 119 +++++++++++++----- .../routine_dialogs/automation_dialog.dart | 93 ++++++++++++++ .../routine_dialogs/setting_dialog.dart | 2 +- .../routiens/widgets/then_container.dart | 38 +++++- 8 files changed, 307 insertions(+), 71 deletions(-) create mode 100644 lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 88414196..076c6cfd 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -14,8 +14,8 @@ part 'routine_state.dart'; const spaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6'; class RoutineBloc extends Bloc { - bool isAutomation = false; - bool isTabToRun = false; + // bool isAutomation = false; + // bool isTabToRun = false; RoutineBloc() : super(const RoutineState()) { on(_onAddToIfContainer); @@ -27,28 +27,32 @@ class RoutineBloc extends Bloc { on(_onAddSelectedIcon); on(_onCreateScene); on(_onRemoveDragCard); + on(_changeOperatorOperator); // on(_onRemoveFunction); // on(_onClearFunctions); } void _onAddToIfContainer(AddToIfContainer event, Emitter emit) { - final updatedIfItems = List>.from(state.ifItems)..add(event.item); + final updatedIfItems = List>.from(state.ifItems) + ..add(event.item); if (event.isTabToRun) { - isTabToRun = true; - isAutomation = false; + emit(state.copyWith( + ifItems: updatedIfItems, isTabToRun: true, isAutomation: false)); } else { - isTabToRun = false; - isAutomation = true; + emit(state.copyWith( + ifItems: updatedIfItems, isTabToRun: false, isAutomation: true)); } - emit(state.copyWith(ifItems: updatedIfItems)); } - void _onAddToThenContainer(AddToThenContainer event, Emitter emit) { - final updatedThenItems = List>.from(state.thenItems)..add(event.item); + void _onAddToThenContainer( + AddToThenContainer event, Emitter emit) { + final updatedThenItems = List>.from(state.thenItems) + ..add(event.item); emit(state.copyWith(thenItems: updatedThenItems)); } - void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter emit) { + void _onAddFunctionsToRoutine( + AddFunctionToRoutine event, Emitter emit) { try { if (event.functions.isEmpty) return; @@ -57,9 +61,11 @@ class RoutineBloc extends Bloc { if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) { currentSelectedFunctions[event.uniqueCustomId] = - List.from(currentSelectedFunctions[event.uniqueCustomId]!)..addAll(event.functions); + List.from(currentSelectedFunctions[event.uniqueCustomId]!) + ..addAll(event.functions); } else { - currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions); + currentSelectedFunctions[event.uniqueCustomId] = + List.from(event.functions); } emit(state.copyWith(selectedFunctions: currentSelectedFunctions)); @@ -68,7 +74,8 @@ class RoutineBloc extends Bloc { } } - Future _onLoadScenes(LoadScenes event, Emitter emit) async { + Future _onLoadScenes( + LoadScenes event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { @@ -87,7 +94,8 @@ class RoutineBloc extends Bloc { } } - Future _onLoadAutomation(LoadAutomation event, Emitter emit) async { + Future _onLoadAutomation( + LoadAutomation event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { @@ -106,13 +114,15 @@ class RoutineBloc extends Bloc { } } - FutureOr _onSearchRoutines(SearchRoutines event, Emitter emit) async { + FutureOr _onSearchRoutines( + SearchRoutines event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); await Future.delayed(const Duration(seconds: 1)); emit(state.copyWith(searchText: event.query)); } - FutureOr _onAddSelectedIcon(AddSelectedIcon event, Emitter emit) { + FutureOr _onAddSelectedIcon( + AddSelectedIcon event, Emitter emit) { emit(state.copyWith(selectedIcon: event.icon)); } @@ -121,7 +131,8 @@ class RoutineBloc extends Bloc { return actions.first['deviceId'] == 'delay'; } - Future _onCreateScene(CreateSceneEvent event, Emitter emit) async { + Future _onCreateScene( + CreateSceneEvent event, Emitter emit) async { try { // Check if first action is delay if (_isFirstActionDelay(state.thenItems)) { @@ -136,7 +147,8 @@ class RoutineBloc extends Bloc { final actions = state.thenItems .map((item) { - final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; + final functions = + state.selectedFunctions[item['uniqueCustomId']] ?? []; if (functions.isEmpty) return null; final function = functions.first; @@ -191,7 +203,8 @@ class RoutineBloc extends Bloc { } } - FutureOr _onRemoveDragCard(RemoveDragCard event, Emitter emit) { + FutureOr _onRemoveDragCard( + RemoveDragCard event, Emitter emit) { if (event.isFromThen) { /// remove element from thenItems at specific index final thenItems = List>.from(state.thenItems); @@ -203,4 +216,11 @@ class RoutineBloc extends Bloc { emit(state.copyWith(ifItems: ifItems)); } } + + FutureOr _changeOperatorOperator( + ChangeAutomationOperator event, Emitter emit) { + emit(state.copyWith( + selectedAutomationOperator: event.operator, + )); + } } diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart index 9e154cf4..38fd5e30 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart @@ -87,4 +87,11 @@ class RemoveDragCard extends RoutineEvent { List get props => [index]; } +class ChangeAutomationOperator extends RoutineEvent { + final String operator; + const ChangeAutomationOperator({required this.operator}); + @override + List get props => [operator]; +} + class ClearFunctions extends RoutineEvent {} diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart index f057c2c0..8f4da16b 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart @@ -14,21 +14,28 @@ class RoutineState extends Equatable { final String? routineName; final String? selectedIcon; final String? searchText; + final bool isTabToRun; + final bool isAutomation; + final String selectedAutomationOperator; - const RoutineState( - {this.ifItems = const [], - this.thenItems = const [], - this.availableCards = const [], - this.scenes = const [], - this.automations = const [], - this.selectedFunctions = const {}, - this.isLoading = false, - this.errorMessage, - this.routineName, - this.selectedIcon, - this.loadScenesErrorMessage, - this.loadAutomationErrorMessage, - this.searchText}); + const RoutineState({ + this.ifItems = const [], + this.thenItems = const [], + this.availableCards = const [], + this.scenes = const [], + this.automations = const [], + this.selectedFunctions = const {}, + this.isLoading = false, + this.errorMessage, + this.routineName, + this.selectedIcon, + this.loadScenesErrorMessage, + this.loadAutomationErrorMessage, + this.searchText, + this.isTabToRun = false, + this.isAutomation = false, + this.selectedAutomationOperator = 'or', + }); RoutineState copyWith( {List>? ifItems, @@ -42,7 +49,10 @@ class RoutineState extends Equatable { String? selectedIcon, String? loadAutomationErrorMessage, String? loadScenesErrorMessage, - String? searchText}) { + String? searchText, + bool? isTabToRun, + bool? isAutomation, + String? selectedAutomationOperator}) { return RoutineState( ifItems: ifItems ?? this.ifItems, thenItems: thenItems ?? this.thenItems, @@ -53,9 +63,15 @@ class RoutineState extends Equatable { errorMessage: errorMessage ?? this.errorMessage, routineName: routineName ?? this.routineName, selectedIcon: selectedIcon ?? this.selectedIcon, - loadScenesErrorMessage: loadScenesErrorMessage ?? this.loadScenesErrorMessage, - loadAutomationErrorMessage: loadAutomationErrorMessage ?? this.loadAutomationErrorMessage, - searchText: searchText ?? this.searchText); + loadScenesErrorMessage: + loadScenesErrorMessage ?? this.loadScenesErrorMessage, + loadAutomationErrorMessage: + loadAutomationErrorMessage ?? this.loadAutomationErrorMessage, + searchText: searchText ?? this.searchText, + isTabToRun: isTabToRun ?? this.isTabToRun, + isAutomation: isAutomation ?? this.isAutomation, + selectedAutomationOperator: + selectedAutomationOperator ?? this.selectedAutomationOperator); } @override @@ -71,6 +87,9 @@ class RoutineState extends Equatable { selectedIcon, loadScenesErrorMessage, loadAutomationErrorMessage, - searchText + searchText, + isTabToRun, + isAutomation, + selectedAutomationOperator ]; } diff --git a/lib/pages/routiens/helper/save_routine_helper.dart b/lib/pages/routiens/helper/save_routine_helper.dart index 69329992..7652554f 100644 --- a/lib/pages/routiens/helper/save_routine_helper.dart +++ b/lib/pages/routiens/helper/save_routine_helper.dart @@ -43,7 +43,7 @@ class SaveRoutineHelper { ), ), const SizedBox(height: 8), - if (context.read().isTabToRun) + if (state.isTabToRun) ListTile( leading: SvgPicture.asset( Assets.tabToRun, diff --git a/lib/pages/routiens/widgets/if_container.dart b/lib/pages/routiens/widgets/if_container.dart index cfb1b27c..a23772d6 100644 --- a/lib/pages/routiens/widgets/if_container.dart +++ b/lib/pages/routiens/widgets/if_container.dart @@ -3,7 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:uuid/uuid.dart'; class IfContainer extends StatelessWidget { @@ -21,11 +23,19 @@ class IfContainer extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('IF', - style: - TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('IF', + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold)), + if (state.isAutomation) + AutomationOperatorSelector( + selectedOperator: state.selectedAutomationOperator), + ], + ), const SizedBox(height: 16), - if (context.read().isTabToRun) + if (state.isTabToRun) const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -36,7 +46,7 @@ class IfContainer extends StatelessWidget { ), ], ), - if (!context.read().isTabToRun) + if (!state.isTabToRun) Wrap( spacing: 8, runSpacing: 8, @@ -64,35 +74,12 @@ class IfContainer extends StatelessWidget { }, onWillAccept: (data) => data != null, onAccept: (data) async { - // final uniqueCustomId = const Uuid().v4(); - - // final mutableData = Map.from(data); - // mutableData['uniqueCustomId'] = uniqueCustomId; - - // if (!context.read().isTabToRun) { - // if (data['deviceId'] == 'tab_to_run') { - // context.read().add(AddToIfContainer(data, true)); - // } else { - // final result = - // await DeviceDialogHelper.showDeviceDialog(context, mutableData); - // if (result != null) { - // context - // .read() - // .add(AddToIfContainer(mutableData, false)); - // } else if (!['AC', '1G', '2G', '3G'] - // .contains(data['productType'])) { - // context - // .read() - // .add(AddToIfContainer(mutableData, false)); - // } - // } - //} final uniqueCustomId = const Uuid().v4(); final mutableData = Map.from(data); mutableData['uniqueCustomId'] = uniqueCustomId; - if (!context.read().isTabToRun) { + if (!state.isTabToRun) { if (mutableData['deviceId'] == 'tab_to_run') { context .read() @@ -119,3 +106,77 @@ class IfContainer extends StatelessWidget { ); } } + +class AutomationOperatorSelector extends StatelessWidget { + const AutomationOperatorSelector({ + super.key, + required this.selectedOperator, + }); + + final String selectedOperator; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + border: Border.all(color: ColorsManager.dividerColor), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + TextButton( + style: TextButton.styleFrom( + backgroundColor: selectedOperator == 'and' + ? ColorsManager.dialogBlueTitle + : ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), + ), + ), + child: Text( + 'All condition is met', + style: context.textTheme.bodyMedium?.copyWith( + color: selectedOperator == 'and' + ? ColorsManager.whiteColors + : ColorsManager.blackColor, + ), + ), + onPressed: () { + context + .read() + .add(const ChangeAutomationOperator(operator: 'and')); + }, + ), + Container( + width: 3, + height: 24, + color: ColorsManager.dividerColor, + ), + TextButton( + style: TextButton.styleFrom( + backgroundColor: selectedOperator == 'or' + ? ColorsManager.dialogBlueTitle + : ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), + ), + ), + child: Text( + 'Any condition is met', + style: context.textTheme.bodyMedium?.copyWith( + color: selectedOperator == 'or' + ? ColorsManager.whiteColors + : ColorsManager.blackColor, + ), + ), + onPressed: () { + context + .read() + .add(const ChangeAutomationOperator(operator: 'or')); + }, + ), + ], + ), + ); + } +} diff --git a/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart b/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart new file mode 100644 index 00000000..a789aeb6 --- /dev/null +++ b/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class AutomationDialog extends StatefulWidget { + final String automationName; + final String automationId; + + const AutomationDialog({ + Key? key, + required this.automationName, + required this.automationId, + }) : super(key: key); + + @override + _AutomationDialogState createState() => _AutomationDialogState(); +} + +class _AutomationDialogState extends State { + bool _isEnabled = true; + + @override + Widget build(BuildContext context) { + return Dialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Container( + width: 400, + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + DialogHeader(widget.automationName), + const SizedBox(height: 16), + ListTile( + leading: SvgPicture.asset(Assets.acPower, width: 24, height: 24), + title: const Text('Enable'), + trailing: Radio( + value: true, + groupValue: _isEnabled, + onChanged: (bool? value) { + setState(() { + _isEnabled = value!; + }); + }, + ), + ), + ListTile( + leading: + SvgPicture.asset(Assets.acPowerOff, width: 24, height: 24), + title: const Text('Disable'), + trailing: Radio( + value: false, + groupValue: _isEnabled, + onChanged: (bool? value) { + setState(() { + _isEnabled = value!; + }); + }, + ), + ), + const SizedBox(height: 16), + DialogFooter( + onConfirm: () { + context.read().add( + AddFunctionToRoutine( + [ + DeviceFunctionData( + entityId: widget.automationId, + functionCode: '', + value: _isEnabled, + operationName: 'Automation', + ), + ], + widget.automationId, + ), + ); + Navigator.of(context).pop(true); + }, + onCancel: () => Navigator.of(context).pop(false), + isConfirmEnabled: true, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/routiens/widgets/routine_dialogs/setting_dialog.dart b/lib/pages/routiens/widgets/routine_dialogs/setting_dialog.dart index 621a5219..4e2bdde6 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/setting_dialog.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/setting_dialog.dart @@ -18,7 +18,7 @@ class SettingHelper { return showDialog( context: context, builder: (BuildContext context) { - final isAutomation = context.read().isAutomation; + final isAutomation = context.read().state.isAutomation; return BlocProvider( create: (_) => SettingBloc()..add(InitialEvent(selectedIcon: iconId ?? '')), diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index 2bc65b0e..e7d01e07 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/automation_dialog.dart'; import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/delay_dialog.dart'; import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; @@ -54,13 +55,48 @@ class ThenContainer extends StatelessWidget { ), ); }, - onWillAccept: (data) => data != null, + onWillAcceptWithDetails: (data) { + if (data == null) return false; + + if (state.isTabToRun) { + return data.data['type'] == 'automation'; + } + + if (state.isAutomation) { + return data.data['type'] == 'scene' || + data.data['type'] == 'automation'; + } + + return data.data['deviceId'] != null; + }, onAccept: (data) async { final uniqueCustomId = const Uuid().v4(); final mutableData = Map.from(data); mutableData['uniqueCustomId'] = uniqueCustomId; + if (mutableData['type'] == 'scene') { + context.read().add(AddToThenContainer(mutableData)); + return; + } + + if (mutableData['type'] == 'automation') { + final result = await showDialog( + context: context, + builder: (BuildContext context) => AutomationDialog( + automationName: mutableData['name'] ?? 'Automation', + automationId: mutableData['deviceId'] ?? '', + ), + ); + + if (result != null) { + context + .read() + .add(AddToThenContainer(mutableData)); + } + return; + } + if (mutableData['deviceId'] == 'delay') { final result = await DelayHelper.showDelayPickerDialog( context, mutableData['uniqueCustomId']); From 7ea628af921220fa210370bc3b4ed2a98a11534b Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Tue, 26 Nov 2024 11:34:40 +0300 Subject: [PATCH 26/40] push connecting to automation api --- .../view/device_managment_page.dart | 47 ++--- .../effective_period/effect_period_bloc.dart | 29 +-- .../bloc/routine_bloc/routine_bloc.dart | 121 +++++++++++- .../bloc/routine_bloc/routine_event.dart | 21 +++ .../bloc/routine_bloc/routine_state.dart | 77 ++++---- .../routiens/helper/save_routine_helper.dart | 29 +++ .../create_automation_model.dart | 173 ++++++++++++++++++ .../create_scene_model.dart | 0 lib/pages/routiens/widgets/dialog_footer.dart | 41 ++--- lib/pages/routiens/widgets/if_container.dart | 56 +++--- .../routine_dialogs/automation_dialog.dart | 12 +- .../routiens/widgets/then_container.dart | 32 ++-- lib/services/routines_api.dart | 37 ++-- lib/utils/constants/api_const.dart | 1 + 14 files changed, 517 insertions(+), 159 deletions(-) create mode 100644 lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart rename lib/pages/routiens/models/{create_scene => create_scene_and_autoamtion}/create_scene_model.dart (100%) diff --git a/lib/pages/device_managment/all_devices/view/device_managment_page.dart b/lib/pages/device_managment/all_devices/view/device_managment_page.dart index ffc57131..966d5361 100644 --- a/lib/pages/device_managment/all_devices/view/device_managment_page.dart +++ b/lib/pages/device_managment/all_devices/view/device_managment_page.dart @@ -88,31 +88,32 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { ); }), rightBody: const NavigateHomeGridView(), - scaffoldBody: BlocBuilder( - builder: (context, state) { - if (state is SelectedTabState && state.selectedTab) { - return const RoutinesView(); - } - if (state is ShowCreateRoutineState && state.showCreateRoutine) { - return const CreateNewRoutineView(); - } + scaffoldBody: CreateNewRoutineView(), + // BlocBuilder( + // builder: (context, state) { + // if (state is SelectedTabState && state.selectedTab) { + // return const RoutinesView(); + // } + // if (state is ShowCreateRoutineState && state.showCreateRoutine) { + // return const CreateNewRoutineView(); + // } - return BlocBuilder( - builder: (context, deviceState) { - if (deviceState is DeviceManagementLoading) { - return const Center(child: CircularProgressIndicator()); - } else if (deviceState is DeviceManagementLoaded || - deviceState is DeviceManagementFiltered) { - final devices = (deviceState as dynamic).devices ?? - (deviceState as DeviceManagementFiltered).filteredDevices; + // return BlocBuilder( + // builder: (context, deviceState) { + // if (deviceState is DeviceManagementLoading) { + // return const Center(child: CircularProgressIndicator()); + // } else if (deviceState is DeviceManagementLoaded || + // deviceState is DeviceManagementFiltered) { + // final devices = (deviceState as dynamic).devices ?? + // (deviceState as DeviceManagementFiltered).filteredDevices; - return DeviceManagementBody(devices: devices); - } else { - return const Center(child: Text('Error fetching Devices')); - } - }, - ); - }), + // return DeviceManagementBody(devices: devices); + // } else { + // return const Center(child: Text('Error fetching Devices')); + // } + // }, + // ); + // }), ), ); } diff --git a/lib/pages/routiens/bloc/effective_period/effect_period_bloc.dart b/lib/pages/routiens/bloc/effective_period/effect_period_bloc.dart index 05f63123..b5a293a1 100644 --- a/lib/pages/routiens/bloc/effective_period/effect_period_bloc.dart +++ b/lib/pages/routiens/bloc/effective_period/effect_period_bloc.dart @@ -2,7 +2,10 @@ import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart'; import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart'; import 'package:syncrow_web/utils/constants/app_enum.dart'; +import 'package:syncrow_web/utils/navigation_service.dart'; class EffectPeriodBloc extends Bloc { final daysMap = { @@ -49,9 +52,9 @@ class EffectPeriodBloc extends Bloc { break; } - // BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( - // EffectiveTimePeriodEvent( - // EffectiveTime(start: startTime, end: endTime, loops: state.selectedDaysBinary))); + BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( + EffectiveTimePeriodEvent( + EffectiveTime(start: startTime, end: endTime, loops: state.selectedDaysBinary))); emit(state.copyWith( selectedPeriod: event.period, customStartTime: startTime, customEndTime: endTime)); @@ -68,11 +71,11 @@ class EffectPeriodBloc extends Bloc { final newDaysBinary = daysList.join(); emit(state.copyWith(selectedDaysBinary: newDaysBinary)); - // BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( - // EffectiveTimePeriodEvent(EffectiveTime( - // start: state.customStartTime ?? '00:00', - // end: state.customEndTime ?? '23:59', - // loops: newDaysBinary))); + BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( + EffectiveTimePeriodEvent(EffectiveTime( + start: state.customStartTime ?? '00:00', + end: state.customEndTime ?? '23:59', + loops: newDaysBinary))); } void _onSetCustomTime(SetCustomTime event, Emitter emit) { @@ -94,9 +97,9 @@ class EffectPeriodBloc extends Bloc { emit( state.copyWith(customStartTime: startTime, customEndTime: endTime, selectedPeriod: period)); - // BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( - // EffectiveTimePeriodEvent( - // EffectiveTime(start: startTime, end: endTime, loops: state.selectedDaysBinary))); + BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( + EffectiveTimePeriodEvent( + EffectiveTime(start: startTime, end: endTime, loops: state.selectedDaysBinary))); } void _onResetEffectivePeriod(ResetEffectivePeriod event, Emitter emit) { @@ -106,8 +109,8 @@ class EffectPeriodBloc extends Bloc { customEndTime: '23:59', selectedDaysBinary: '1111111')); - // BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( - // EffectiveTimePeriodEvent(EffectiveTime(start: '00:00', end: '23:59', loops: '1111111'))); + BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( + EffectiveTimePeriodEvent(EffectiveTime(start: '00:00', end: '23:59', loops: '1111111'))); } void _onResetDays(ResetDays event, Emitter emit) { diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 5eb6bd4c..64872925 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -3,7 +3,8 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/routiens/models/create_scene/create_scene_model.dart'; +import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart'; +import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/routine_model.dart'; import 'package:syncrow_web/services/routines_api.dart'; @@ -28,6 +29,8 @@ class RoutineBloc extends Bloc { on(_onCreateScene); on(_onRemoveDragCard); on(_changeOperatorOperator); + on(_onEffectiveTimeEvent); + on(_onCreateAutomation); // on(_onRemoveFunction); // on(_onClearFunctions); } @@ -213,10 +216,119 @@ class RoutineBloc extends Bloc { } } + Future _onCreateAutomation( + CreateAutomationEvent event, Emitter emit) async { + try { + if (state.routineName == null || state.routineName!.isEmpty) { + emit(state.copyWith( + errorMessage: 'Automation name is required', + )); + return; + } + + emit(state.copyWith(isLoading: true)); + + final conditions = state.ifItems + .map((item) { + final functions = + state.selectedFunctions[item['uniqueCustomId']] ?? []; + if (functions.isEmpty) return null; + + final function = functions.first; + return CreateCondition( + code: state.ifItems.indexOf(item) + 1, + entityId: function.entityId, + entityType: 'device_report', + expr: ConditionExpr( + statusCode: function.functionCode, + comparator: function.condition ?? '==', + statusValue: function.value, + ), + ); + }) + .whereType() + .toList(); + + if (conditions.isEmpty) { + emit(state.copyWith( + isLoading: false, + errorMessage: 'At least one condition is required', + )); + return; + } + + final createAutomationModel = CreateAutomationModel( + unitUuid: spaceId, + automationName: state.routineName!, + decisionExpr: state.selectedAutomationOperator, + effectiveTime: state.effectiveTime ?? + EffectiveTime( + start: '00:00', + end: '23:59', + loops: '1111111', + ), + conditions: conditions, + actions: state.thenItems + .map((item) { + final functions = + state.selectedFunctions[item['uniqueCustomId']] ?? []; + if (functions.isEmpty) return null; + + final function = functions.first; + if (function.functionCode == 'automation') { + return CreateSceneAction( + entityId: function.entityId, + actionExecutor: function.value, + executorProperty: null, + ); + } + + if (item['deviceId'] == 'delay') { + return CreateSceneAction( + entityId: function.entityId, + actionExecutor: 'delay', + executorProperty: CreateSceneExecutorProperty( + functionCode: '', + functionValue: '', + delaySeconds: function.value, + ), + ); + } + + return CreateSceneAction( + entityId: function.entityId, + actionExecutor: 'device_issue', + executorProperty: CreateSceneExecutorProperty( + functionCode: function.functionCode, + functionValue: function.value, + delaySeconds: 0, + ), + ); + }) + .whereType() + .toList(), + ); + + final result = await SceneApi.createAutomation(createAutomationModel); + if (result['success']) { + emit(const RoutineState()); + } else { + emit(state.copyWith( + isLoading: false, + errorMessage: result['message'], + )); + } + } catch (e) { + emit(state.copyWith( + isLoading: false, + errorMessage: e.toString(), + )); + } + } + FutureOr _onRemoveDragCard( RemoveDragCard event, Emitter emit) { if (event.isFromThen) { - /// remove element from thenItems at specific index final thenItems = List>.from(state.thenItems); thenItems.removeAt(event.index); emit(state.copyWith(thenItems: thenItems)); @@ -233,4 +345,9 @@ class RoutineBloc extends Bloc { selectedAutomationOperator: event.operator, )); } + + FutureOr _onEffectiveTimeEvent( + EffectiveTimePeriodEvent event, Emitter emit) { + emit(state.copyWith(effectiveTime: event.effectiveTime)); + } } diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart index 38fd5e30..2a89d1b4 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart @@ -94,4 +94,25 @@ class ChangeAutomationOperator extends RoutineEvent { List get props => [operator]; } +class EffectiveTimePeriodEvent extends RoutineEvent { + final EffectiveTime effectiveTime; + const EffectiveTimePeriodEvent(this.effectiveTime); + @override + List get props => [effectiveTime]; +} + +class CreateAutomationEvent extends RoutineEvent { + // final CreateAutomationModel createAutomationModel; + final String? automationId; + final bool updateAutomation; + + const CreateAutomationEvent({ + //required this.createAutomationModel, + this.automationId, + this.updateAutomation = false, + }); + @override + List get props => []; +} + class ClearFunctions extends RoutineEvent {} diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart index 8f4da16b..42f584ba 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart @@ -17,6 +17,7 @@ class RoutineState extends Equatable { final bool isTabToRun; final bool isAutomation; final String selectedAutomationOperator; + final EffectiveTime? effectiveTime; const RoutineState({ this.ifItems = const [], @@ -35,43 +36,48 @@ class RoutineState extends Equatable { this.isTabToRun = false, this.isAutomation = false, this.selectedAutomationOperator = 'or', + this.effectiveTime, }); - RoutineState copyWith( - {List>? ifItems, - List>? thenItems, - List? scenes, - List? automations, - Map>? selectedFunctions, - bool? isLoading, - String? errorMessage, - String? routineName, - String? selectedIcon, - String? loadAutomationErrorMessage, - String? loadScenesErrorMessage, - String? searchText, - bool? isTabToRun, - bool? isAutomation, - String? selectedAutomationOperator}) { + RoutineState copyWith({ + List>? ifItems, + List>? thenItems, + List? scenes, + List? automations, + Map>? selectedFunctions, + bool? isLoading, + String? errorMessage, + String? routineName, + String? selectedIcon, + String? loadAutomationErrorMessage, + String? loadScenesErrorMessage, + String? searchText, + bool? isTabToRun, + bool? isAutomation, + String? selectedAutomationOperator, + EffectiveTime? effectiveTime, + }) { return RoutineState( - ifItems: ifItems ?? this.ifItems, - thenItems: thenItems ?? this.thenItems, - scenes: scenes ?? this.scenes, - automations: automations ?? this.automations, - selectedFunctions: selectedFunctions ?? this.selectedFunctions, - isLoading: isLoading ?? this.isLoading, - errorMessage: errorMessage ?? this.errorMessage, - routineName: routineName ?? this.routineName, - selectedIcon: selectedIcon ?? this.selectedIcon, - loadScenesErrorMessage: - loadScenesErrorMessage ?? this.loadScenesErrorMessage, - loadAutomationErrorMessage: - loadAutomationErrorMessage ?? this.loadAutomationErrorMessage, - searchText: searchText ?? this.searchText, - isTabToRun: isTabToRun ?? this.isTabToRun, - isAutomation: isAutomation ?? this.isAutomation, - selectedAutomationOperator: - selectedAutomationOperator ?? this.selectedAutomationOperator); + ifItems: ifItems ?? this.ifItems, + thenItems: thenItems ?? this.thenItems, + scenes: scenes ?? this.scenes, + automations: automations ?? this.automations, + selectedFunctions: selectedFunctions ?? this.selectedFunctions, + isLoading: isLoading ?? this.isLoading, + errorMessage: errorMessage ?? this.errorMessage, + routineName: routineName ?? this.routineName, + selectedIcon: selectedIcon ?? this.selectedIcon, + loadScenesErrorMessage: + loadScenesErrorMessage ?? this.loadScenesErrorMessage, + loadAutomationErrorMessage: + loadAutomationErrorMessage ?? this.loadAutomationErrorMessage, + searchText: searchText ?? this.searchText, + isTabToRun: isTabToRun ?? this.isTabToRun, + isAutomation: isAutomation ?? this.isAutomation, + selectedAutomationOperator: + selectedAutomationOperator ?? this.selectedAutomationOperator, + effectiveTime: effectiveTime ?? this.effectiveTime, + ); } @override @@ -90,6 +96,7 @@ class RoutineState extends Equatable { searchText, isTabToRun, isAutomation, - selectedAutomationOperator + selectedAutomationOperator, + effectiveTime ]; } diff --git a/lib/pages/routiens/helper/save_routine_helper.dart b/lib/pages/routiens/helper/save_routine_helper.dart index 7652554f..c6a8b2aa 100644 --- a/lib/pages/routiens/helper/save_routine_helper.dart +++ b/lib/pages/routiens/helper/save_routine_helper.dart @@ -52,6 +52,35 @@ class SaveRoutineHelper { ), title: const Text('Tab to run'), ), + if (state.isAutomation) + ...state.ifItems.map((item) { + final functions = state.selectedFunctions[ + item['uniqueCustomId']] ?? + []; + return ListTile( + leading: SvgPicture.asset( + item['imagePath'], + width: 22, + height: 22, + ), + title: Text(item['title'], + style: const TextStyle(fontSize: 14)), + subtitle: Wrap( + children: functions + .map((f) => Text( + '${f.operationName}: ${f.value}, ', + style: const TextStyle( + color: ColorsManager + .grayColor, + fontSize: 8), + overflow: + TextOverflow.ellipsis, + maxLines: 3, + )) + .toList(), + ), + ); + }), ], ), ), diff --git a/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart b/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart new file mode 100644 index 00000000..f7bb1fb5 --- /dev/null +++ b/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart @@ -0,0 +1,173 @@ + +import 'dart:convert'; + +import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart'; + +class CreateAutomationModel { + String unitUuid; + String automationName; + String decisionExpr; + EffectiveTime effectiveTime; + List conditions; + List actions; + + CreateAutomationModel({ + required this.unitUuid, + required this.automationName, + required this.decisionExpr, + required this.effectiveTime, + required this.conditions, + required this.actions, + }); + + CreateAutomationModel copyWith({ + String? unitUuid, + String? automationName, + String? decisionExpr, + EffectiveTime? effectiveTime, + List? conditions, + List? actions, + }) { + return CreateAutomationModel( + unitUuid: unitUuid ?? this.unitUuid, + automationName: automationName ?? this.automationName, + decisionExpr: decisionExpr ?? this.decisionExpr, + effectiveTime: effectiveTime ?? this.effectiveTime, + conditions: conditions ?? this.conditions, + actions: actions ?? this.actions, + ); + } + + Map toMap([String? automationId]) { + return { + if (automationId == null) 'spaceUuid': unitUuid, + 'automationName': automationName, + 'decisionExpr': decisionExpr, + 'effectiveTime': effectiveTime.toMap(), + 'conditions': conditions.map((x) => x.toMap()).toList(), + 'actions': actions.map((x) => x.toMap()).toList(), + }; + } + + factory CreateAutomationModel.fromMap(Map map) { + return CreateAutomationModel( + unitUuid: map['spaceUuid'] ?? '', + automationName: map['automationName'] ?? '', + decisionExpr: map['decisionExpr'] ?? '', + effectiveTime: EffectiveTime.fromMap(map['effectiveTime']), + conditions: List.from( + map['conditions']?.map((x) => CreateCondition.fromMap(x))), + actions: List.from( + map['actions']?.map((x) => CreateSceneAction.fromMap(x))), + ); + } + + String toJson([String? automationId]) => json.encode(toMap(automationId)); + + factory CreateAutomationModel.fromJson(String source) => + CreateAutomationModel.fromMap(json.decode(source)); + + @override + String toString() { + return 'CreateAutomationModel(unitUuid: $unitUuid, automationName: $automationName, decisionExpr: $decisionExpr, effectiveTime: $effectiveTime, conditions: $conditions, actions: $actions)'; + } +} + +class EffectiveTime { + String start; + String end; + String loops; + + EffectiveTime({ + required this.start, + required this.end, + required this.loops, + }); + + Map toMap() { + return { + 'start': start, + 'end': end, + 'loops': loops, + }; + } + + factory EffectiveTime.fromMap(Map map) { + return EffectiveTime( + start: map['start'] ?? '', + end: map['end'] ?? '', + loops: map['loops'] ?? '', + ); + } + + @override + String toString() => 'EffectiveTime(start: $start, end: $end, loops: $loops)'; +} + +class CreateCondition { + int code; + String entityId; + String entityType; + ConditionExpr expr; + + CreateCondition({ + required this.code, + required this.entityId, + required this.entityType, + required this.expr, + }); + + Map toMap() { + return { + 'code': code, + 'entityId': entityId, + 'entityType': entityType, + 'expr': expr.toMap(), + }; + } + + factory CreateCondition.fromMap(Map map) { + return CreateCondition( + code: map['code'] ?? 0, + entityId: map['entityId'] ?? '', + entityType: map['entityType'] ?? '', + expr: ConditionExpr.fromMap(map['expr']), + ); + } + + @override + String toString() => + 'CreateCondition(code: $code, entityId: $entityId, entityType: $entityType, expr: $expr)'; +} + +class ConditionExpr { + String statusCode; + String comparator; + dynamic statusValue; + + ConditionExpr({ + required this.statusCode, + required this.comparator, + required this.statusValue, + }); + + Map toMap() { + return { + 'statusCode': statusCode, + 'comparator': comparator, + 'statusValue': statusValue, + }; + } + + factory ConditionExpr.fromMap(Map map) { + return ConditionExpr( + statusCode: map['statusCode'] ?? '', + comparator: map['comparator'] ?? '', + statusValue: map['statusValue'], + ); + } + + @override + String toString() => + 'ConditionExpr(statusCode: $statusCode, comparator: $comparator, statusValue: $statusValue)'; +} diff --git a/lib/pages/routiens/models/create_scene/create_scene_model.dart b/lib/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart similarity index 100% rename from lib/pages/routiens/models/create_scene/create_scene_model.dart rename to lib/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart diff --git a/lib/pages/routiens/widgets/dialog_footer.dart b/lib/pages/routiens/widgets/dialog_footer.dart index 90c6baec..15db9732 100644 --- a/lib/pages/routiens/widgets/dialog_footer.dart +++ b/lib/pages/routiens/widgets/dialog_footer.dart @@ -5,12 +5,14 @@ class DialogFooter extends StatelessWidget { final VoidCallback onCancel; final VoidCallback? onConfirm; final bool isConfirmEnabled; + final int? dialogWidth; const DialogFooter({ Key? key, required this.onCancel, required this.onConfirm, required this.isConfirmEnabled, + this.dialogWidth, }) : super(key: key); @override @@ -26,24 +28,23 @@ class DialogFooter extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - _buildFooterButton( - context, - 'Cancel', - onCancel, - width: isConfirmEnabled ? 299 : 179, - ), - if (isConfirmEnabled) - Row( - children: [ - Container(width: 1, height: 50, color: ColorsManager.greyColor), - _buildFooterButton( - context, - 'Confirm', - onConfirm, - width: 299, - ), - ], + Expanded( + child: _buildFooterButton( + context, + 'Cancel', + onCancel, ), + ), + if (isConfirmEnabled) ...[ + Container(width: 1, height: 50, color: ColorsManager.greyColor), + Expanded( + child: _buildFooterButton( + context, + 'Confirm', + onConfirm, + ), + ), + ], ], ), ); @@ -52,14 +53,12 @@ class DialogFooter extends StatelessWidget { Widget _buildFooterButton( BuildContext context, String text, - VoidCallback? onTap, { - required double width, - }) { + VoidCallback? onTap, + ) { return GestureDetector( onTap: onTap, child: SizedBox( height: 50, - width: width, child: Center( child: Text( text, diff --git a/lib/pages/routiens/widgets/if_container.dart b/lib/pages/routiens/widgets/if_container.dart index a23772d6..57577f64 100644 --- a/lib/pages/routiens/widgets/if_container.dart +++ b/lib/pages/routiens/widgets/if_container.dart @@ -124,34 +124,6 @@ class AutomationOperatorSelector extends StatelessWidget { ), child: Row( children: [ - TextButton( - style: TextButton.styleFrom( - backgroundColor: selectedOperator == 'and' - ? ColorsManager.dialogBlueTitle - : ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(0), - ), - ), - child: Text( - 'All condition is met', - style: context.textTheme.bodyMedium?.copyWith( - color: selectedOperator == 'and' - ? ColorsManager.whiteColors - : ColorsManager.blackColor, - ), - ), - onPressed: () { - context - .read() - .add(const ChangeAutomationOperator(operator: 'and')); - }, - ), - Container( - width: 3, - height: 24, - color: ColorsManager.dividerColor, - ), TextButton( style: TextButton.styleFrom( backgroundColor: selectedOperator == 'or' @@ -175,6 +147,34 @@ class AutomationOperatorSelector extends StatelessWidget { .add(const ChangeAutomationOperator(operator: 'or')); }, ), + Container( + width: 3, + height: 24, + color: ColorsManager.dividerColor, + ), + TextButton( + style: TextButton.styleFrom( + backgroundColor: selectedOperator == 'and' + ? ColorsManager.dialogBlueTitle + : ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), + ), + ), + child: Text( + 'All condition is met', + style: context.textTheme.bodyMedium?.copyWith( + color: selectedOperator == 'and' + ? ColorsManager.whiteColors + : ColorsManager.blackColor, + ), + ), + onPressed: () { + context + .read() + .add(const ChangeAutomationOperator(operator: 'and')); + }, + ), ], ), ); diff --git a/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart b/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart index a789aeb6..c494622a 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart @@ -5,18 +5,19 @@ import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class AutomationDialog extends StatefulWidget { final String automationName; final String automationId; + final String uniqueCustomId; const AutomationDialog({ - Key? key, + super.key, required this.automationName, required this.automationId, - }) : super(key: key); + required this.uniqueCustomId, + }); @override _AutomationDialogState createState() => _AutomationDialogState(); @@ -72,18 +73,19 @@ class _AutomationDialogState extends State { [ DeviceFunctionData( entityId: widget.automationId, - functionCode: '', + functionCode: 'automation', value: _isEnabled, operationName: 'Automation', ), ], - widget.automationId, + widget.uniqueCustomId, ), ); Navigator.of(context).pop(true); }, onCancel: () => Navigator.of(context).pop(false), isConfirmEnabled: true, + dialogWidth: 400, ), ], ), diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index a5e4e60b..9d729327 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -94,20 +94,21 @@ class ThenContainer extends StatelessWidget { ), ); }, - onWillAcceptWithDetails: (data) { - if (data == null) return false; + // onWillAcceptWithDetails: (data) { + // if (data == null) return false; + // return data.data; - if (state.isTabToRun) { - return data.data['type'] == 'automation'; - } + // // if (state.isTabToRun) { + // // return data.data['type'] == 'automation'; + // // } - if (state.isAutomation) { - return data.data['type'] == 'scene' || - data.data['type'] == 'automation'; - } + // // if (state.isAutomation) { + // // return data.data['type'] == 'scene' || + // // data.data['type'] == 'automation'; + // // } - return data.data['deviceId'] != null; - }, + // // return data.data['deviceId'] != null; + // }, onAcceptWithDetails: (data) async { final uniqueCustomId = const Uuid().v4(); final mutableData = Map.from(data.data); @@ -124,13 +125,16 @@ class ThenContainer extends StatelessWidget { builder: (BuildContext context) => AutomationDialog( automationName: mutableData['name'] ?? 'Automation', automationId: mutableData['deviceId'] ?? '', + uniqueCustomId: uniqueCustomId, ), ); if (result != null) { - context - .read() - .add(AddToThenContainer(mutableData)); + context.read().add(AddToThenContainer({ + ...mutableData, + 'imagePath': Assets.automation, + 'title': mutableData['name'], + })); } return; } diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index 0ce106b2..f92f212f 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/routiens/models/create_scene/create_scene_model.dart'; +import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart'; +import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart'; import 'package:syncrow_web/pages/routiens/models/icon_model.dart'; import 'package:syncrow_web/pages/routiens/models/routine_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; @@ -29,23 +30,23 @@ class SceneApi { } } // -// // create automation -// static Future> createAutomation( -// CreateAutomationModel createAutomationModel) async { -// try { -// final response = await _httpService.post( -// path: ApiEndpoints.createAutomation, -// body: createAutomationModel.toMap(), -// showServerMessage: false, -// expectedResponseModel: (json) { -// return json; -// }, -// ); -// return response; -// } catch (e) { -// rethrow; -// } -// } +// create automation + static Future> createAutomation( + CreateAutomationModel createAutomationModel) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.createAutomation, + body: createAutomationModel.toMap(), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } static Future> getIcon() async { final response = await _httpService.get( diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index a5decc3b..4bc0e752 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -54,4 +54,5 @@ abstract class ApiEndpoints { static const String getSpaceAutomation = '/automation/{unitUuid}'; static const String getIconScene = '/scene/icon'; static const String createScene = '/scene/tap-to-run'; + static const String createAutomation = '/automation'; } From 8d908e894b548ca96eca1702217815b10d3c6a07 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Tue, 26 Nov 2024 11:40:34 +0300 Subject: [PATCH 27/40] push routine name field --- lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart | 6 ++++++ lib/pages/routiens/bloc/routine_bloc/routine_event.dart | 6 ++++++ lib/pages/routiens/widgets/routine_search_and_buttons.dart | 4 +++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 64872925..7694e8ec 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -31,6 +31,7 @@ class RoutineBloc extends Bloc { on(_changeOperatorOperator); on(_onEffectiveTimeEvent); on(_onCreateAutomation); + on(_onSetRoutineName); // on(_onRemoveFunction); // on(_onClearFunctions); } @@ -350,4 +351,9 @@ class RoutineBloc extends Bloc { EffectiveTimePeriodEvent event, Emitter emit) { emit(state.copyWith(effectiveTime: event.effectiveTime)); } + + FutureOr _onSetRoutineName( + SetRoutineName event, Emitter emit) { + emit(state.copyWith(routineName: event.name)); + } } diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart index 2a89d1b4..9337e88e 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart @@ -115,4 +115,10 @@ class CreateAutomationEvent extends RoutineEvent { List get props => []; } +class SetRoutineName extends RoutineEvent { + final String name; + const SetRoutineName(this.name); + @override + List get props => [name]; +} class ClearFunctions extends RoutineEvent {} diff --git a/lib/pages/routiens/widgets/routine_search_and_buttons.dart b/lib/pages/routiens/widgets/routine_search_and_buttons.dart index 01352bf8..5bf9db8e 100644 --- a/lib/pages/routiens/widgets/routine_search_and_buttons.dart +++ b/lib/pages/routiens/widgets/routine_search_and_buttons.dart @@ -45,7 +45,9 @@ class RoutineSearchAndButtons extends StatelessWidget { isRequired: true, width: 450, onChanged: (value) { - // context.read().add(SearchRoutines(value)); + context + .read() + .add(SetRoutineName(value)); }, ), ), From 644e56aa7ac8799646de22dd459fae82bf123224 Mon Sep 17 00:00:00 2001 From: ashraf_personal Date: Wed, 27 Nov 2024 00:30:13 +0300 Subject: [PATCH 28/40] fixed automation and tab to run bugs --- .../bloc/routine_bloc/routine_bloc.dart | 214 +++++++------ .../routiens/helper/save_routine_helper.dart | 13 +- .../create_automation_model.dart | 297 +++++++++++++++--- .../routine_dialogs/automation_dialog.dart | 2 +- lib/services/routines_api.dart | 4 + 5 files changed, 374 insertions(+), 156 deletions(-) diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 7694e8ec..0a9e6b0a 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -15,9 +15,6 @@ part 'routine_state.dart'; const spaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6'; class RoutineBloc extends Bloc { - // bool isAutomation = false; - // bool isTabToRun = false; - RoutineBloc() : super(const RoutineState()) { on(_onAddToIfContainer); on(_onAddToThenContainer); @@ -159,50 +156,54 @@ class RoutineBloc extends Bloc { emit(state.copyWith(isLoading: true)); - final actions = state.thenItems - .map((item) { - final functions = - state.selectedFunctions[item['uniqueCustomId']] ?? []; - if (functions.isEmpty) return null; - - final function = functions.first; - if (item['deviceId'] == 'delay') { - return CreateSceneAction( - entityId: function.entityId, - actionExecutor: 'delay', - executorProperty: CreateSceneExecutorProperty( - functionCode: '', - functionValue: '', - delaySeconds: function.value, - ), - ); - } - + final actions = state.thenItems.expand((item) { + final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; + return functions.map((function) { + if (function.functionCode == 'automation') { return CreateSceneAction( entityId: function.entityId, - actionExecutor: 'device_issue', + actionExecutor: function.value, + executorProperty: null, + ); + } + + if (item['deviceId'] == 'delay') { + return CreateSceneAction( + entityId: function.entityId, + actionExecutor: 'delay', executorProperty: CreateSceneExecutorProperty( - functionCode: function.functionCode.toString(), - functionValue: function.value, - delaySeconds: 0, + functionCode: '', + functionValue: '', + delaySeconds: int.tryParse(function.value.toString()) ?? 0, ), ); - }) - .whereType() - .toList(); + } + + return CreateSceneAction( + entityId: function.entityId, + actionExecutor: 'device_issue', + executorProperty: CreateSceneExecutorProperty( + functionCode: function.functionCode, + functionValue: function.value, + delaySeconds: 0, + ), + ); + }); + }).toList(); final createSceneModel = CreateSceneModel( spaceUuid: spaceId, iconId: state.selectedIcon ?? '', showInDevice: true, - sceneName: state.routineName ?? '', + sceneName: state.routineName!, decisionExpr: 'and', actions: actions, ); final result = await SceneApi.createScene(createSceneModel); if (result['success']) { - emit(const RoutineState()); + emit(_resetState()); + add(const LoadScenes(spaceId)); } else { emit(state.copyWith( isLoading: false, @@ -212,7 +213,7 @@ class RoutineBloc extends Bloc { } catch (e) { emit(state.copyWith( isLoading: false, - errorMessage: e.toString(), + errorMessage: 'Something went wrong', )); } } @@ -229,26 +230,24 @@ class RoutineBloc extends Bloc { emit(state.copyWith(isLoading: true)); - final conditions = state.ifItems - .map((item) { - final functions = - state.selectedFunctions[item['uniqueCustomId']] ?? []; - if (functions.isEmpty) return null; - - final function = functions.first; - return CreateCondition( - code: state.ifItems.indexOf(item) + 1, - entityId: function.entityId, - entityType: 'device_report', - expr: ConditionExpr( - statusCode: function.functionCode, - comparator: function.condition ?? '==', - statusValue: function.value, - ), - ); - }) - .whereType() - .toList(); + final conditions = state.ifItems.expand((item) { + final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; + return functions.map((function) { + return Condition( + code: state.selectedFunctions[item['uniqueCustomId']]!.indexOf( + function, + ) + + 1, + entityId: function.entityId, + entityType: 'device_report', + expr: ConditionExpr( + statusCode: function.functionCode, + comparator: function.condition ?? '==', + statusValue: function.value, + ), + ); + }); + }).toList(); if (conditions.isEmpty) { emit(state.copyWith( @@ -258,61 +257,54 @@ class RoutineBloc extends Bloc { return; } + final actions = state.thenItems.expand((item) { + final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; + return functions.map((function) { + if (function.functionCode == 'automation') { + return AutomationAction( + entityId: function.entityId, + actionExecutor: function.value, + ); + } + + if (item['deviceId'] == 'delay') { + return AutomationAction( + entityId: function.entityId, + actionExecutor: 'delay', + executorProperty: ExecutorProperty( + delaySeconds: int.tryParse(function.value.toString()) ?? 0, + ), + ); + } + + return AutomationAction( + entityId: function.entityId, + actionExecutor: 'device_issue', + executorProperty: ExecutorProperty( + functionCode: function.functionCode, + functionValue: function.value, + ), + ); + }); + }).toList(); + final createAutomationModel = CreateAutomationModel( - unitUuid: spaceId, + spaceUuid: spaceId, automationName: state.routineName!, decisionExpr: state.selectedAutomationOperator, - effectiveTime: state.effectiveTime ?? - EffectiveTime( - start: '00:00', - end: '23:59', - loops: '1111111', - ), + effectiveTime: EffectiveTime( + start: state.effectiveTime?.start ?? '00:00', + end: state.effectiveTime?.end ?? '23:59', + loops: state.effectiveTime?.loops ?? '1111111', + ), conditions: conditions, - actions: state.thenItems - .map((item) { - final functions = - state.selectedFunctions[item['uniqueCustomId']] ?? []; - if (functions.isEmpty) return null; - - final function = functions.first; - if (function.functionCode == 'automation') { - return CreateSceneAction( - entityId: function.entityId, - actionExecutor: function.value, - executorProperty: null, - ); - } - - if (item['deviceId'] == 'delay') { - return CreateSceneAction( - entityId: function.entityId, - actionExecutor: 'delay', - executorProperty: CreateSceneExecutorProperty( - functionCode: '', - functionValue: '', - delaySeconds: function.value, - ), - ); - } - - return CreateSceneAction( - entityId: function.entityId, - actionExecutor: 'device_issue', - executorProperty: CreateSceneExecutorProperty( - functionCode: function.functionCode, - functionValue: function.value, - delaySeconds: 0, - ), - ); - }) - .whereType() - .toList(), + actions: actions, ); final result = await SceneApi.createAutomation(createAutomationModel); if (result['success']) { - emit(const RoutineState()); + emit(_resetState()); + add(const LoadAutomation(spaceId)); } else { emit(state.copyWith( isLoading: false, @@ -322,7 +314,7 @@ class RoutineBloc extends Bloc { } catch (e) { emit(state.copyWith( isLoading: false, - errorMessage: e.toString(), + errorMessage: 'Something went wrong', )); } } @@ -349,6 +341,7 @@ class RoutineBloc extends Bloc { FutureOr _onEffectiveTimeEvent( EffectiveTimePeriodEvent event, Emitter emit) { + debugPrint(event.effectiveTime.toString()); emit(state.copyWith(effectiveTime: event.effectiveTime)); } @@ -356,4 +349,25 @@ class RoutineBloc extends Bloc { SetRoutineName event, Emitter emit) { emit(state.copyWith(routineName: event.name)); } + + RoutineState _resetState() { + return const RoutineState( + ifItems: [], + thenItems: [], + selectedFunctions: {}, + scenes: [], + automations: [], + isLoading: false, + errorMessage: null, + loadScenesErrorMessage: null, + loadAutomationErrorMessage: null, + searchText: '', + selectedIcon: null, + isTabToRun: false, + isAutomation: false, + selectedAutomationOperator: 'AND', + effectiveTime: null, + routineName: null, + ); + } } diff --git a/lib/pages/routiens/helper/save_routine_helper.dart b/lib/pages/routiens/helper/save_routine_helper.dart index c6a8b2aa..2a57baa5 100644 --- a/lib/pages/routiens/helper/save_routine_helper.dart +++ b/lib/pages/routiens/helper/save_routine_helper.dart @@ -142,9 +142,16 @@ class SaveRoutineHelper { DialogFooter( onCancel: () => Navigator.pop(context), onConfirm: () { - context - .read() - .add(const CreateSceneEvent()); + if (state.isAutomation) { + context + .read() + .add(const CreateAutomationEvent()); + } else { + context + .read() + .add(const CreateSceneEvent()); + } + Navigator.pop(context); }, isConfirmEnabled: true, diff --git a/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart b/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart index f7bb1fb5..cd9a9c60 100644 --- a/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart +++ b/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart @@ -1,18 +1,187 @@ - import 'dart:convert'; import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart'; +// class CreateAutomationModel { +// String unitUuid; +// String automationName; +// String decisionExpr; +// EffectiveTime effectiveTime; +// List conditions; +// List actions; + +// CreateAutomationModel({ +// required this.unitUuid, +// required this.automationName, +// required this.decisionExpr, +// required this.effectiveTime, +// required this.conditions, +// required this.actions, +// }); + +// CreateAutomationModel copyWith({ +// String? unitUuid, +// String? automationName, +// String? decisionExpr, +// EffectiveTime? effectiveTime, +// List? conditions, +// List? actions, +// }) { +// return CreateAutomationModel( +// unitUuid: unitUuid ?? this.unitUuid, +// automationName: automationName ?? this.automationName, +// decisionExpr: decisionExpr ?? this.decisionExpr, +// effectiveTime: effectiveTime ?? this.effectiveTime, +// conditions: conditions ?? this.conditions, +// actions: actions ?? this.actions, +// ); +// } + +// Map toMap([String? automationId]) { +// return { +// if (automationId == null) 'spaceUuid': unitUuid, +// 'automationName': automationName, +// 'decisionExpr': decisionExpr, +// 'effectiveTime': effectiveTime.toMap(), +// 'conditions': conditions.map((x) => x.toMap()).toList(), +// 'actions': actions.map((x) => x.toMap()).toList(), +// }; +// } + +// factory CreateAutomationModel.fromMap(Map map) { +// return CreateAutomationModel( +// unitUuid: map['spaceUuid'] ?? '', +// automationName: map['automationName'] ?? '', +// decisionExpr: map['decisionExpr'] ?? '', +// effectiveTime: EffectiveTime.fromMap(map['effectiveTime']), +// conditions: List.from( +// map['conditions']?.map((x) => CreateCondition.fromMap(x))), +// actions: List.from( +// map['actions']?.map((x) => CreateSceneAction.fromMap(x))), +// ); +// } + +// String toJson([String? automationId]) => json.encode(toMap(automationId)); + +// factory CreateAutomationModel.fromJson(String source) => +// CreateAutomationModel.fromMap(json.decode(source)); + +// @override +// String toString() { +// return 'CreateAutomationModel(unitUuid: $unitUuid, automationName: $automationName, decisionExpr: $decisionExpr, effectiveTime: $effectiveTime, conditions: $conditions, actions: $actions)'; +// } +// } + +// class EffectiveTime { +// String start; +// String end; +// String loops; + +// EffectiveTime({ +// required this.start, +// required this.end, +// required this.loops, +// }); + +// Map toMap() { +// return { +// 'start': start, +// 'end': end, +// 'loops': loops, +// }; +// } + +// factory EffectiveTime.fromMap(Map map) { +// return EffectiveTime( +// start: map['start'] ?? '', +// end: map['end'] ?? '', +// loops: map['loops'] ?? '', +// ); +// } + +// @override +// String toString() => 'EffectiveTime(start: $start, end: $end, loops: $loops)'; +// } + +// class CreateCondition { +// int code; +// String entityId; +// String entityType; +// ConditionExpr expr; + +// CreateCondition({ +// required this.code, +// required this.entityId, +// required this.entityType, +// required this.expr, +// }); + +// Map toMap() { +// return { +// 'code': code, +// 'entityId': entityId, +// 'entityType': entityType, +// 'expr': expr.toMap(), +// }; +// } + +// factory CreateCondition.fromMap(Map map) { +// return CreateCondition( +// code: map['code'] ?? 0, +// entityId: map['entityId'] ?? '', +// entityType: map['entityType'] ?? '', +// expr: ConditionExpr.fromMap(map['expr']), +// ); +// } + +// @override +// String toString() => +// 'CreateCondition(code: $code, entityId: $entityId, entityType: $entityType, expr: $expr)'; +// } + +// class ConditionExpr { +// String statusCode; +// String comparator; +// dynamic statusValue; + +// ConditionExpr({ +// required this.statusCode, +// required this.comparator, +// required this.statusValue, +// }); + +// Map toMap() { +// return { +// 'statusCode': statusCode, +// 'comparator': comparator, +// 'statusValue': statusValue, +// }; +// } + +// factory ConditionExpr.fromMap(Map map) { +// return ConditionExpr( +// statusCode: map['statusCode'] ?? '', +// comparator: map['comparator'] ?? '', +// statusValue: map['statusValue'], +// ); +// } + +// @override +// String toString() => +// 'ConditionExpr(statusCode: $statusCode, comparator: $comparator, statusValue: $statusValue)'; +// } +import 'dart:convert'; + class CreateAutomationModel { - String unitUuid; + String spaceUuid; String automationName; String decisionExpr; EffectiveTime effectiveTime; - List conditions; - List actions; + List conditions; + List actions; CreateAutomationModel({ - required this.unitUuid, + required this.spaceUuid, required this.automationName, required this.decisionExpr, required this.effectiveTime, @@ -20,27 +189,9 @@ class CreateAutomationModel { required this.actions, }); - CreateAutomationModel copyWith({ - String? unitUuid, - String? automationName, - String? decisionExpr, - EffectiveTime? effectiveTime, - List? conditions, - List? actions, - }) { - return CreateAutomationModel( - unitUuid: unitUuid ?? this.unitUuid, - automationName: automationName ?? this.automationName, - decisionExpr: decisionExpr ?? this.decisionExpr, - effectiveTime: effectiveTime ?? this.effectiveTime, - conditions: conditions ?? this.conditions, - actions: actions ?? this.actions, - ); - } - - Map toMap([String? automationId]) { + Map toMap() { return { - if (automationId == null) 'spaceUuid': unitUuid, + 'spaceUuid': spaceUuid, 'automationName': automationName, 'decisionExpr': decisionExpr, 'effectiveTime': effectiveTime.toMap(), @@ -51,26 +202,21 @@ class CreateAutomationModel { factory CreateAutomationModel.fromMap(Map map) { return CreateAutomationModel( - unitUuid: map['spaceUuid'] ?? '', + spaceUuid: map['spaceUuid'] ?? '', automationName: map['automationName'] ?? '', decisionExpr: map['decisionExpr'] ?? '', effectiveTime: EffectiveTime.fromMap(map['effectiveTime']), - conditions: List.from( - map['conditions']?.map((x) => CreateCondition.fromMap(x))), - actions: List.from( - map['actions']?.map((x) => CreateSceneAction.fromMap(x))), + conditions: List.from( + map['conditions']?.map((x) => Condition.fromMap(x)) ?? []), + actions: List.from( + map['actions']?.map((x) => AutomationAction.fromMap(x)) ?? []), ); } - String toJson([String? automationId]) => json.encode(toMap(automationId)); + String toJson() => json.encode(toMap()); factory CreateAutomationModel.fromJson(String source) => CreateAutomationModel.fromMap(json.decode(source)); - - @override - String toString() { - return 'CreateAutomationModel(unitUuid: $unitUuid, automationName: $automationName, decisionExpr: $decisionExpr, effectiveTime: $effectiveTime, conditions: $conditions, actions: $actions)'; - } } class EffectiveTime { @@ -99,18 +245,15 @@ class EffectiveTime { loops: map['loops'] ?? '', ); } - - @override - String toString() => 'EffectiveTime(start: $start, end: $end, loops: $loops)'; } -class CreateCondition { +class Condition { int code; String entityId; String entityType; ConditionExpr expr; - CreateCondition({ + Condition({ required this.code, required this.entityId, required this.entityType, @@ -126,18 +269,14 @@ class CreateCondition { }; } - factory CreateCondition.fromMap(Map map) { - return CreateCondition( - code: map['code'] ?? 0, + factory Condition.fromMap(Map map) { + return Condition( + code: map['code']?.toInt() ?? 0, entityId: map['entityId'] ?? '', entityType: map['entityType'] ?? '', expr: ConditionExpr.fromMap(map['expr']), ); } - - @override - String toString() => - 'CreateCondition(code: $code, entityId: $entityId, entityType: $entityType, expr: $expr)'; } class ConditionExpr { @@ -166,8 +305,62 @@ class ConditionExpr { statusValue: map['statusValue'], ); } - - @override - String toString() => - 'ConditionExpr(statusCode: $statusCode, comparator: $comparator, statusValue: $statusValue)'; +} + +class AutomationAction { + String entityId; + String actionExecutor; + ExecutorProperty? executorProperty; + + AutomationAction({ + required this.entityId, + required this.actionExecutor, + this.executorProperty, + }); + + Map toMap() { + return { + 'entityId': entityId, + 'actionExecutor': actionExecutor, + 'executorProperty': executorProperty?.toMap(), + }; + } + + factory AutomationAction.fromMap(Map map) { + return AutomationAction( + entityId: map['entityId'] ?? '', + actionExecutor: map['actionExecutor'] ?? '', + executorProperty: map['executorProperty'] != null + ? ExecutorProperty.fromMap(map['executorProperty']) + : null, + ); + } +} + +class ExecutorProperty { + String? functionCode; + dynamic functionValue; + int? delaySeconds; + + ExecutorProperty({ + this.functionCode, + this.functionValue, + this.delaySeconds, + }); + + Map toMap() { + return { + if (functionCode != null) 'functionCode': functionCode, + if (functionValue != null) 'functionValue': functionValue, + if (delaySeconds != null) 'delaySeconds': delaySeconds, + }; + } + + factory ExecutorProperty.fromMap(Map map) { + return ExecutorProperty( + functionCode: map['functionCode'], + functionValue: map['functionValue'], + delaySeconds: map['delaySeconds']?.toInt(), + ); + } } diff --git a/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart b/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart index c494622a..06995882 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart @@ -74,7 +74,7 @@ class _AutomationDialogState extends State { DeviceFunctionData( entityId: widget.automationId, functionCode: 'automation', - value: _isEnabled, + value: _isEnabled ? 'rule_enable' : 'rule_disable', operationName: 'Automation', ), ], diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index f92f212f..756d533e 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -29,11 +29,13 @@ class SceneApi { rethrow; } } + // // create automation static Future> createAutomation( CreateAutomationModel createAutomationModel) async { try { + debugPrint("automation body ${createAutomationModel.toMap()}"); final response = await _httpService.post( path: ApiEndpoints.createAutomation, body: createAutomationModel.toMap(), @@ -42,8 +44,10 @@ class SceneApi { return json; }, ); + debugPrint('create automation response: $response'); return response; } catch (e) { + debugPrint(e.toString()); rethrow; } } From 03f7b962238757d2fa611139b4e3927dd4c13ecc Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Wed, 27 Nov 2024 00:53:48 +0300 Subject: [PATCH 29/40] bug fixes in setting and if contianer --- .../effective_period/effect_period_bloc.dart | 20 - .../bloc/routine_bloc/routine_bloc.dart | 110 ++- .../bloc/routine_bloc/routine_event.dart | 6 +- .../routiens/view/effective_period_view.dart | 63 +- lib/pages/routiens/widgets/if_container.dart | 79 +- lib/pages/routiens/widgets/period_option.dart | 16 +- .../routine_dialogs/setting_dialog.dart | 815 ++++++++---------- .../widgets/routine_search_and_buttons.dart | 362 ++++---- .../routiens/widgets/then_container.dart | 62 +- 9 files changed, 731 insertions(+), 802 deletions(-) diff --git a/lib/pages/routiens/bloc/effective_period/effect_period_bloc.dart b/lib/pages/routiens/bloc/effective_period/effect_period_bloc.dart index b5a293a1..f4db836b 100644 --- a/lib/pages/routiens/bloc/effective_period/effect_period_bloc.dart +++ b/lib/pages/routiens/bloc/effective_period/effect_period_bloc.dart @@ -2,10 +2,7 @@ import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart'; import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart'; import 'package:syncrow_web/utils/constants/app_enum.dart'; -import 'package:syncrow_web/utils/navigation_service.dart'; class EffectPeriodBloc extends Bloc { final daysMap = { @@ -52,10 +49,6 @@ class EffectPeriodBloc extends Bloc { break; } - BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( - EffectiveTimePeriodEvent( - EffectiveTime(start: startTime, end: endTime, loops: state.selectedDaysBinary))); - emit(state.copyWith( selectedPeriod: event.period, customStartTime: startTime, customEndTime: endTime)); } @@ -70,12 +63,6 @@ class EffectPeriodBloc extends Bloc { } final newDaysBinary = daysList.join(); emit(state.copyWith(selectedDaysBinary: newDaysBinary)); - - BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( - EffectiveTimePeriodEvent(EffectiveTime( - start: state.customStartTime ?? '00:00', - end: state.customEndTime ?? '23:59', - loops: newDaysBinary))); } void _onSetCustomTime(SetCustomTime event, Emitter emit) { @@ -96,10 +83,6 @@ class EffectPeriodBloc extends Bloc { emit( state.copyWith(customStartTime: startTime, customEndTime: endTime, selectedPeriod: period)); - - BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( - EffectiveTimePeriodEvent( - EffectiveTime(start: startTime, end: endTime, loops: state.selectedDaysBinary))); } void _onResetEffectivePeriod(ResetEffectivePeriod event, Emitter emit) { @@ -108,9 +91,6 @@ class EffectPeriodBloc extends Bloc { customStartTime: '00:00', customEndTime: '23:59', selectedDaysBinary: '1111111')); - - BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( - EffectiveTimePeriodEvent(EffectiveTime(start: '00:00', end: '23:59', loops: '1111111'))); } void _onResetDays(ResetDays event, Emitter emit) { diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 7694e8ec..45447ed4 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -37,24 +37,31 @@ class RoutineBloc extends Bloc { } void _onAddToIfContainer(AddToIfContainer event, Emitter emit) { - final updatedIfItems = List>.from(state.ifItems) - ..add(event.item); - if (event.isTabToRun) { - emit(state.copyWith( - ifItems: updatedIfItems, isTabToRun: true, isAutomation: false)); + final updatedIfItems = List>.from(state.ifItems); + + // Find the index of the item in teh current itemsList + int index = + updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); + // Replace the map if the index is valid + if (index != -1) { + updatedIfItems[index] = event.item; } else { - emit(state.copyWith( - ifItems: updatedIfItems, isTabToRun: false, isAutomation: true)); + updatedIfItems.add(event.item); + } + + if (event.isTabToRun) { + emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false)); + } else { + emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true)); } } - void _onAddToThenContainer( - AddToThenContainer event, Emitter emit) { + void _onAddToThenContainer(AddToThenContainer event, Emitter emit) { final currentItems = List>.from(state.thenItems); // Find the index of the item in teh current itemsList - int index = currentItems.indexWhere( - (map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); + int index = + currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); // Replace the map if the index is valid if (index != -1) { currentItems[index] = event.item; @@ -65,21 +72,38 @@ class RoutineBloc extends Bloc { emit(state.copyWith(thenItems: currentItems)); } - void _onAddFunctionsToRoutine( - AddFunctionToRoutine event, Emitter emit) { + void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter emit) { try { if (event.functions.isEmpty) return; - final currentSelectedFunctions = - Map>.from(state.selectedFunctions); + List selectedFunction = List.from(event.functions); + Map> currentSelectedFunctions = + Map>.from(state.selectedFunctions); if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) { - currentSelectedFunctions[event.uniqueCustomId] = - List.from(currentSelectedFunctions[event.uniqueCustomId]!) - ..addAll(event.functions); + List currentFunctions = + List.from(currentSelectedFunctions[event.uniqueCustomId] ?? []); + + List functionCode = []; + for (int i = 0; i < selectedFunction.length; i++) { + for (int j = 0; j < currentFunctions.length; j++) { + if (selectedFunction[i].functionCode == currentFunctions[j].functionCode) { + currentFunctions[j] = selectedFunction[i]; + if (!functionCode.contains(currentFunctions[j].functionCode)) { + functionCode.add(currentFunctions[j].functionCode); + } + } + } + } + + for (int i = 0; i < functionCode.length; i++) { + selectedFunction.removeWhere((code) => code.functionCode == functionCode[i]); + } + + currentSelectedFunctions[event.uniqueCustomId] = List.from(currentFunctions) + ..addAll(selectedFunction); } else { - currentSelectedFunctions[event.uniqueCustomId] = - List.from(event.functions); + currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions); } emit(state.copyWith(selectedFunctions: currentSelectedFunctions)); @@ -88,8 +112,7 @@ class RoutineBloc extends Bloc { } } - Future _onLoadScenes( - LoadScenes event, Emitter emit) async { + Future _onLoadScenes(LoadScenes event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { @@ -108,8 +131,7 @@ class RoutineBloc extends Bloc { } } - Future _onLoadAutomation( - LoadAutomation event, Emitter emit) async { + Future _onLoadAutomation(LoadAutomation event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { @@ -128,15 +150,13 @@ class RoutineBloc extends Bloc { } } - FutureOr _onSearchRoutines( - SearchRoutines event, Emitter emit) async { + FutureOr _onSearchRoutines(SearchRoutines event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); await Future.delayed(const Duration(seconds: 1)); emit(state.copyWith(searchText: event.query)); } - FutureOr _onAddSelectedIcon( - AddSelectedIcon event, Emitter emit) { + FutureOr _onAddSelectedIcon(AddSelectedIcon event, Emitter emit) { emit(state.copyWith(selectedIcon: event.icon)); } @@ -145,8 +165,7 @@ class RoutineBloc extends Bloc { return actions.first['deviceId'] == 'delay'; } - Future _onCreateScene( - CreateSceneEvent event, Emitter emit) async { + Future _onCreateScene(CreateSceneEvent event, Emitter emit) async { try { // Check if first action is delay if (_isFirstActionDelay(state.thenItems)) { @@ -161,8 +180,7 @@ class RoutineBloc extends Bloc { final actions = state.thenItems .map((item) { - final functions = - state.selectedFunctions[item['uniqueCustomId']] ?? []; + final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; if (functions.isEmpty) return null; final function = functions.first; @@ -217,8 +235,7 @@ class RoutineBloc extends Bloc { } } - Future _onCreateAutomation( - CreateAutomationEvent event, Emitter emit) async { + Future _onCreateAutomation(CreateAutomationEvent event, Emitter emit) async { try { if (state.routineName == null || state.routineName!.isEmpty) { emit(state.copyWith( @@ -231,8 +248,7 @@ class RoutineBloc extends Bloc { final conditions = state.ifItems .map((item) { - final functions = - state.selectedFunctions[item['uniqueCustomId']] ?? []; + final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; if (functions.isEmpty) return null; final function = functions.first; @@ -271,8 +287,7 @@ class RoutineBloc extends Bloc { conditions: conditions, actions: state.thenItems .map((item) { - final functions = - state.selectedFunctions[item['uniqueCustomId']] ?? []; + final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; if (functions.isEmpty) return null; final function = functions.first; @@ -327,16 +342,21 @@ class RoutineBloc extends Bloc { } } - FutureOr _onRemoveDragCard( - RemoveDragCard event, Emitter emit) { + FutureOr _onRemoveDragCard(RemoveDragCard event, Emitter emit) { if (event.isFromThen) { final thenItems = List>.from(state.thenItems); + final selectedFunctions = Map>.from(state.selectedFunctions); + thenItems.removeAt(event.index); - emit(state.copyWith(thenItems: thenItems)); + selectedFunctions.remove(event.key); + emit(state.copyWith(thenItems: thenItems, selectedFunctions: selectedFunctions)); } else { final ifItems = List>.from(state.ifItems); + final selectedFunctions = Map>.from(state.selectedFunctions); + ifItems.removeAt(event.index); - emit(state.copyWith(ifItems: ifItems)); + selectedFunctions.remove(event.key); + emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions)); } } @@ -347,13 +367,11 @@ class RoutineBloc extends Bloc { )); } - FutureOr _onEffectiveTimeEvent( - EffectiveTimePeriodEvent event, Emitter emit) { + FutureOr _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter emit) { emit(state.copyWith(effectiveTime: event.effectiveTime)); } - FutureOr _onSetRoutineName( - SetRoutineName event, Emitter emit) { + FutureOr _onSetRoutineName(SetRoutineName event, Emitter emit) { emit(state.copyWith(routineName: event.name)); } } diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart index 9337e88e..6f1b8a95 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart @@ -82,9 +82,10 @@ class CreateSceneEvent extends RoutineEvent { class RemoveDragCard extends RoutineEvent { final int index; final bool isFromThen; - const RemoveDragCard({required this.index, required this.isFromThen}); + final String key; + const RemoveDragCard({required this.index, required this.isFromThen, required this.key}); @override - List get props => [index]; + List get props => [index, isFromThen, key]; } class ChangeAutomationOperator extends RoutineEvent { @@ -121,4 +122,5 @@ class SetRoutineName extends RoutineEvent { @override List get props => [name]; } + class ClearFunctions extends RoutineEvent {} diff --git a/lib/pages/routiens/view/effective_period_view.dart b/lib/pages/routiens/view/effective_period_view.dart index d9d9bc2f..5e6c33da 100644 --- a/lib/pages/routiens/view/effective_period_view.dart +++ b/lib/pages/routiens/view/effective_period_view.dart @@ -1,6 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart'; import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/effictive_period_dialog.dart'; import 'package:syncrow_web/pages/routiens/widgets/period_option.dart'; import 'package:syncrow_web/pages/routiens/widgets/repeat_days.dart'; @@ -11,39 +9,36 @@ class EffectivePeriodView extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocProvider( - create: (_) => EffectPeriodBloc(), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: ListView( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Spacer(), - Expanded( - child: Text( - 'Effective Period', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w400, - fontSize: 14), - ), + return Padding( + padding: const EdgeInsets.all(16.0), + child: ListView( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Spacer(), + Expanded( + child: Text( + 'Effective Period', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 14), ), - const Spacer(), - ], - ), - const Divider( - color: ColorsManager.backgroundColor, - ), - const PeriodOptions( - showCustomTimePicker: EffectPeriodHelper.showCustomTimePicker, - ), - const SizedBox(height: 16), - const RepeatDays(), - const SizedBox(height: 24), - ], - ), + ), + const Spacer(), + ], + ), + const Divider( + color: ColorsManager.backgroundColor, + ), + const PeriodOptions( + showCustomTimePicker: EffectPeriodHelper.showCustomTimePicker, + ), + const SizedBox(height: 16), + const RepeatDays(), + const SizedBox(height: 24), + ], ), ); } diff --git a/lib/pages/routiens/widgets/if_container.dart b/lib/pages/routiens/widgets/if_container.dart index 57577f64..d0273adc 100644 --- a/lib/pages/routiens/widgets/if_container.dart +++ b/lib/pages/routiens/widgets/if_container.dart @@ -26,9 +26,7 @@ class IfContainer extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text('IF', - style: TextStyle( - fontSize: 18, fontWeight: FontWeight.bold)), + const Text('IF', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), if (state.isAutomation) AutomationOperatorSelector( selectedOperator: state.selectedAutomationOperator), @@ -52,20 +50,38 @@ class IfContainer extends StatelessWidget { runSpacing: 8, children: List.generate( state.ifItems.length, - (index) => DraggableCard( - imagePath: - state.ifItems[index]['imagePath'] ?? '', - title: state.ifItems[index]['title'] ?? '', - deviceData: state.ifItems[index], - padding: const EdgeInsets.symmetric( - horizontal: 4, vertical: 8), - isFromThen: false, - isFromIf: true, - onRemove: () { - context.read().add( - RemoveDragCard( - index: index, isFromThen: false)); + (index) => GestureDetector( + onTap: () async { + if (!state.isTabToRun) { + final result = await DeviceDialogHelper.showDeviceDialog( + context, state.ifItems[index]); + + if (result != null) { + context + .read() + .add(AddToIfContainer(state.ifItems[index], false)); + } else if (!['AC', '1G', '2G', '3G'] + .contains(state.ifItems[index]['productType'])) { + context + .read() + .add(AddToIfContainer(state.ifItems[index], false)); + } + } }, + child: DraggableCard( + imagePath: state.ifItems[index]['imagePath'] ?? '', + title: state.ifItems[index]['title'] ?? '', + deviceData: state.ifItems[index], + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), + isFromThen: false, + isFromIf: true, + onRemove: () { + context.read().add(RemoveDragCard( + index: index, + isFromThen: false, + key: state.ifItems[index]['uniqueCustomId'])); + }, + ), )), ), ], @@ -81,22 +97,14 @@ class IfContainer extends StatelessWidget { if (!state.isTabToRun) { if (mutableData['deviceId'] == 'tab_to_run') { - context - .read() - .add(AddToIfContainer(mutableData, true)); + context.read().add(AddToIfContainer(mutableData, true)); } else { - final result = await DeviceDialogHelper.showDeviceDialog( - context, mutableData); + final result = await DeviceDialogHelper.showDeviceDialog(context, mutableData); if (result != null) { - context - .read() - .add(AddToIfContainer(mutableData, false)); - } else if (!['AC', '1G', '2G', '3G'] - .contains(mutableData['productType'])) { - context - .read() - .add(AddToIfContainer(mutableData, false)); + context.read().add(AddToIfContainer(mutableData, false)); + } else if (!['AC', '1G', '2G', '3G'].contains(mutableData['productType'])) { + context.read().add(AddToIfContainer(mutableData, false)); } } } @@ -136,15 +144,12 @@ class AutomationOperatorSelector extends StatelessWidget { child: Text( 'Any condition is met', style: context.textTheme.bodyMedium?.copyWith( - color: selectedOperator == 'or' - ? ColorsManager.whiteColors - : ColorsManager.blackColor, + color: + selectedOperator == 'or' ? ColorsManager.whiteColors : ColorsManager.blackColor, ), ), onPressed: () { - context - .read() - .add(const ChangeAutomationOperator(operator: 'or')); + context.read().add(const ChangeAutomationOperator(operator: 'or')); }, ), Container( @@ -170,9 +175,7 @@ class AutomationOperatorSelector extends StatelessWidget { ), ), onPressed: () { - context - .read() - .add(const ChangeAutomationOperator(operator: 'and')); + context.read().add(const ChangeAutomationOperator(operator: 'and')); }, ), ], diff --git a/lib/pages/routiens/widgets/period_option.dart b/lib/pages/routiens/widgets/period_option.dart index 8a89e2f9..5c1c2d51 100644 --- a/lib/pages/routiens/widgets/period_option.dart +++ b/lib/pages/routiens/widgets/period_option.dart @@ -21,12 +21,9 @@ class PeriodOptions extends StatelessWidget { builder: (context, state) { return Column( children: [ - _buildRadioOption( - context, EnumEffectivePeriodOptions.allDay, '24 Hours'), - _buildRadioOption(context, EnumEffectivePeriodOptions.daytime, - 'Sunrise to Sunset'), - _buildRadioOption( - context, EnumEffectivePeriodOptions.night, 'Sunset to Sunrise'), + _buildRadioOption(context, EnumEffectivePeriodOptions.allDay, '24 Hours'), + _buildRadioOption(context, EnumEffectivePeriodOptions.daytime, 'Sunrise to Sunset'), + _buildRadioOption(context, EnumEffectivePeriodOptions.night, 'Sunset to Sunrise'), ListTile( contentPadding: EdgeInsets.zero, onTap: () => showCustomTimePicker(context), @@ -37,8 +34,7 @@ class PeriodOptions extends StatelessWidget { fontWeight: FontWeight.w400, fontSize: 14), ), - subtitle: state.customStartTime != null && - state.customEndTime != null + subtitle: state.customStartTime != null && state.customEndTime != null ? Text( '${"${state.customStartTime}"} - ${"${state.customEndTime}"}', style: Theme.of(context).textTheme.bodyMedium!.copyWith( @@ -83,9 +79,7 @@ class PeriodOptions extends StatelessWidget { subtitle: Text( subtitle, style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w400, - fontSize: 10), + color: ColorsManager.textPrimaryColor, fontWeight: FontWeight.w400, fontSize: 10), ), trailing: Radio( value: value, diff --git a/lib/pages/routiens/widgets/routine_dialogs/setting_dialog.dart b/lib/pages/routiens/widgets/routine_dialogs/setting_dialog.dart index 4e2bdde6..7fc723af 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/setting_dialog.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/setting_dialog.dart @@ -1,9 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_event.dart'; import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_state.dart'; +import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart'; import 'package:syncrow_web/pages/routiens/models/icon_model.dart'; import 'package:syncrow_web/pages/routiens/view/effective_period_view.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; @@ -19,444 +22,392 @@ class SettingHelper { context: context, builder: (BuildContext context) { final isAutomation = context.read().state.isAutomation; - return BlocProvider( - create: (_) => - SettingBloc()..add(InitialEvent(selectedIcon: iconId ?? '')), + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (_) => EffectPeriodBloc(), + ), + BlocProvider( + create: (_) => SettingBloc()..add(InitialEvent(selectedIcon: iconId ?? ''))), + ], child: AlertDialog( contentPadding: EdgeInsets.zero, - content: BlocBuilder( - builder: (context, state) { - String selectedIcon = ''; - List list = []; - if (state is TabToRunSettingLoaded) { - selectedIcon = state.selectedIcon; - list = state.iconList; - } - return Container( - width: context.read().isExpanded ? 800 : 400, - height: context.read().isExpanded && isAutomation - ? 500 - : 300, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - ), - padding: const EdgeInsets.only(top: 20), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const DialogHeader('Settings'), - Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 400, - child: isAutomation - ? Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: const EdgeInsets.only( - top: 10, - left: 10, - right: 10, - bottom: 10), - child: Column( - children: [ - InkWell( - onTap: () {}, - child: Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: [ - Text( - 'Validity', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: ColorsManager - .textPrimaryColor, - fontWeight: - FontWeight - .w400, - fontSize: 14), - ), - const Icon( - Icons - .arrow_forward_ios_outlined, - color: ColorsManager - .textGray, - size: 15, - ) - ], - ), - ), - const SizedBox( - height: 5, - ), - const Divider( - color: ColorsManager.graysColor, - ), - const SizedBox( - height: 5, - ), - InkWell( - onTap: () { - BlocProvider.of( - context) - .add(FetchIcons( - expanded: !context - .read< - SettingBloc>() - .isExpanded)); - }, - child: Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: [ - Text( - 'Effective Period', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: ColorsManager - .textPrimaryColor, - fontWeight: - FontWeight - .w400, - fontSize: 14), - ), - const Icon( - Icons - .arrow_forward_ios_outlined, - color: ColorsManager - .textGray, - size: 15, - ) - ], - ), - ), - const SizedBox( - height: 5, - ), - const Divider( - color: ColorsManager.graysColor, - ), - const SizedBox( - height: 5, - ), - Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: [ - Text( - 'Executed by', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: ColorsManager - .textPrimaryColor, - fontWeight: - FontWeight.w400, - fontSize: 14), - ), - Text('Cloud', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: - ColorsManager - .textGray, - fontWeight: - FontWeight - .w400, - fontSize: 14)), - ], - ), - ], - )), - ], - ) - : Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: const EdgeInsets.only( - top: 10, - left: 10, - right: 10, - bottom: 10), - child: Column( - children: [ - InkWell( - onTap: () { - BlocProvider.of( - context) - .add(FetchIcons( - expanded: !context - .read< - SettingBloc>() - .isExpanded)); - }, - child: Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: [ - Text( - 'Icons', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: ColorsManager - .textPrimaryColor, - fontWeight: - FontWeight - .w400, - fontSize: 14), - ), - const Icon( - Icons - .arrow_forward_ios_outlined, - color: ColorsManager - .textGray, - size: 15, - ) - ], - ), - ), - const SizedBox( - height: 5, - ), - const Divider( - color: ColorsManager.graysColor, - ), - const SizedBox( - height: 5, - ), - Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: [ - Text( - 'Show on devices page', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: ColorsManager - .textPrimaryColor, - fontWeight: - FontWeight.w400, - fontSize: 14), - ), - Row( - mainAxisAlignment: - MainAxisAlignment.end, - children: [ - Container( - height: 30, - width: 1, - color: ColorsManager - .graysColor, - ), - Transform.scale( - scale: .8, - child: CupertinoSwitch( - value: true, - onChanged: (value) {}, - applyTheme: true, - ), - ), - ], - ) - ], - ), - const SizedBox( - height: 5, - ), - const Divider( - color: ColorsManager.graysColor, - ), - const SizedBox( - height: 5, - ), - Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: [ - Text( - 'Executed by', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: ColorsManager - .textPrimaryColor, - fontWeight: - FontWeight.w400, - fontSize: 14), - ), - Text('Cloud', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: - ColorsManager - .textGray, - fontWeight: - FontWeight - .w400, - fontSize: 14)), - ], - ), - ], - )), - ], - ), - ), - if (context.read().isExpanded && - !isAutomation) - SizedBox( - width: 400, - height: 150, - child: state is LoadingState - ? const Center( - child: CircularProgressIndicator()) - : GridView.builder( - gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 6, - crossAxisSpacing: 12, - mainAxisSpacing: 12, - ), - shrinkWrap: true, - itemCount: list.length, - itemBuilder: (context, index) { - final iconModel = list[index]; - return SizedBox( - width: 35, - height: 35, - child: InkWell( - onTap: () { - BlocProvider.of( - context) - .add(SelectIcon( - iconId: iconModel.uuid, - )); - selectedIcon = iconModel.uuid; - }, - child: SizedBox( - child: ClipOval( - child: Container( - padding: - const EdgeInsets.all(1), - decoration: BoxDecoration( - border: Border.all( - color: selectedIcon == - iconModel.uuid - ? ColorsManager - .primaryColorWithOpacity - : Colors - .transparent, - width: 2, - ), - shape: BoxShape.circle, - ), - child: Image.memory( - iconModel.iconBytes, - ), - ), - ), - ), - ), - ); - }, - )), - if (context.read().isExpanded && - isAutomation) - const SizedBox( - height: 350, - width: 400, - child: EffectivePeriodView()) - ], - ), - Container( - width: MediaQuery.sizeOf(context).width, - decoration: const BoxDecoration( - border: Border( - top: BorderSide( - color: ColorsManager.greyColor, - ), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + content: BlocBuilder( + builder: (context, effectPeriodState) { + return BlocBuilder( + builder: (context, settingState) { + String selectedIcon = ''; + List list = []; + if (settingState is TabToRunSettingLoaded) { + selectedIcon = settingState.selectedIcon; + list = settingState.iconList; + } + return Container( + width: context.read().isExpanded ? 800 : 400, + height: context.read().isExpanded && isAutomation ? 500 : 300, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const DialogHeader('Settings'), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - child: InkWell( - onTap: () { - Navigator.of(context).pop(); - }, - child: Container( - alignment: AlignmentDirectional.center, - child: Text( - 'Cancel', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: ColorsManager.textGray, - ), - ), - ), - ), - ), - Container( - width: 1, - height: 50, - color: ColorsManager.greyColor), - Expanded( - child: InkWell( - onTap: () { - Navigator.of(context).pop(selectedIcon); - }, - child: Container( - alignment: AlignmentDirectional.center, - child: Text( - 'Confirm', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: ColorsManager - .primaryColorWithOpacity, - ), - ), - ), - ), + SizedBox( + width: 400, + child: isAutomation + ? Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.only( + top: 10, left: 10, right: 10, bottom: 10), + child: Column( + children: [ + InkWell( + onTap: () {}, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Validity', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: + ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 14), + ), + const Icon( + Icons.arrow_forward_ios_outlined, + color: ColorsManager.textGray, + size: 15, + ) + ], + ), + ), + const SizedBox( + height: 5, + ), + const Divider( + color: ColorsManager.graysColor, + ), + const SizedBox( + height: 5, + ), + InkWell( + onTap: () { + BlocProvider.of(context).add( + FetchIcons( + expanded: !context + .read() + .isExpanded)); + }, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Effective Period', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: + ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 14), + ), + const Icon( + Icons.arrow_forward_ios_outlined, + color: ColorsManager.textGray, + size: 15, + ) + ], + ), + ), + const SizedBox( + height: 5, + ), + const Divider( + color: ColorsManager.graysColor, + ), + const SizedBox( + height: 5, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Executed by', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 14), + ), + Text('Cloud', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: ColorsManager.textGray, + fontWeight: FontWeight.w400, + fontSize: 14)), + ], + ), + ], + )), + ], + ) + : Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.only( + top: 10, left: 10, right: 10, bottom: 10), + child: Column( + children: [ + InkWell( + onTap: () { + BlocProvider.of(context).add( + FetchIcons( + expanded: !context + .read() + .isExpanded)); + }, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Icons', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: + ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 14), + ), + const Icon( + Icons.arrow_forward_ios_outlined, + color: ColorsManager.textGray, + size: 15, + ) + ], + ), + ), + const SizedBox( + height: 5, + ), + const Divider( + color: ColorsManager.graysColor, + ), + const SizedBox( + height: 5, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Show on devices page', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 14), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + height: 30, + width: 1, + color: ColorsManager.graysColor, + ), + Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: true, + onChanged: (value) {}, + applyTheme: true, + ), + ), + ], + ) + ], + ), + const SizedBox( + height: 5, + ), + const Divider( + color: ColorsManager.graysColor, + ), + const SizedBox( + height: 5, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Executed by', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 14), + ), + Text('Cloud', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: ColorsManager.textGray, + fontWeight: FontWeight.w400, + fontSize: 14)), + ], + ), + ], + )), + ], + ), ), + if (context.read().isExpanded && !isAutomation) + SizedBox( + width: 400, + height: 150, + child: settingState is LoadingState + ? const Center(child: CircularProgressIndicator()) + : GridView.builder( + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 6, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + shrinkWrap: true, + itemCount: list.length, + itemBuilder: (context, index) { + final iconModel = list[index]; + return SizedBox( + width: 35, + height: 35, + child: InkWell( + onTap: () { + BlocProvider.of(context) + .add(SelectIcon( + iconId: iconModel.uuid, + )); + selectedIcon = iconModel.uuid; + }, + child: SizedBox( + child: ClipOval( + child: Container( + padding: const EdgeInsets.all(1), + decoration: BoxDecoration( + border: Border.all( + color: selectedIcon == iconModel.uuid + ? ColorsManager + .primaryColorWithOpacity + : Colors.transparent, + width: 2, + ), + shape: BoxShape.circle, + ), + child: Image.memory( + iconModel.iconBytes, + ), + ), + ), + ), + ), + ); + }, + )), + if (context.read().isExpanded && isAutomation) + const SizedBox(height: 350, width: 400, child: EffectivePeriodView()) ], ), - ) - ], - ), - ); - }, - ), + Container( + width: MediaQuery.sizeOf(context).width, + decoration: const BoxDecoration( + border: Border( + top: BorderSide( + color: ColorsManager.greyColor, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: Container( + alignment: AlignmentDirectional.center, + child: Text( + 'Cancel', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.textGray, + ), + ), + ), + ), + ), + Container(width: 1, height: 50, color: ColorsManager.greyColor), + Expanded( + child: InkWell( + onTap: () { + if (isAutomation) { + BlocProvider.of(context).add( + EffectiveTimePeriodEvent(EffectiveTime( + start: effectPeriodState.customStartTime!, + end: effectPeriodState.customEndTime!, + loops: effectPeriodState.selectedDaysBinary))); + Navigator.of(context).pop(); + } else { + Navigator.of(context).pop(selectedIcon); + } + }, + child: Container( + alignment: AlignmentDirectional.center, + child: Text( + 'Confirm', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), + ), + ), + ), + ), + ], + ), + ) + ], + ), + ); + }, + ); + }), ), ); }, diff --git a/lib/pages/routiens/widgets/routine_search_and_buttons.dart b/lib/pages/routiens/widgets/routine_search_and_buttons.dart index 5bf9db8e..f7738861 100644 --- a/lib/pages/routiens/widgets/routine_search_and_buttons.dart +++ b/lib/pages/routiens/widgets/routine_search_and_buttons.dart @@ -15,203 +15,203 @@ class RoutineSearchAndButtons extends StatelessWidget { @override Widget build(BuildContext context) { - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return Wrap( - runSpacing: 16, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Expanded( - child: Wrap( - spacing: 12, - runSpacing: 12, - crossAxisAlignment: WrapCrossAlignment.end, - children: [ - ConstrainedBox( - constraints: BoxConstraints( - maxWidth: constraints.maxWidth > 700 - ? 450 - : constraints.maxWidth - 32), - child: StatefulTextField( - title: 'Routine Name', - height: 40, - controller: TextEditingController(), - hintText: 'Please enter the name', - boxDecoration: containerWhiteDecoration, - elevation: 0, - borderRadius: 15, - isRequired: true, - width: 450, - onChanged: (value) { - context - .read() - .add(SetRoutineName(value)); - }, + return BlocBuilder(builder: (context, state) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Wrap( + runSpacing: 16, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: Wrap( + spacing: 12, + runSpacing: 12, + crossAxisAlignment: WrapCrossAlignment.end, + children: [ + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: + constraints.maxWidth > 700 ? 450 : constraints.maxWidth - 32), + child: StatefulTextField( + title: 'Routine Name', + height: 40, + controller: TextEditingController(), + hintText: 'Please enter the name', + boxDecoration: containerWhiteDecoration, + elevation: 0, + borderRadius: 15, + isRequired: true, + width: 450, + onChanged: (value) { + context.read().add(SetRoutineName(value)); + }, + ), ), - ), - (constraints.maxWidth <= 1000) - ? const SizedBox() - : SizedBox( - height: 40, - width: 200, - child: Center( - child: DefaultButton( - onPressed: () async { - final result = - await SettingHelper.showSettingDialog( - context: context, - ); - if (result != null) { - context - .read() - .add(AddSelectedIcon(result)); - } - }, - borderRadius: 15, - elevation: 0, - borderColor: ColorsManager.greyColor, - backgroundColor: ColorsManager.boxColor, - child: const Text( - 'Settings', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - color: ColorsManager.primaryColor, + (constraints.maxWidth <= 1000) + ? const SizedBox() + : SizedBox( + height: 40, + width: 200, + child: Center( + child: DefaultButton( + onPressed: state.isAutomation || state.isTabToRun + ? () async { + final result = await SettingHelper.showSettingDialog( + context: context, + ); + if (result != null) { + context + .read() + .add(AddSelectedIcon(result)); + } + } + : null, + borderRadius: 15, + elevation: 0, + borderColor: ColorsManager.greyColor, + backgroundColor: ColorsManager.boxColor, + child: const Text( + 'Settings', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: ColorsManager.primaryColor, + ), ), ), ), ), - ), - ], + ], + ), ), - ), - if (constraints.maxWidth > 1000) - Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - height: 40, - width: 200, - child: Center( - child: DefaultButton( - onPressed: () {}, - borderRadius: 15, - elevation: 0, - borderColor: ColorsManager.greyColor, - backgroundColor: ColorsManager.boxColor, - child: const Text( - 'Cancel', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - color: ColorsManager.blackColor, + if (constraints.maxWidth > 1000) + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 40, + width: 200, + child: Center( + child: DefaultButton( + onPressed: () {}, + borderRadius: 15, + elevation: 0, + borderColor: ColorsManager.greyColor, + backgroundColor: ColorsManager.boxColor, + child: const Text( + 'Cancel', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: ColorsManager.blackColor, + ), ), ), ), ), - ), - const SizedBox(width: 12), - SizedBox( - height: 40, - width: 200, - child: Center( - child: DefaultButton( - onPressed: () { - SaveRoutineHelper.showSaveRoutineDialog(context); - }, - borderRadius: 15, - elevation: 0, - backgroundColor: ColorsManager.primaryColor, - child: const Text( - 'Save', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - color: ColorsManager.whiteColors, + const SizedBox(width: 12), + SizedBox( + height: 40, + width: 200, + child: Center( + child: DefaultButton( + onPressed: () { + SaveRoutineHelper.showSaveRoutineDialog(context); + }, + borderRadius: 15, + elevation: 0, + backgroundColor: ColorsManager.primaryColor, + child: const Text( + 'Save', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: ColorsManager.whiteColors, + ), ), ), ), ), - ), - ], - ), - ], - ), - if (constraints.maxWidth <= 1000) - Wrap( - runSpacing: 12, - children: [ - SizedBox( - height: 40, - width: 200, - child: Center( - child: DefaultButton( - onPressed: () {}, - borderRadius: 15, - elevation: 0, - borderColor: ColorsManager.greyColor, - backgroundColor: ColorsManager.boxColor, - child: const Text( - 'Settings', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - color: ColorsManager.primaryColor, - ), - ), - ), + ], ), - ), - const SizedBox(width: 12), - SizedBox( - height: 40, - width: 200, - child: Center( - child: DefaultButton( - onPressed: () {}, - borderRadius: 15, - elevation: 0, - borderColor: ColorsManager.greyColor, - backgroundColor: ColorsManager.boxColor, - child: const Text( - 'Cancel', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - color: ColorsManager.blackColor, - ), - ), - ), - ), - ), - const SizedBox(width: 12), - SizedBox( - height: 40, - width: 200, - child: Center( - child: DefaultButton( - onPressed: () {}, - borderRadius: 15, - elevation: 0, - backgroundColor: ColorsManager.primaryColor, - child: const Text( - 'Save', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - color: ColorsManager.whiteColors, - ), - ), - ), - ), - ), ], ), - ], - ); - }, - ); + if (constraints.maxWidth <= 1000) + Wrap( + runSpacing: 12, + children: [ + SizedBox( + height: 40, + width: 200, + child: Center( + child: DefaultButton( + onPressed: () {}, + borderRadius: 15, + elevation: 0, + borderColor: ColorsManager.greyColor, + backgroundColor: ColorsManager.boxColor, + child: const Text( + 'Settings', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: ColorsManager.primaryColor, + ), + ), + ), + ), + ), + const SizedBox(width: 12), + SizedBox( + height: 40, + width: 200, + child: Center( + child: DefaultButton( + onPressed: () {}, + borderRadius: 15, + elevation: 0, + borderColor: ColorsManager.greyColor, + backgroundColor: ColorsManager.boxColor, + child: const Text( + 'Cancel', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: ColorsManager.blackColor, + ), + ), + ), + ), + ), + const SizedBox(width: 12), + SizedBox( + height: 40, + width: 200, + child: Center( + child: DefaultButton( + onPressed: () {}, + borderRadius: 15, + elevation: 0, + backgroundColor: ColorsManager.primaryColor, + child: const Text( + 'Save', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: ColorsManager.whiteColors, + ), + ), + ), + ), + ), + ], + ), + ], + ); + }, + ); + }); } } diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index 9d729327..52749851 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -26,9 +26,7 @@ class ThenContainer extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('THEN', - style: TextStyle( - fontSize: 18, fontWeight: FontWeight.bold)), + const Text('THEN', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 16), Wrap( spacing: 8, @@ -37,16 +35,12 @@ class ThenContainer extends StatelessWidget { state.thenItems.length, (index) => GestureDetector( onTap: () async { - if (state.thenItems[index]['deviceId'] == - 'delay') { - final result = await DelayHelper - .showDelayPickerDialog( - context, state.thenItems[index]); + if (state.thenItems[index]['deviceId'] == 'delay') { + final result = await DelayHelper.showDelayPickerDialog( + context, state.thenItems[index]); if (result != null) { - context - .read() - .add(AddToThenContainer({ + context.read().add(AddToThenContainer({ ...state.thenItems[index], 'imagePath': Assets.delay, 'title': 'Delay', @@ -55,37 +49,32 @@ class ThenContainer extends StatelessWidget { return; } - final result = await DeviceDialogHelper - .showDeviceDialog( - context, state.thenItems[index]); + final result = await DeviceDialogHelper.showDeviceDialog( + context, state.thenItems[index]); if (result != null) { - context.read().add( - AddToThenContainer( - state.thenItems[index])); + context + .read() + .add(AddToThenContainer(state.thenItems[index])); } else if (!['AC', '1G', '2G', '3G'] - .contains(state.thenItems[index] - ['productType'])) { - context.read().add( - AddToThenContainer( - state.thenItems[index])); + .contains(state.thenItems[index]['productType'])) { + context + .read() + .add(AddToThenContainer(state.thenItems[index])); } }, child: DraggableCard( - imagePath: state.thenItems[index] - ['imagePath'] ?? - '', - title: - state.thenItems[index]['title'] ?? '', + imagePath: state.thenItems[index]['imagePath'] ?? '', + title: state.thenItems[index]['title'] ?? '', deviceData: state.thenItems[index], - padding: const EdgeInsets.symmetric( - horizontal: 4, vertical: 8), + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), isFromThen: true, isFromIf: false, onRemove: () { - context.read().add( - RemoveDragCard( - index: index, isFromThen: true)); + context.read().add(RemoveDragCard( + index: index, + isFromThen: true, + key: state.thenItems[index]['uniqueCustomId'])); }, ), ))), @@ -140,8 +129,7 @@ class ThenContainer extends StatelessWidget { } if (mutableData['deviceId'] == 'delay') { - final result = - await DelayHelper.showDelayPickerDialog(context, mutableData); + final result = await DelayHelper.showDelayPickerDialog(context, mutableData); if (result != null) { context.read().add(AddToThenContainer({ @@ -153,13 +141,11 @@ class ThenContainer extends StatelessWidget { return; } - final result = - await DeviceDialogHelper.showDeviceDialog(context, mutableData); + final result = await DeviceDialogHelper.showDeviceDialog(context, mutableData); if (result != null) { context.read().add(AddToThenContainer(mutableData)); - } else if (!['AC', '1G', '2G', '3G'] - .contains(mutableData['productType'])) { + } else if (!['AC', '1G', '2G', '3G'].contains(mutableData['productType'])) { context.read().add(AddToThenContainer(mutableData)); } }, From fbec2fbeaed6f8019f1a6ac4f9a74b7f0af9e350 Mon Sep 17 00:00:00 2001 From: ashraf_personal Date: Wed, 27 Nov 2024 01:11:23 +0300 Subject: [PATCH 30/40] finlizing --- .../view/device_managment_page.dart | 47 +++++++------- .../bloc/routine_bloc/routine_bloc.dart | 6 ++ .../bloc/routine_bloc/routine_event.dart | 3 + .../routine_dialogs/discard_dialog.dart | 62 +++++++++++++++++++ .../widgets/routine_search_and_buttons.dart | 20 +++++- lib/utils/extension/build_context_x.dart | 25 +++++--- 6 files changed, 127 insertions(+), 36 deletions(-) create mode 100644 lib/pages/routiens/widgets/routine_dialogs/discard_dialog.dart diff --git a/lib/pages/device_managment/all_devices/view/device_managment_page.dart b/lib/pages/device_managment/all_devices/view/device_managment_page.dart index 966d5361..ffc57131 100644 --- a/lib/pages/device_managment/all_devices/view/device_managment_page.dart +++ b/lib/pages/device_managment/all_devices/view/device_managment_page.dart @@ -88,32 +88,31 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { ); }), rightBody: const NavigateHomeGridView(), - scaffoldBody: CreateNewRoutineView(), - // BlocBuilder( - // builder: (context, state) { - // if (state is SelectedTabState && state.selectedTab) { - // return const RoutinesView(); - // } - // if (state is ShowCreateRoutineState && state.showCreateRoutine) { - // return const CreateNewRoutineView(); - // } + scaffoldBody: BlocBuilder( + builder: (context, state) { + if (state is SelectedTabState && state.selectedTab) { + return const RoutinesView(); + } + if (state is ShowCreateRoutineState && state.showCreateRoutine) { + return const CreateNewRoutineView(); + } - // return BlocBuilder( - // builder: (context, deviceState) { - // if (deviceState is DeviceManagementLoading) { - // return const Center(child: CircularProgressIndicator()); - // } else if (deviceState is DeviceManagementLoaded || - // deviceState is DeviceManagementFiltered) { - // final devices = (deviceState as dynamic).devices ?? - // (deviceState as DeviceManagementFiltered).filteredDevices; + return BlocBuilder( + builder: (context, deviceState) { + if (deviceState is DeviceManagementLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (deviceState is DeviceManagementLoaded || + deviceState is DeviceManagementFiltered) { + final devices = (deviceState as dynamic).devices ?? + (deviceState as DeviceManagementFiltered).filteredDevices; - // return DeviceManagementBody(devices: devices); - // } else { - // return const Center(child: Text('Error fetching Devices')); - // } - // }, - // ); - // }), + return DeviceManagementBody(devices: devices); + } else { + return const Center(child: Text('Error fetching Devices')); + } + }, + ); + }), ), ); } diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 0a9e6b0a..481b397e 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -29,6 +29,7 @@ class RoutineBloc extends Bloc { on(_onEffectiveTimeEvent); on(_onCreateAutomation); on(_onSetRoutineName); + on(_onResetRoutineState); // on(_onRemoveFunction); // on(_onClearFunctions); } @@ -370,4 +371,9 @@ class RoutineBloc extends Bloc { routineName: null, ); } + + FutureOr _onResetRoutineState( + ResetRoutineState event, Emitter emit) { + emit(_resetState()); + } } diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart index 9337e88e..90ca81ed 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart @@ -121,4 +121,7 @@ class SetRoutineName extends RoutineEvent { @override List get props => [name]; } + +class ResetRoutineState extends RoutineEvent {} + class ClearFunctions extends RoutineEvent {} diff --git a/lib/pages/routiens/widgets/routine_dialogs/discard_dialog.dart b/lib/pages/routiens/widgets/routine_dialogs/discard_dialog.dart new file mode 100644 index 00000000..8b22a671 --- /dev/null +++ b/lib/pages/routiens/widgets/routine_dialogs/discard_dialog.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/common/custom_table.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart'; +import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class DiscardDialog { + static void show(BuildContext context) { + context.customAlertDialog( + alertBody: Container( + height: 150, + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Text( + 'If you close, you will lose all the changes you have made.', + textAlign: TextAlign.center, + style: context.textTheme.bodyMedium!.copyWith( + color: ColorsManager.red, + fontWeight: FontWeight.w400, + fontSize: 14, + ), + ), + const SizedBox( + height: 20, + ), + Text( + 'Are you sure you wish to close?', + style: context.textTheme.bodyMedium!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.grayColor, + ), + ) + ], + )), + title: 'Discard', + titleStyle: context.textTheme.titleLarge!.copyWith( + color: ColorsManager.red, + fontWeight: FontWeight.bold, + ), + onDismissText: "Don’t Close", + onConfirmText: "Close", + onDismissColor: ColorsManager.grayColor, + onConfirmColor: ColorsManager.red.withOpacity(0.8), + onDismiss: () { + Navigator.pop(context); + }, + onConfirm: () { + context.read().add(ResetRoutineState()); + Navigator.pop(context); + BlocProvider.of(context).add( + const CreateNewRoutineViewEvent(false), + ); + BlocProvider.of(context).add( + const TriggerSwitchTabsEvent(true), + ); + }); + } +} diff --git a/lib/pages/routiens/widgets/routine_search_and_buttons.dart b/lib/pages/routiens/widgets/routine_search_and_buttons.dart index 5bf9db8e..026c8702 100644 --- a/lib/pages/routiens/widgets/routine_search_and_buttons.dart +++ b/lib/pages/routiens/widgets/routine_search_and_buttons.dart @@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/helper/save_routine_helper.dart'; +import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/discard_dialog.dart'; import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/setting_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -96,7 +97,9 @@ class RoutineSearchAndButtons extends StatelessWidget { width: 200, child: Center( child: DefaultButton( - onPressed: () {}, + onPressed: () { + DiscardDialog.show(context); + }, borderRadius: 15, elevation: 0, borderColor: ColorsManager.greyColor, @@ -148,7 +151,16 @@ class RoutineSearchAndButtons extends StatelessWidget { width: 200, child: Center( child: DefaultButton( - onPressed: () {}, + onPressed: () async { + final result = await SettingHelper.showSettingDialog( + context: context, + ); + if (result != null) { + context + .read() + .add(AddSelectedIcon(result)); + } + }, borderRadius: 15, elevation: 0, borderColor: ColorsManager.greyColor, @@ -192,7 +204,9 @@ class RoutineSearchAndButtons extends StatelessWidget { width: 200, child: Center( child: DefaultButton( - onPressed: () {}, + onPressed: () { + SaveRoutineHelper.showSaveRoutineDialog(context); + }, borderRadius: 15, elevation: 0, backgroundColor: ColorsManager.primaryColor, diff --git a/lib/utils/extension/build_context_x.dart b/lib/utils/extension/build_context_x.dart index dbdbb347..0abd16a1 100644 --- a/lib/utils/extension/build_context_x.dart +++ b/lib/utils/extension/build_context_x.dart @@ -23,6 +23,11 @@ extension BuildContextExt on BuildContext { VoidCallback? onDismiss, bool? hideConfirmButton, final double? dialogWidth, + TextStyle? titleStyle, + String? onDismissText, + String? onConfirmText, + Color? onDismissColor, + Color? onConfirmColor, }) { showDialog( context: this, @@ -42,10 +47,11 @@ extension BuildContextExt on BuildContext { /// header widget Text( title, - style: context.textTheme.bodyMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - fontWeight: FontWeight.bold, - ), + style: titleStyle ?? + context.textTheme.bodyMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontWeight.bold, + ), ), Padding( padding: const EdgeInsets.symmetric( @@ -79,9 +85,10 @@ extension BuildContextExt on BuildContext { }, child: Center( child: Text( - 'Cancel', - style: context.textTheme.bodyMedium! - .copyWith(color: ColorsManager.greyColor), + onDismissText ?? 'Cancel', + style: context.textTheme.bodyMedium!.copyWith( + color: onDismissColor ?? + ColorsManager.greyColor), ), ), ), @@ -94,9 +101,9 @@ extension BuildContextExt on BuildContext { onTap: onConfirm, child: Center( child: Text( - 'Confirm', + onConfirmText ?? 'Confirm', style: context.textTheme.bodyMedium!.copyWith( - color: + color: onConfirmColor ?? ColorsManager.primaryColorWithOpacity), ), ), From 75aa2042c125c1e0dfc31dff986af90dea821350 Mon Sep 17 00:00:00 2001 From: ashraf_personal Date: Wed, 27 Nov 2024 01:29:15 +0300 Subject: [PATCH 31/40] setting button --- .../widgets/routine_search_and_buttons.dart | 306 +++++++++--------- 1 file changed, 158 insertions(+), 148 deletions(-) diff --git a/lib/pages/routiens/widgets/routine_search_and_buttons.dart b/lib/pages/routiens/widgets/routine_search_and_buttons.dart index fb982a70..73bc403d 100644 --- a/lib/pages/routiens/widgets/routine_search_and_buttons.dart +++ b/lib/pages/routiens/widgets/routine_search_and_buttons.dart @@ -16,50 +16,148 @@ class RoutineSearchAndButtons extends StatelessWidget { @override Widget build(BuildContext context) { - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return Wrap( - runSpacing: 16, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.end, + return BlocBuilder( + builder: (context, state) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Wrap( + runSpacing: 16, children: [ - Expanded( - child: Wrap( - spacing: 12, - runSpacing: 12, - crossAxisAlignment: WrapCrossAlignment.end, - children: [ - ConstrainedBox( - constraints: BoxConstraints( - maxWidth: constraints.maxWidth > 700 - ? 450 - : constraints.maxWidth - 32), - child: StatefulTextField( - title: 'Routine Name', - height: 40, - controller: TextEditingController(), - hintText: 'Please enter the name', - boxDecoration: containerWhiteDecoration, - elevation: 0, - borderRadius: 15, - isRequired: true, - width: 450, - onChanged: (value) { - context - .read() - .add(SetRoutineName(value)); - }, - ), - ), - (constraints.maxWidth <= 1000) - ? const SizedBox() - : SizedBox( + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: Wrap( + spacing: 12, + runSpacing: 12, + crossAxisAlignment: WrapCrossAlignment.end, + children: [ + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: constraints.maxWidth > 700 + ? 450 + : constraints.maxWidth - 32), + child: StatefulTextField( + title: 'Routine Name', height: 40, - width: 200, - child: Center( - child: DefaultButton( - onPressed: () async { + controller: TextEditingController(), + hintText: 'Please enter the name', + boxDecoration: containerWhiteDecoration, + elevation: 0, + borderRadius: 15, + isRequired: true, + width: 450, + onChanged: (value) { + context + .read() + .add(SetRoutineName(value)); + }, + ), + ), + (constraints.maxWidth <= 1000) + ? const SizedBox() + : SizedBox( + height: 40, + width: 200, + child: Center( + child: DefaultButton( + onPressed: state.isAutomation || + state.isTabToRun + ? () async { + final result = await SettingHelper + .showSettingDialog( + context: context, + ); + if (result != null) { + context.read().add( + AddSelectedIcon(result)); + } + } + : null, + borderRadius: 15, + elevation: 0, + borderColor: ColorsManager.greyColor, + backgroundColor: ColorsManager.boxColor, + child: const Text( + 'Settings', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: ColorsManager.primaryColor, + ), + ), + ), + ), + ), + ], + ), + ), + if (constraints.maxWidth > 1000) + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 40, + width: 200, + child: Center( + child: DefaultButton( + onPressed: () { + DiscardDialog.show(context); + }, + borderRadius: 15, + elevation: 0, + borderColor: ColorsManager.greyColor, + backgroundColor: ColorsManager.boxColor, + child: const Text( + 'Cancel', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: ColorsManager.blackColor, + ), + ), + ), + ), + ), + const SizedBox(width: 12), + SizedBox( + height: 40, + width: 200, + child: Center( + child: DefaultButton( + onPressed: () { + SaveRoutineHelper.showSaveRoutineDialog( + context); + }, + borderRadius: 15, + elevation: 0, + backgroundColor: ColorsManager.primaryColor, + child: const Text( + 'Save', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: ColorsManager.whiteColors, + ), + ), + ), + ), + ), + ], + ), + ], + ), + if (constraints.maxWidth <= 1000) + Wrap( + runSpacing: 12, + children: [ + SizedBox( + height: 40, + width: 200, + child: Center( + child: DefaultButton( + onPressed: state.isAutomation || state.isTabToRun + ? () async { final result = await SettingHelper.showSettingDialog( context: context, @@ -69,29 +167,24 @@ class RoutineSearchAndButtons extends StatelessWidget { .read() .add(AddSelectedIcon(result)); } - }, - borderRadius: 15, - elevation: 0, - borderColor: ColorsManager.greyColor, - backgroundColor: ColorsManager.boxColor, - child: const Text( - 'Settings', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - color: ColorsManager.primaryColor, - ), - ), - ), + } + : null, + borderRadius: 15, + elevation: 0, + borderColor: ColorsManager.greyColor, + backgroundColor: ColorsManager.boxColor, + child: const Text( + 'Settings', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: ColorsManager.primaryColor, ), ), - ], - ), - ), - if (constraints.maxWidth > 1000) - Row( - mainAxisSize: MainAxisSize.min, - children: [ + ), + ), + ), + const SizedBox(width: 12), SizedBox( height: 40, width: 200, @@ -141,91 +234,8 @@ class RoutineSearchAndButtons extends StatelessWidget { ], ), ], - ), - if (constraints.maxWidth <= 1000) - Wrap( - runSpacing: 12, - children: [ - SizedBox( - height: 40, - width: 200, - child: Center( - child: DefaultButton( - onPressed: () async { - final result = await SettingHelper.showSettingDialog( - context: context, - ); - if (result != null) { - context - .read() - .add(AddSelectedIcon(result)); - } - }, - borderRadius: 15, - elevation: 0, - borderColor: ColorsManager.greyColor, - backgroundColor: ColorsManager.boxColor, - child: const Text( - 'Settings', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - color: ColorsManager.primaryColor, - ), - ), - ), - ), - ), - const SizedBox(width: 12), - SizedBox( - height: 40, - width: 200, - child: Center( - child: DefaultButton( - onPressed: () { - DiscardDialog.show(context); - }, - borderRadius: 15, - elevation: 0, - borderColor: ColorsManager.greyColor, - backgroundColor: ColorsManager.boxColor, - child: const Text( - 'Cancel', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - color: ColorsManager.blackColor, - ), - ), - ), - ), - ), - const SizedBox(width: 12), - SizedBox( - height: 40, - width: 200, - child: Center( - child: DefaultButton( - onPressed: () { - SaveRoutineHelper.showSaveRoutineDialog(context); - }, - borderRadius: 15, - elevation: 0, - backgroundColor: ColorsManager.primaryColor, - child: const Text( - 'Save', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - color: ColorsManager.whiteColors, - ), - ), - ), - ), - ), - ], - ), - ], + ); + }, ); }, ); From abccaeabf889d7034c0dcb0e003c40fe1add3419 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Wed, 27 Nov 2024 13:40:08 +0300 Subject: [PATCH 32/40] bug fixes --- .../bloc/routine_bloc/routine_bloc.dart | 99 +++++----- .../widgets/routine_search_and_buttons.dart | 174 ++++++++++++++---- .../widgets/scenes_and_automations.dart | 2 +- 3 files changed, 186 insertions(+), 89 deletions(-) diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 5c99d85c..6eb03160 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -38,8 +38,8 @@ class RoutineBloc extends Bloc { final updatedIfItems = List>.from(state.ifItems); // Find the index of the item in teh current itemsList - int index = updatedIfItems.indexWhere( - (map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); + int index = + updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); // Replace the map if the index is valid if (index != -1) { updatedIfItems[index] = event.item; @@ -48,21 +48,18 @@ class RoutineBloc extends Bloc { } if (event.isTabToRun) { - emit(state.copyWith( - ifItems: updatedIfItems, isTabToRun: true, isAutomation: false)); + emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false)); } else { - emit(state.copyWith( - ifItems: updatedIfItems, isTabToRun: false, isAutomation: true)); + emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true)); } } - void _onAddToThenContainer( - AddToThenContainer event, Emitter emit) { + void _onAddToThenContainer(AddToThenContainer event, Emitter emit) { final currentItems = List>.from(state.thenItems); // Find the index of the item in teh current itemsList - int index = currentItems.indexWhere( - (map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); + int index = + currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); // Replace the map if the index is valid if (index != -1) { currentItems[index] = event.item; @@ -73,26 +70,22 @@ class RoutineBloc extends Bloc { emit(state.copyWith(thenItems: currentItems)); } - void _onAddFunctionsToRoutine( - AddFunctionToRoutine event, Emitter emit) { + void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter emit) { try { if (event.functions.isEmpty) return; - List selectedFunction = - List.from(event.functions); + List selectedFunction = List.from(event.functions); Map> currentSelectedFunctions = Map>.from(state.selectedFunctions); if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) { List currentFunctions = - List.from( - currentSelectedFunctions[event.uniqueCustomId] ?? []); + List.from(currentSelectedFunctions[event.uniqueCustomId] ?? []); List functionCode = []; for (int i = 0; i < selectedFunction.length; i++) { for (int j = 0; j < currentFunctions.length; j++) { - if (selectedFunction[i].functionCode == - currentFunctions[j].functionCode) { + if (selectedFunction[i].functionCode == currentFunctions[j].functionCode) { currentFunctions[j] = selectedFunction[i]; if (!functionCode.contains(currentFunctions[j].functionCode)) { functionCode.add(currentFunctions[j].functionCode); @@ -102,15 +95,13 @@ class RoutineBloc extends Bloc { } for (int i = 0; i < functionCode.length; i++) { - selectedFunction - .removeWhere((code) => code.functionCode == functionCode[i]); + selectedFunction.removeWhere((code) => code.functionCode == functionCode[i]); } - currentSelectedFunctions[event.uniqueCustomId] = - List.from(currentFunctions)..addAll(selectedFunction); + currentSelectedFunctions[event.uniqueCustomId] = List.from(currentFunctions) + ..addAll(selectedFunction); } else { - currentSelectedFunctions[event.uniqueCustomId] = - List.from(event.functions); + currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions); } emit(state.copyWith(selectedFunctions: currentSelectedFunctions)); @@ -119,8 +110,7 @@ class RoutineBloc extends Bloc { } } - Future _onLoadScenes( - LoadScenes event, Emitter emit) async { + Future _onLoadScenes(LoadScenes event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { @@ -139,16 +129,24 @@ class RoutineBloc extends Bloc { } } - Future _onLoadAutomation( - LoadAutomation event, Emitter emit) async { + Future _onLoadAutomation(LoadAutomation event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { final automations = await SceneApi.getAutomationByUnitId(event.unitId); - emit(state.copyWith( - automations: automations, - isLoading: false, - )); + if (automations.isNotEmpty) { + emit(state.copyWith( + automations: automations, + isLoading: false, + )); + } else { + emit(state.copyWith( + isLoading: false, + loadAutomationErrorMessage: 'Failed to load automations', + errorMessage: '', + loadScenesErrorMessage: '', + )); + } } catch (e) { emit(state.copyWith( isLoading: false, @@ -159,15 +157,14 @@ class RoutineBloc extends Bloc { } } - FutureOr _onSearchRoutines( - SearchRoutines event, Emitter emit) async { + FutureOr _onSearchRoutines(SearchRoutines event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); await Future.delayed(const Duration(seconds: 1)); + emit(state.copyWith(isLoading: false, errorMessage: null)); emit(state.copyWith(searchText: event.query)); } - FutureOr _onAddSelectedIcon( - AddSelectedIcon event, Emitter emit) { + FutureOr _onAddSelectedIcon(AddSelectedIcon event, Emitter emit) { emit(state.copyWith(selectedIcon: event.icon)); } @@ -176,8 +173,7 @@ class RoutineBloc extends Bloc { return actions.first['deviceId'] == 'delay'; } - Future _onCreateScene( - CreateSceneEvent event, Emitter emit) async { + Future _onCreateScene(CreateSceneEvent event, Emitter emit) async { try { // Check if first action is delay if (_isFirstActionDelay(state.thenItems)) { @@ -252,8 +248,7 @@ class RoutineBloc extends Bloc { } } - Future _onCreateAutomation( - CreateAutomationEvent event, Emitter emit) async { + Future _onCreateAutomation(CreateAutomationEvent event, Emitter emit) async { try { if (state.routineName == null || state.routineName!.isEmpty) { emit(state.copyWith( @@ -353,26 +348,21 @@ class RoutineBloc extends Bloc { } } - FutureOr _onRemoveDragCard( - RemoveDragCard event, Emitter emit) { + FutureOr _onRemoveDragCard(RemoveDragCard event, Emitter emit) { if (event.isFromThen) { final thenItems = List>.from(state.thenItems); - final selectedFunctions = - Map>.from(state.selectedFunctions); + final selectedFunctions = Map>.from(state.selectedFunctions); thenItems.removeAt(event.index); selectedFunctions.remove(event.key); - emit(state.copyWith( - thenItems: thenItems, selectedFunctions: selectedFunctions)); + emit(state.copyWith(thenItems: thenItems, selectedFunctions: selectedFunctions)); } else { final ifItems = List>.from(state.ifItems); - final selectedFunctions = - Map>.from(state.selectedFunctions); + final selectedFunctions = Map>.from(state.selectedFunctions); ifItems.removeAt(event.index); selectedFunctions.remove(event.key); - emit(state.copyWith( - ifItems: ifItems, selectedFunctions: selectedFunctions)); + emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions)); } } @@ -383,13 +373,11 @@ class RoutineBloc extends Bloc { )); } - FutureOr _onEffectiveTimeEvent( - EffectiveTimePeriodEvent event, Emitter emit) { + FutureOr _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter emit) { emit(state.copyWith(effectiveTime: event.effectiveTime)); } - FutureOr _onSetRoutineName( - SetRoutineName event, Emitter emit) { + FutureOr _onSetRoutineName(SetRoutineName event, Emitter emit) { emit(state.copyWith(routineName: event.name)); } @@ -414,8 +402,7 @@ class RoutineBloc extends Bloc { ); } - FutureOr _onResetRoutineState( - ResetRoutineState event, Emitter emit) { + FutureOr _onResetRoutineState(ResetRoutineState event, Emitter emit) { emit(_resetState()); } } diff --git a/lib/pages/routiens/widgets/routine_search_and_buttons.dart b/lib/pages/routiens/widgets/routine_search_and_buttons.dart index 73bc403d..6696225f 100644 --- a/lib/pages/routiens/widgets/routine_search_and_buttons.dart +++ b/lib/pages/routiens/widgets/routine_search_and_buttons.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; -import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/helper/save_routine_helper.dart'; import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/discard_dialog.dart'; import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/setting_dialog.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 RoutineSearchAndButtons extends StatelessWidget { @@ -34,24 +34,73 @@ class RoutineSearchAndButtons extends StatelessWidget { children: [ ConstrainedBox( constraints: BoxConstraints( - maxWidth: constraints.maxWidth > 700 - ? 450 - : constraints.maxWidth - 32), - child: StatefulTextField( - title: 'Routine Name', - height: 40, - controller: TextEditingController(), - hintText: 'Please enter the name', - boxDecoration: containerWhiteDecoration, - elevation: 0, - borderRadius: 15, - isRequired: true, - width: 450, - onChanged: (value) { - context - .read() - .add(SetRoutineName(value)); - }, + maxWidth: + constraints.maxWidth > 700 ? 450 : constraints.maxWidth - 32), + // child: StatefulTextField( + // title: 'Routine Name', + // initialValue: state.routineName ?? '', + // height: 40, + // // controller: TextEditingController(), + // hintText: 'Please enter the name', + // boxDecoration: containerWhiteDecoration, + // elevation: 0, + // borderRadius: 15, + // isRequired: true, + // width: 450, + // onSubmitted: (value) { + // // context.read().add(SetRoutineName(value)); + // }, + // onChanged: (value) { + // context.read().add(SetRoutineName(value)); + // }, + // ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text('* ', + style: context.textTheme.bodyMedium! + .copyWith(color: ColorsManager.red, fontSize: 13)), + Text( + 'Routine Name', + style: context.textTheme.bodyMedium!.copyWith( + fontSize: 13, + fontWeight: FontWeight.w600, + color: ColorsManager.blackColor, + ), + ), + ], + ), + Container( + width: 450, + height: 40, + decoration: containerWhiteDecoration, + child: TextFormField( + style: context.textTheme.bodyMedium! + .copyWith(color: ColorsManager.blackColor), + initialValue: state.routineName, + decoration: InputDecoration( + hintText: 'Please enter the name', + hintStyle: context.textTheme.bodyMedium! + .copyWith(fontSize: 12, color: ColorsManager.grayColor), + contentPadding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + border: InputBorder.none, + ), + onChanged: (value) { + context.read().add(SetRoutineName(value)); + }, + validator: (value) { + if (value == null || value.isEmpty) { + return 'This field is required'; + } + return null; + }, + ), + ), + ], ), ), (constraints.maxWidth <= 1000) @@ -61,16 +110,15 @@ class RoutineSearchAndButtons extends StatelessWidget { width: 200, child: Center( child: DefaultButton( - onPressed: state.isAutomation || - state.isTabToRun + onPressed: state.isAutomation || state.isTabToRun ? () async { - final result = await SettingHelper - .showSettingDialog( + final result = await SettingHelper.showSettingDialog( context: context, ); if (result != null) { - context.read().add( - AddSelectedIcon(result)); + context + .read() + .add(AddSelectedIcon(result)); } } : null, @@ -126,8 +174,40 @@ class RoutineSearchAndButtons extends StatelessWidget { child: Center( child: DefaultButton( onPressed: () { - SaveRoutineHelper.showSaveRoutineDialog( - context); + if (state.routineName == null || state.routineName!.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text('Please enter the routine name'), + duration: const Duration(seconds: 2), + backgroundColor: ColorsManager.red, + action: SnackBarAction( + label: 'Dismiss', + onPressed: () { + // Optional action on Snackbar + }, + ), + ), + ); + return; + } + + if (state.ifItems.isEmpty || state.thenItems.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text('Please add if and then condition'), + duration: const Duration(seconds: 2), + backgroundColor: ColorsManager.red, + action: SnackBarAction( + label: 'Dismiss', + onPressed: () { + // Optional action on Snackbar + }, + ), + ), + ); + return; + } + SaveRoutineHelper.showSaveRoutineDialog(context); }, borderRadius: 15, elevation: 0, @@ -158,14 +238,11 @@ class RoutineSearchAndButtons extends StatelessWidget { child: DefaultButton( onPressed: state.isAutomation || state.isTabToRun ? () async { - final result = - await SettingHelper.showSettingDialog( + final result = await SettingHelper.showSettingDialog( context: context, ); if (result != null) { - context - .read() - .add(AddSelectedIcon(result)); + context.read().add(AddSelectedIcon(result)); } } : null, @@ -215,6 +292,39 @@ class RoutineSearchAndButtons extends StatelessWidget { child: Center( child: DefaultButton( onPressed: () { + if (state.routineName == null || state.routineName!.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text('Please enter the routine name'), + duration: const Duration(seconds: 2), + backgroundColor: ColorsManager.red, + action: SnackBarAction( + label: 'Dismiss', + onPressed: () { + // Optional action on Snackbar + }, + ), + ), + ); + return; + } + + if (state.ifItems.isEmpty || state.thenItems.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text('Please add if and then condition'), + duration: const Duration(seconds: 2), + backgroundColor: ColorsManager.red, + action: SnackBarAction( + label: 'Dismiss', + onPressed: () { + // Optional action on Snackbar + }, + ), + ), + ); + return; + } SaveRoutineHelper.showSaveRoutineDialog(context); }, borderRadius: 15, diff --git a/lib/pages/routiens/widgets/scenes_and_automations.dart b/lib/pages/routiens/widgets/scenes_and_automations.dart index 746795af..36ee7db2 100644 --- a/lib/pages/routiens/widgets/scenes_and_automations.dart +++ b/lib/pages/routiens/widgets/scenes_and_automations.dart @@ -26,7 +26,7 @@ class _ScenesAndAutomationsState extends State { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - if (state.scenes.isNotEmpty || state.automations.isNotEmpty) { + if (!state.isLoading) { var scenes = [...state.scenes, ...state.automations]; return Wrap( spacing: 10, From db7bec764190742eb106a3e5e8b7f3bc7a31d3ab Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Wed, 27 Nov 2024 18:42:30 +0300 Subject: [PATCH 33/40] stash changes --- .../bloc/routine_bloc/routine_bloc.dart | 81 +++++-------- .../bloc/routine_bloc/routine_state.dart | 9 +- lib/pages/routiens/view/routines_view.dart | 61 ++++------ .../fetch_routine_scenes_automation.dart | 109 ++++++++++++++++++ .../main_routine_view/routine_view_card.dart | 79 +++++++++++++ 5 files changed, 241 insertions(+), 98 deletions(-) create mode 100644 lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart create mode 100644 lib/pages/routiens/widgets/main_routine_view/routine_view_card.dart diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 5c99d85c..9a780be8 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -38,8 +38,7 @@ class RoutineBloc extends Bloc { final updatedIfItems = List>.from(state.ifItems); // Find the index of the item in teh current itemsList - int index = updatedIfItems.indexWhere( - (map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); + int index = updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); // Replace the map if the index is valid if (index != -1) { updatedIfItems[index] = event.item; @@ -48,21 +47,17 @@ class RoutineBloc extends Bloc { } if (event.isTabToRun) { - emit(state.copyWith( - ifItems: updatedIfItems, isTabToRun: true, isAutomation: false)); + emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false)); } else { - emit(state.copyWith( - ifItems: updatedIfItems, isTabToRun: false, isAutomation: true)); + emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true)); } } - void _onAddToThenContainer( - AddToThenContainer event, Emitter emit) { + void _onAddToThenContainer(AddToThenContainer event, Emitter emit) { final currentItems = List>.from(state.thenItems); // Find the index of the item in teh current itemsList - int index = currentItems.indexWhere( - (map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); + int index = currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); // Replace the map if the index is valid if (index != -1) { currentItems[index] = event.item; @@ -73,26 +68,22 @@ class RoutineBloc extends Bloc { emit(state.copyWith(thenItems: currentItems)); } - void _onAddFunctionsToRoutine( - AddFunctionToRoutine event, Emitter emit) { + void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter emit) { try { if (event.functions.isEmpty) return; - List selectedFunction = - List.from(event.functions); + List selectedFunction = List.from(event.functions); Map> currentSelectedFunctions = Map>.from(state.selectedFunctions); if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) { List currentFunctions = - List.from( - currentSelectedFunctions[event.uniqueCustomId] ?? []); + List.from(currentSelectedFunctions[event.uniqueCustomId] ?? []); List functionCode = []; for (int i = 0; i < selectedFunction.length; i++) { for (int j = 0; j < currentFunctions.length; j++) { - if (selectedFunction[i].functionCode == - currentFunctions[j].functionCode) { + if (selectedFunction[i].functionCode == currentFunctions[j].functionCode) { currentFunctions[j] = selectedFunction[i]; if (!functionCode.contains(currentFunctions[j].functionCode)) { functionCode.add(currentFunctions[j].functionCode); @@ -102,15 +93,12 @@ class RoutineBloc extends Bloc { } for (int i = 0; i < functionCode.length; i++) { - selectedFunction - .removeWhere((code) => code.functionCode == functionCode[i]); + selectedFunction.removeWhere((code) => code.functionCode == functionCode[i]); } - currentSelectedFunctions[event.uniqueCustomId] = - List.from(currentFunctions)..addAll(selectedFunction); + currentSelectedFunctions[event.uniqueCustomId] = List.from(currentFunctions)..addAll(selectedFunction); } else { - currentSelectedFunctions[event.uniqueCustomId] = - List.from(event.functions); + currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions); } emit(state.copyWith(selectedFunctions: currentSelectedFunctions)); @@ -119,8 +107,7 @@ class RoutineBloc extends Bloc { } } - Future _onLoadScenes( - LoadScenes event, Emitter emit) async { + Future _onLoadScenes(LoadScenes event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { @@ -139,8 +126,7 @@ class RoutineBloc extends Bloc { } } - Future _onLoadAutomation( - LoadAutomation event, Emitter emit) async { + Future _onLoadAutomation(LoadAutomation event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { @@ -159,15 +145,13 @@ class RoutineBloc extends Bloc { } } - FutureOr _onSearchRoutines( - SearchRoutines event, Emitter emit) async { + FutureOr _onSearchRoutines(SearchRoutines event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); await Future.delayed(const Duration(seconds: 1)); emit(state.copyWith(searchText: event.query)); } - FutureOr _onAddSelectedIcon( - AddSelectedIcon event, Emitter emit) { + FutureOr _onAddSelectedIcon(AddSelectedIcon event, Emitter emit) { emit(state.copyWith(selectedIcon: event.icon)); } @@ -176,8 +160,7 @@ class RoutineBloc extends Bloc { return actions.first['deviceId'] == 'delay'; } - Future _onCreateScene( - CreateSceneEvent event, Emitter emit) async { + Future _onCreateScene(CreateSceneEvent event, Emitter emit) async { try { // Check if first action is delay if (_isFirstActionDelay(state.thenItems)) { @@ -252,8 +235,7 @@ class RoutineBloc extends Bloc { } } - Future _onCreateAutomation( - CreateAutomationEvent event, Emitter emit) async { + Future _onCreateAutomation(CreateAutomationEvent event, Emitter emit) async { try { if (state.routineName == null || state.routineName!.isEmpty) { emit(state.copyWith( @@ -353,43 +335,35 @@ class RoutineBloc extends Bloc { } } - FutureOr _onRemoveDragCard( - RemoveDragCard event, Emitter emit) { + FutureOr _onRemoveDragCard(RemoveDragCard event, Emitter emit) { if (event.isFromThen) { final thenItems = List>.from(state.thenItems); - final selectedFunctions = - Map>.from(state.selectedFunctions); + final selectedFunctions = Map>.from(state.selectedFunctions); thenItems.removeAt(event.index); selectedFunctions.remove(event.key); - emit(state.copyWith( - thenItems: thenItems, selectedFunctions: selectedFunctions)); + emit(state.copyWith(thenItems: thenItems, selectedFunctions: selectedFunctions)); } else { final ifItems = List>.from(state.ifItems); - final selectedFunctions = - Map>.from(state.selectedFunctions); + final selectedFunctions = Map>.from(state.selectedFunctions); ifItems.removeAt(event.index); selectedFunctions.remove(event.key); - emit(state.copyWith( - ifItems: ifItems, selectedFunctions: selectedFunctions)); + emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions)); } } - FutureOr _changeOperatorOperator( - ChangeAutomationOperator event, Emitter emit) { + FutureOr _changeOperatorOperator(ChangeAutomationOperator event, Emitter emit) { emit(state.copyWith( selectedAutomationOperator: event.operator, )); } - FutureOr _onEffectiveTimeEvent( - EffectiveTimePeriodEvent event, Emitter emit) { + FutureOr _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter emit) { emit(state.copyWith(effectiveTime: event.effectiveTime)); } - FutureOr _onSetRoutineName( - SetRoutineName event, Emitter emit) { + FutureOr _onSetRoutineName(SetRoutineName event, Emitter emit) { emit(state.copyWith(routineName: event.name)); } @@ -414,8 +388,7 @@ class RoutineBloc extends Bloc { ); } - FutureOr _onResetRoutineState( - ResetRoutineState event, Emitter emit) { + FutureOr _onResetRoutineState(ResetRoutineState event, Emitter emit) { emit(_resetState()); } } diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart index 42f584ba..b0689f0d 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart @@ -67,15 +67,12 @@ class RoutineState extends Equatable { errorMessage: errorMessage ?? this.errorMessage, routineName: routineName ?? this.routineName, selectedIcon: selectedIcon ?? this.selectedIcon, - loadScenesErrorMessage: - loadScenesErrorMessage ?? this.loadScenesErrorMessage, - loadAutomationErrorMessage: - loadAutomationErrorMessage ?? this.loadAutomationErrorMessage, + loadScenesErrorMessage: loadScenesErrorMessage ?? this.loadScenesErrorMessage, + loadAutomationErrorMessage: loadAutomationErrorMessage ?? this.loadAutomationErrorMessage, searchText: searchText ?? this.searchText, isTabToRun: isTabToRun ?? this.isTabToRun, isAutomation: isAutomation ?? this.isAutomation, - selectedAutomationOperator: - selectedAutomationOperator ?? this.selectedAutomationOperator, + selectedAutomationOperator: selectedAutomationOperator ?? this.selectedAutomationOperator, effectiveTime: effectiveTime ?? this.effectiveTime, ); } diff --git a/lib/pages/routiens/view/routines_view.dart b/lib/pages/routiens/view/routines_view.dart index 27690a9c..05560b2b 100644 --- a/lib/pages/routiens/view/routines_view.dart +++ b/lib/pages/routiens/view/routines_view.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart'; +import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart'; +import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/routine_view_card.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class RoutinesView extends StatelessWidget { @@ -23,48 +26,30 @@ class RoutinesView extends StatelessWidget { children: [ Text( "Create New Routines", - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.bold, + style: Theme.of(context).textTheme.titleLarge?.copyWith( color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, ), ), - SizedBox( - height: 200, - width: 150, - child: GestureDetector( - onTap: () { - BlocProvider.of(context).add( - const CreateNewRoutineViewEvent(true), - ); - }, - child: Card( - elevation: 3, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - color: ColorsManager.whiteColors, - child: Center( - child: Container( - decoration: BoxDecoration( - color: ColorsManager.graysColor, - borderRadius: BorderRadius.circular(120), - border: Border.all( - color: ColorsManager.greyColor, - width: 2.0, - ), - ), - height: 70, - width: 70, - child: Icon( - Icons.add, - color: ColorsManager.dialogBlueTitle, - size: 40, - ), - ), - ), - ), - ), + const SizedBox( + height: 10, ), + RoutineViewCard( + onTap: () { + BlocProvider.of(context).add( + const CreateNewRoutineViewEvent(true), + ); + context.read().add( + (ResetRoutineState()), + ); + }, + icon: Icons.add, + textString: '', + ), + const SizedBox( + height: 30, + ), + const FetchRoutineScenesAutomation(), const Spacer(), ], ), diff --git a/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart b/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart new file mode 100644 index 00000000..82c83c3c --- /dev/null +++ b/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/routine_view_card.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class FetchRoutineScenesAutomation extends StatefulWidget { + const FetchRoutineScenesAutomation({super.key}); + + @override + State createState() => _FetchRoutineScenesState(); +} + +class _FetchRoutineScenesState extends State { + @override + void initState() { + super.initState(); + context.read() + ..add(const LoadScenes(spaceId)) + ..add(const LoadAutomation(spaceId)); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + debugPrint(state.scenes.toString()); + debugPrint(state.automations.toString()); + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Scenes (Tab to Run)", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox( + height: 10, + ), + state.loadScenesErrorMessage != null + ? Text(state.loadScenesErrorMessage ?? '') + : state.scenes.isNotEmpty + ? SizedBox( + height: 200, + child: ListView.builder( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + itemCount: state.scenes.length, + itemBuilder: (context, index) { + return RoutineViewCard( + onTap: () {}, + textString: state.scenes[index].name, + icon: state.scenes[index].icon ?? Assets.logoHorizontal, + ); + }, + ), + ) + : const CircularProgressIndicator(), + const SizedBox( + height: 30, + ), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Automations", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox( + height: 10, + ), + state.loadAutomationErrorMessage != null + ? Text(state.loadAutomationErrorMessage ?? '') + : state.automations.isNotEmpty + ? Expanded( + child: SizedBox( + height: 200, + child: ListView.builder( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + itemCount: state.automations.length, + itemBuilder: (context, index) { + return RoutineViewCard( + onTap: () {}, + textString: state.automations[index].name, + icon: Assets.automation, + ); + }, + ), + ), + ) + : const CircularProgressIndicator(), + ], + ) + ], + ); + }, + ); + } +} diff --git a/lib/pages/routiens/widgets/main_routine_view/routine_view_card.dart b/lib/pages/routiens/widgets/main_routine_view/routine_view_card.dart new file mode 100644 index 00000000..6c9f16f6 --- /dev/null +++ b/lib/pages/routiens/widgets/main_routine_view/routine_view_card.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class RoutineViewCard extends StatelessWidget { + const RoutineViewCard({ + super.key, + required this.onTap, + required this.icon, + required this.textString, + }); + + final Function() onTap; + final dynamic icon; + final String textString; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 200, + width: 150, + child: GestureDetector( + onTap: () { + onTap(); + }, + child: Card( + elevation: 3, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + color: ColorsManager.whiteColors, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Center( + child: Container( + decoration: BoxDecoration( + color: ColorsManager.graysColor, + borderRadius: BorderRadius.circular(120), + border: Border.all( + color: ColorsManager.greyColor, + width: 2.0, + ), + ), + height: 70, + width: 70, + child: (icon is String) && icon.contains('.svg') + ? SvgPicture.asset(icon) + : Icon( + icon, + color: ColorsManager.dialogBlueTitle, + size: 40, + ), + ), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 3), + child: Text( + textString, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontSize: 12, + ), + ), + ), + ], + ), + ), + ), + ); + } +} From cf817fd5dcc572799b0ee5b34db2e1902005dbac Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Wed, 27 Nov 2024 19:41:43 +0300 Subject: [PATCH 34/40] push display scenes and automations --- .../view/device_managment_page.dart | 17 +- .../widgets/device_search_filters.dart | 8 - .../bloc/routine_bloc/routine_bloc.dart | 5 +- .../bloc/routine_bloc/routine_event.dart | 5 +- .../create_automation_model.dart | 172 ------------------ lib/pages/routiens/models/routine_model.dart | 4 +- lib/pages/routiens/view/routines_view.dart | 3 +- .../fetch_routine_scenes_automation.dart | 155 ++++++++-------- .../main_routine_view/routine_view_card.dart | 12 +- .../routine_dialogs/discard_dialog.dart | 2 - .../widgets/scenes_and_automations.dart | 6 +- lib/services/routines_api.dart | 32 +++- lib/utils/constants/api_const.dart | 2 + 13 files changed, 141 insertions(+), 282 deletions(-) diff --git a/lib/pages/device_managment/all_devices/view/device_managment_page.dart b/lib/pages/device_managment/all_devices/view/device_managment_page.dart index ffc57131..06adfd6c 100644 --- a/lib/pages/device_managment/all_devices/view/device_managment_page.dart +++ b/lib/pages/device_managment/all_devices/view/device_managment_page.dart @@ -18,13 +18,13 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ - BlocProvider( - create: (context) => DeviceManagementBloc()..add(FetchDevices()), - ), BlocProvider( create: (context) => SwitchTabsBloc()..add(const TriggerSwitchTabsEvent(false)), ), + BlocProvider( + create: (context) => DeviceManagementBloc()..add(FetchDevices()), + ), ], child: WebScaffold( appBarTitle: FittedBox( @@ -101,12 +101,11 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { builder: (context, deviceState) { if (deviceState is DeviceManagementLoading) { return const Center(child: CircularProgressIndicator()); - } else if (deviceState is DeviceManagementLoaded || - deviceState is DeviceManagementFiltered) { - final devices = (deviceState as dynamic).devices ?? - (deviceState as DeviceManagementFiltered).filteredDevices; - - return DeviceManagementBody(devices: devices); + } else if (deviceState is DeviceManagementLoaded) { + return DeviceManagementBody(devices: deviceState.devices); + } else if (deviceState is DeviceManagementFiltered) { + return DeviceManagementBody( + devices: deviceState.filteredDevices); } else { return const Center(child: Text('Error fetching Devices')); } diff --git a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart index e3bec220..b9e36c25 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart @@ -18,14 +18,6 @@ class _DeviceSearchFiltersState extends State final TextEditingController unitNameController = TextEditingController(); final TextEditingController productNameController = TextEditingController(); - @override - void dispose() { - communityController.dispose(); - unitNameController.dispose(); - productNameController.dispose(); - super.dispose(); - } - @override Widget build(BuildContext context) { return isExtraLargeScreenSize(context) diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index dd76e01a..8506171b 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -13,6 +13,7 @@ part 'routine_event.dart'; part 'routine_state.dart'; const spaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6'; +const communityId = '25c96044-fadf-44bb-93c7-3c079e527ce6'; class RoutineBloc extends Bloc { RoutineBloc() : super(const RoutineState()) { @@ -114,7 +115,7 @@ class RoutineBloc extends Bloc { emit(state.copyWith(isLoading: true, errorMessage: null)); try { - final scenes = await SceneApi.getScenesByUnitId(event.unitId); + final scenes = await SceneApi.getScenesByUnitId(event.unitId, event.communityId); emit(state.copyWith( scenes: scenes, isLoading: false, @@ -233,7 +234,7 @@ class RoutineBloc extends Bloc { final result = await SceneApi.createScene(createSceneModel); if (result['success']) { emit(_resetState()); - add(const LoadScenes(spaceId)); + add(const LoadScenes(spaceId, communityId)); } else { emit(state.copyWith( isLoading: false, diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart index 575f199d..909e458d 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart @@ -28,11 +28,12 @@ class AddToThenContainer extends RoutineEvent { class LoadScenes extends RoutineEvent { final String unitId; + final String communityId; - const LoadScenes(this.unitId); + const LoadScenes(this.unitId, this.communityId); @override - List get props => [unitId]; + List get props => [unitId, communityId]; } class LoadAutomation extends RoutineEvent { diff --git a/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart b/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart index cd9a9c60..c02e5dab 100644 --- a/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart +++ b/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart @@ -1,177 +1,5 @@ import 'dart:convert'; -import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart'; - -// class CreateAutomationModel { -// String unitUuid; -// String automationName; -// String decisionExpr; -// EffectiveTime effectiveTime; -// List conditions; -// List actions; - -// CreateAutomationModel({ -// required this.unitUuid, -// required this.automationName, -// required this.decisionExpr, -// required this.effectiveTime, -// required this.conditions, -// required this.actions, -// }); - -// CreateAutomationModel copyWith({ -// String? unitUuid, -// String? automationName, -// String? decisionExpr, -// EffectiveTime? effectiveTime, -// List? conditions, -// List? actions, -// }) { -// return CreateAutomationModel( -// unitUuid: unitUuid ?? this.unitUuid, -// automationName: automationName ?? this.automationName, -// decisionExpr: decisionExpr ?? this.decisionExpr, -// effectiveTime: effectiveTime ?? this.effectiveTime, -// conditions: conditions ?? this.conditions, -// actions: actions ?? this.actions, -// ); -// } - -// Map toMap([String? automationId]) { -// return { -// if (automationId == null) 'spaceUuid': unitUuid, -// 'automationName': automationName, -// 'decisionExpr': decisionExpr, -// 'effectiveTime': effectiveTime.toMap(), -// 'conditions': conditions.map((x) => x.toMap()).toList(), -// 'actions': actions.map((x) => x.toMap()).toList(), -// }; -// } - -// factory CreateAutomationModel.fromMap(Map map) { -// return CreateAutomationModel( -// unitUuid: map['spaceUuid'] ?? '', -// automationName: map['automationName'] ?? '', -// decisionExpr: map['decisionExpr'] ?? '', -// effectiveTime: EffectiveTime.fromMap(map['effectiveTime']), -// conditions: List.from( -// map['conditions']?.map((x) => CreateCondition.fromMap(x))), -// actions: List.from( -// map['actions']?.map((x) => CreateSceneAction.fromMap(x))), -// ); -// } - -// String toJson([String? automationId]) => json.encode(toMap(automationId)); - -// factory CreateAutomationModel.fromJson(String source) => -// CreateAutomationModel.fromMap(json.decode(source)); - -// @override -// String toString() { -// return 'CreateAutomationModel(unitUuid: $unitUuid, automationName: $automationName, decisionExpr: $decisionExpr, effectiveTime: $effectiveTime, conditions: $conditions, actions: $actions)'; -// } -// } - -// class EffectiveTime { -// String start; -// String end; -// String loops; - -// EffectiveTime({ -// required this.start, -// required this.end, -// required this.loops, -// }); - -// Map toMap() { -// return { -// 'start': start, -// 'end': end, -// 'loops': loops, -// }; -// } - -// factory EffectiveTime.fromMap(Map map) { -// return EffectiveTime( -// start: map['start'] ?? '', -// end: map['end'] ?? '', -// loops: map['loops'] ?? '', -// ); -// } - -// @override -// String toString() => 'EffectiveTime(start: $start, end: $end, loops: $loops)'; -// } - -// class CreateCondition { -// int code; -// String entityId; -// String entityType; -// ConditionExpr expr; - -// CreateCondition({ -// required this.code, -// required this.entityId, -// required this.entityType, -// required this.expr, -// }); - -// Map toMap() { -// return { -// 'code': code, -// 'entityId': entityId, -// 'entityType': entityType, -// 'expr': expr.toMap(), -// }; -// } - -// factory CreateCondition.fromMap(Map map) { -// return CreateCondition( -// code: map['code'] ?? 0, -// entityId: map['entityId'] ?? '', -// entityType: map['entityType'] ?? '', -// expr: ConditionExpr.fromMap(map['expr']), -// ); -// } - -// @override -// String toString() => -// 'CreateCondition(code: $code, entityId: $entityId, entityType: $entityType, expr: $expr)'; -// } - -// class ConditionExpr { -// String statusCode; -// String comparator; -// dynamic statusValue; - -// ConditionExpr({ -// required this.statusCode, -// required this.comparator, -// required this.statusValue, -// }); - -// Map toMap() { -// return { -// 'statusCode': statusCode, -// 'comparator': comparator, -// 'statusValue': statusValue, -// }; -// } - -// factory ConditionExpr.fromMap(Map map) { -// return ConditionExpr( -// statusCode: map['statusCode'] ?? '', -// comparator: map['comparator'] ?? '', -// statusValue: map['statusValue'], -// ); -// } - -// @override -// String toString() => -// 'ConditionExpr(statusCode: $statusCode, comparator: $comparator, statusValue: $statusValue)'; -// } -import 'dart:convert'; - class CreateAutomationModel { String spaceUuid; String automationName; diff --git a/lib/pages/routiens/models/routine_model.dart b/lib/pages/routiens/models/routine_model.dart index e2075579..3ee7bd03 100644 --- a/lib/pages/routiens/models/routine_model.dart +++ b/lib/pages/routiens/models/routine_model.dart @@ -22,9 +22,9 @@ class ScenesModel { name: json["name"] ?? '', status: json["status"] ?? '', type: json["type"] ?? '', - icon: (isAutomation ?? false) + icon: isAutomation == true ? Assets.automation - : json["icon"] as String?, + : (json["icon"] as String?), ); Map toJson() => { diff --git a/lib/pages/routiens/view/routines_view.dart b/lib/pages/routiens/view/routines_view.dart index 05560b2b..f2dac85f 100644 --- a/lib/pages/routiens/view/routines_view.dart +++ b/lib/pages/routiens/view/routines_view.dart @@ -49,8 +49,7 @@ class RoutinesView extends StatelessWidget { const SizedBox( height: 30, ), - const FetchRoutineScenesAutomation(), - const Spacer(), + const Expanded(child: FetchRoutineScenesAutomation()), ], ), ); diff --git a/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart b/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart index 82c83c3c..81dfa13b 100644 --- a/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart +++ b/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart @@ -4,12 +4,14 @@ import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/routine_view_card.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; class FetchRoutineScenesAutomation extends StatefulWidget { const FetchRoutineScenesAutomation({super.key}); @override - State createState() => _FetchRoutineScenesState(); + State createState() => + _FetchRoutineScenesState(); } class _FetchRoutineScenesState extends State { @@ -17,7 +19,7 @@ class _FetchRoutineScenesState extends State { void initState() { super.initState(); context.read() - ..add(const LoadScenes(spaceId)) + ..add(const LoadScenes(spaceId, communityId)) ..add(const LoadAutomation(spaceId)); } @@ -25,83 +27,84 @@ class _FetchRoutineScenesState extends State { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - debugPrint(state.scenes.toString()); - debugPrint(state.automations.toString()); - - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Scenes (Tab to Run)", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox( - height: 10, - ), - state.loadScenesErrorMessage != null - ? Text(state.loadScenesErrorMessage ?? '') - : state.scenes.isNotEmpty - ? SizedBox( - height: 200, - child: ListView.builder( - shrinkWrap: true, - scrollDirection: Axis.horizontal, - itemCount: state.scenes.length, - itemBuilder: (context, index) { - return RoutineViewCard( - onTap: () {}, - textString: state.scenes[index].name, - icon: state.scenes[index].icon ?? Assets.logoHorizontal, - ); - }, - ), - ) - : const CircularProgressIndicator(), - const SizedBox( - height: 30, - ), - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + return SizedBox( + height: 500, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (state.isLoading) + const Center( + child: CircularProgressIndicator(), + ), + Text( + "Scenes (Tab to Run)", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + if (state.scenes.isEmpty) Text( - "Automations", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.bold, - ), + "No scenes found", + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.grayColor, + ), ), - const SizedBox( - height: 10, + if (state.scenes.isNotEmpty) + SizedBox( + height: 200, + child: ListView.builder( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + itemCount: state.scenes.length, + itemBuilder: (context, index) { + return RoutineViewCard( + onTap: () {}, + textString: state.scenes[index].name, + icon: state.scenes[index].icon ?? Assets.logoHorizontal, + ); + }, + ), ), - state.loadAutomationErrorMessage != null - ? Text(state.loadAutomationErrorMessage ?? '') - : state.automations.isNotEmpty - ? Expanded( - child: SizedBox( - height: 200, - child: ListView.builder( - shrinkWrap: true, - scrollDirection: Axis.horizontal, - itemCount: state.automations.length, - itemBuilder: (context, index) { - return RoutineViewCard( - onTap: () {}, - textString: state.automations[index].name, - icon: Assets.automation, - ); - }, - ), - ), - ) - : const CircularProgressIndicator(), - ], - ) - ], + const SizedBox(height: 30), + Text( + "Automations", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + if (state.automations.isEmpty) + Text( + "No automations found", + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.grayColor, + ), + ), + if (state.automations.isNotEmpty) + SizedBox( + height: 200, + child: ListView.builder( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + itemCount: state.automations.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.only(right: 8.0), + child: RoutineViewCard( + onTap: () {}, + textString: state.automations[index].name, + icon: state.automations[index].icon ?? + Assets.automation, + ), + ); + }, + ), + ), + ], + ), ); }, ); diff --git a/lib/pages/routiens/widgets/main_routine_view/routine_view_card.dart b/lib/pages/routiens/widgets/main_routine_view/routine_view_card.dart index 6c9f16f6..9d8b2d19 100644 --- a/lib/pages/routiens/widgets/main_routine_view/routine_view_card.dart +++ b/lib/pages/routiens/widgets/main_routine_view/routine_view_card.dart @@ -47,8 +47,16 @@ class RoutineViewCard extends StatelessWidget { ), height: 70, width: 70, - child: (icon is String) && icon.contains('.svg') - ? SvgPicture.asset(icon) + child: (icon is String) + ? icon.endsWith('.svg') + ? SvgPicture.asset( + icon, + fit: BoxFit.contain, + ) + : Image.asset( + icon, + fit: BoxFit.contain, + ) : Icon( icon, color: ColorsManager.dialogBlueTitle, diff --git a/lib/pages/routiens/widgets/routine_dialogs/discard_dialog.dart b/lib/pages/routiens/widgets/routine_dialogs/discard_dialog.dart index 8b22a671..4c8e4688 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/discard_dialog.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/discard_dialog.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/common/custom_table.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart'; -import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; diff --git a/lib/pages/routiens/widgets/scenes_and_automations.dart b/lib/pages/routiens/widgets/scenes_and_automations.dart index 36ee7db2..a0bd1ed6 100644 --- a/lib/pages/routiens/widgets/scenes_and_automations.dart +++ b/lib/pages/routiens/widgets/scenes_and_automations.dart @@ -18,7 +18,7 @@ class _ScenesAndAutomationsState extends State { void initState() { super.initState(); context.read() - ..add(const LoadScenes(spaceId)) + ..add(const LoadScenes(spaceId, communityId)) ..add(const LoadAutomation(spaceId)); } @@ -34,7 +34,9 @@ class _ScenesAndAutomationsState extends State { children: scenes.asMap().entries.map((entry) { final scene = entry.value; if (state.searchText != null && state.searchText!.isNotEmpty) { - return scene.name.toLowerCase().contains(state.searchText!.toLowerCase()) + return scene.name + .toLowerCase() + .contains(state.searchText!.toLowerCase()) ? DraggableCard( imagePath: scene.icon ?? Assets.loginLogo, title: scene.name, diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index 756d533e..048b6d35 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -69,14 +69,21 @@ class SceneApi { //get scene by unit id - static Future> getScenesByUnitId(String unitId) async { + static Future> getScenesByUnitId( + String unitId, String communityId, + {showInDevice = false}) async { try { final response = await _httpService.get( - path: ApiEndpoints.getSpaceScenes.replaceAll('{unitUuid}', unitId), + path: ApiEndpoints.getUnitScenes + .replaceAll('{spaceUuid}', unitId) + .replaceAll('{communityUuid}', communityId), + queryParameters: {'showInHomePage': showInDevice}, showServerMessage: false, expectedResponseModel: (json) { + final scenesJson = json['data'] as List; + List scenes = []; - for (var scene in json) { + for (var scene in scenesJson) { scenes.add(ScenesModel.fromJson(scene)); } return scenes; @@ -88,6 +95,25 @@ class SceneApi { } } + // static Future> getScenesByUnitId(String unitId) async { + // try { + // final response = await _httpService.get( + // path: ApiEndpoints.getSpaceScenes.replaceAll('{unitUuid}', unitId), + // showServerMessage: false, + // expectedResponseModel: (json) { + // List scenes = []; + // for (var scene in json) { + // scenes.add(ScenesModel.fromJson(scene)); + // } + // return scenes; + // }, + // ); + // return response; + // } catch (e) { + // rethrow; + // } + // } + //getAutomation static Future> getAutomationByUnitId(String unitId) async { diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 4bc0e752..1cb3be32 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -55,4 +55,6 @@ abstract class ApiEndpoints { static const String getIconScene = '/scene/icon'; static const String createScene = '/scene/tap-to-run'; static const String createAutomation = '/automation'; + static const String getUnitScenes = + '/communities/{communityUuid}/spaces/{spaceUuid}/scenes'; } From 9b5ddc4dc86203cecb41bed21ed329e17895e32b Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Wed, 27 Nov 2024 21:03:19 +0300 Subject: [PATCH 35/40] push routine details model and api settup --- .../bloc/routine_bloc/routine_bloc.dart | 2 +- .../models/routine_details_model.dart | 262 ++++++++++++++++++ lib/pages/routiens/models/routine_model.dart | 33 ++- lib/pages/routiens/view/routines_view.dart | 2 +- .../fetch_routine_scenes_automation.dart | 143 +++++----- .../main_routine_view/routine_view_card.dart | 160 +++++++---- lib/services/routines_api.dart | 81 +++--- lib/utils/constants/api_const.dart | 4 +- 8 files changed, 499 insertions(+), 188 deletions(-) create mode 100644 lib/pages/routiens/models/routine_details_model.dart diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 8506171b..d857de76 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -13,7 +13,7 @@ part 'routine_event.dart'; part 'routine_state.dart'; const spaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6'; -const communityId = '25c96044-fadf-44bb-93c7-3c079e527ce6'; +const communityId = 'aff21a57-2f91-4e5c-b99b-0182c3ab65a9'; class RoutineBloc extends Bloc { RoutineBloc() : super(const RoutineState()) { diff --git a/lib/pages/routiens/models/routine_details_model.dart b/lib/pages/routiens/models/routine_details_model.dart new file mode 100644 index 00000000..8ad48c58 --- /dev/null +++ b/lib/pages/routiens/models/routine_details_model.dart @@ -0,0 +1,262 @@ +import 'dart:convert'; + +import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart'; +import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart'; + +class RoutineDetailsModel { + final String spaceUuid; + final String name; + final String decisionExpr; + final List actions; + final String? iconId; + final bool? showInDevice; + final EffectiveTime? effectiveTime; + final List? conditions; + final String? type; + + RoutineDetailsModel({ + required this.spaceUuid, + required this.name, + required this.decisionExpr, + required this.actions, + this.iconId, + this.showInDevice, + this.effectiveTime, + this.conditions, + this.type, + }); + + // Convert to CreateSceneModel + CreateSceneModel toCreateSceneModel() { + return CreateSceneModel( + spaceUuid: spaceUuid, + iconId: iconId ?? '', + showInDevice: showInDevice ?? false, + sceneName: name, + decisionExpr: decisionExpr, + actions: actions.map((a) => a.toCreateSceneAction()).toList(), + ); + } + + // Convert to CreateAutomationModel + CreateAutomationModel toCreateAutomationModel() { + return CreateAutomationModel( + spaceUuid: spaceUuid, + automationName: name, + decisionExpr: decisionExpr, + effectiveTime: + effectiveTime ?? EffectiveTime(start: '', end: '', loops: ''), + conditions: conditions?.map((c) => c.toCondition()).toList() ?? [], + actions: actions.map((a) => a.toAutomationAction()).toList(), + ); + } + + Map toMap() { + return { + 'spaceUuid': spaceUuid, + 'name': name, + 'decisionExpr': decisionExpr, + 'actions': actions.map((x) => x.toMap()).toList(), + if (iconId != null) 'iconId': iconId, + if (showInDevice != null) 'showInDevice': showInDevice, + if (effectiveTime != null) 'effectiveTime': effectiveTime!.toMap(), + if (conditions != null) + 'conditions': conditions!.map((x) => x.toMap()).toList(), + if (type != null) 'type': type, + }; + } + + factory RoutineDetailsModel.fromMap(Map map) { + return RoutineDetailsModel( + spaceUuid: map['spaceUuid'] ?? '', + name: map['name'] ?? '', + decisionExpr: map['decisionExpr'] ?? '', + actions: List.from( + map['actions']?.map((x) => RoutineAction.fromMap(x)) ?? [], + ), + iconId: map['iconId'], + showInDevice: map['showInDevice'], + effectiveTime: map['effectiveTime'] != null + ? EffectiveTime.fromMap(map['effectiveTime']) + : null, + conditions: map['conditions'] != null + ? List.from( + map['conditions'].map((x) => RoutineCondition.fromMap(x))) + : null, + type: map['type'], + ); + } + + String toJson() => json.encode(toMap()); + + factory RoutineDetailsModel.fromJson(String source) => + RoutineDetailsModel.fromMap(json.decode(source)); +} + +class RoutineAction { + final String entityId; + final String actionExecutor; + final RoutineExecutorProperty? executorProperty; + + RoutineAction({ + required this.entityId, + required this.actionExecutor, + this.executorProperty, + }); + + CreateSceneAction toCreateSceneAction() { + return CreateSceneAction( + entityId: entityId, + actionExecutor: actionExecutor, + executorProperty: executorProperty?.toCreateSceneExecutorProperty(), + ); + } + + AutomationAction toAutomationAction() { + return AutomationAction( + entityId: entityId, + actionExecutor: actionExecutor, + executorProperty: executorProperty?.toExecutorProperty(), + ); + } + + Map toMap() { + return { + 'entityId': entityId, + 'actionExecutor': actionExecutor, + if (executorProperty != null) + 'executorProperty': executorProperty!.toMap(), + }; + } + + factory RoutineAction.fromMap(Map map) { + return RoutineAction( + entityId: map['entityId'] ?? '', + actionExecutor: map['actionExecutor'] ?? '', + executorProperty: map['executorProperty'] != null + ? RoutineExecutorProperty.fromMap(map['executorProperty']) + : null, + ); + } +} + +class RoutineExecutorProperty { + final String? functionCode; + final dynamic functionValue; + final int? delaySeconds; + + RoutineExecutorProperty({ + this.functionCode, + this.functionValue, + this.delaySeconds, + }); + + CreateSceneExecutorProperty toCreateSceneExecutorProperty() { + return CreateSceneExecutorProperty( + functionCode: functionCode ?? '', + functionValue: functionValue, + delaySeconds: delaySeconds ?? 0, + ); + } + + ExecutorProperty toExecutorProperty() { + return ExecutorProperty( + functionCode: functionCode, + functionValue: functionValue, + delaySeconds: delaySeconds, + ); + } + + Map toMap() { + return { + if (functionCode != null) 'functionCode': functionCode, + if (functionValue != null) 'functionValue': functionValue, + if (delaySeconds != null) 'delaySeconds': delaySeconds, + }; + } + + factory RoutineExecutorProperty.fromMap(Map map) { + return RoutineExecutorProperty( + functionCode: map['functionCode'], + functionValue: map['functionValue'], + delaySeconds: map['delaySeconds']?.toInt(), + ); + } +} + +class RoutineCondition { + final int code; + final String entityId; + final String entityType; + final RoutineConditionExpr expr; + + RoutineCondition({ + required this.code, + required this.entityId, + required this.entityType, + required this.expr, + }); + + Condition toCondition() { + return Condition( + code: code, + entityId: entityId, + entityType: entityType, + expr: expr.toConditionExpr(), + ); + } + + Map toMap() { + return { + 'code': code, + 'entityId': entityId, + 'entityType': entityType, + 'expr': expr.toMap(), + }; + } + + factory RoutineCondition.fromMap(Map map) { + return RoutineCondition( + code: map['code']?.toInt() ?? 0, + entityId: map['entityId'] ?? '', + entityType: map['entityType'] ?? '', + expr: RoutineConditionExpr.fromMap(map['expr']), + ); + } +} + +class RoutineConditionExpr { + final String statusCode; + final String comparator; + final dynamic statusValue; + + RoutineConditionExpr({ + required this.statusCode, + required this.comparator, + required this.statusValue, + }); + + ConditionExpr toConditionExpr() { + return ConditionExpr( + statusCode: statusCode, + comparator: comparator, + statusValue: statusValue, + ); + } + + Map toMap() { + return { + 'statusCode': statusCode, + 'comparator': comparator, + 'statusValue': statusValue, + }; + } + + factory RoutineConditionExpr.fromMap(Map map) { + return RoutineConditionExpr( + statusCode: map['statusCode'] ?? '', + comparator: map['comparator'] ?? '', + statusValue: map['statusValue'], + ); + } +} diff --git a/lib/pages/routiens/models/routine_model.dart b/lib/pages/routiens/models/routine_model.dart index 3ee7bd03..fe22a38c 100644 --- a/lib/pages/routiens/models/routine_model.dart +++ b/lib/pages/routiens/models/routine_model.dart @@ -1,7 +1,10 @@ +import 'dart:convert'; +import 'dart:typed_data'; import 'package:syncrow_web/utils/constants/assets.dart'; class ScenesModel { final String id; + final String? sceneTuyaId; final String name; final String status; final String type; @@ -9,26 +12,36 @@ class ScenesModel { ScenesModel({ required this.id, + this.sceneTuyaId, required this.name, required this.status, required this.type, this.icon, }); + factory ScenesModel.fromRawJson(String str) => + ScenesModel.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + Uint8List get iconInBytes => base64Decode(icon ?? ''); + factory ScenesModel.fromJson(Map json, - {bool? isAutomation}) => - ScenesModel( - id: json["id"], - name: json["name"] ?? '', - status: json["status"] ?? '', - type: json["type"] ?? '', - icon: isAutomation == true - ? Assets.automation - : (json["icon"] as String?), - ); + {bool? isAutomation}) { + return ScenesModel( + id: json["id"] ?? json["uuid"] ?? '', + sceneTuyaId: json["sceneTuyaId"] as String?, + name: json["name"] ?? '', + status: json["status"] ?? '', + type: json["type"] ?? '', + icon: + isAutomation == true ? Assets.automation : (json["icon"] as String?), + ); + } Map toJson() => { "id": id, + "sceneTuyaId": sceneTuyaId ?? '', "name": name, "status": status, "type": type, diff --git a/lib/pages/routiens/view/routines_view.dart b/lib/pages/routiens/view/routines_view.dart index f2dac85f..be5a5a3a 100644 --- a/lib/pages/routiens/view/routines_view.dart +++ b/lib/pages/routiens/view/routines_view.dart @@ -47,7 +47,7 @@ class RoutinesView extends StatelessWidget { textString: '', ), const SizedBox( - height: 30, + height: 15, ), const Expanded(child: FetchRoutineScenesAutomation()), ], diff --git a/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart b/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart index 81dfa13b..031b8f63 100644 --- a/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart +++ b/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/routine_vie import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; class FetchRoutineScenesAutomation extends StatefulWidget { const FetchRoutineScenesAutomation({super.key}); @@ -14,7 +15,8 @@ class FetchRoutineScenesAutomation extends StatefulWidget { _FetchRoutineScenesState(); } -class _FetchRoutineScenesState extends State { +class _FetchRoutineScenesState extends State + with HelperResponsiveLayout { @override void initState() { super.initState(); @@ -27,83 +29,94 @@ class _FetchRoutineScenesState extends State { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - return SizedBox( - height: 500, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (state.isLoading) - const Center( - child: CircularProgressIndicator(), - ), - Text( - "Scenes (Tab to Run)", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 10), - if (state.scenes.isEmpty) + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if (state.isLoading) + const Center( + child: CircularProgressIndicator(), + ), Text( - "No scenes found", - style: context.textTheme.bodyMedium?.copyWith( - color: ColorsManager.grayColor, - ), + "Scenes (Tab to Run)", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), ), - if (state.scenes.isNotEmpty) - SizedBox( - height: 200, - child: ListView.builder( - shrinkWrap: true, - scrollDirection: Axis.horizontal, - itemCount: state.scenes.length, - itemBuilder: (context, index) { - return RoutineViewCard( - onTap: () {}, - textString: state.scenes[index].name, - icon: state.scenes[index].icon ?? Assets.logoHorizontal, - ); - }, - ), - ), - const SizedBox(height: 30), - Text( - "Automations", - style: Theme.of(context).textTheme.titleLarge?.copyWith( + const SizedBox(height: 10), + if (state.scenes.isEmpty) + Text( + "No scenes found", + style: context.textTheme.bodyMedium?.copyWith( color: ColorsManager.grayColor, - fontWeight: FontWeight.bold, ), - ), - const SizedBox(height: 10), - if (state.automations.isEmpty) - Text( - "No automations found", - style: context.textTheme.bodyMedium?.copyWith( - color: ColorsManager.grayColor, ), + if (state.scenes.isNotEmpty) + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: isSmallScreenSize(context) ? 160 : 170, + ), + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: state.scenes.length, + itemBuilder: (context, index) => Padding( + padding: EdgeInsets.only( + right: isSmallScreenSize(context) ? 4.0 : 8.0, + ), + child: RoutineViewCard( + onTap: () {}, + textString: state.scenes[index].name, + icon: + state.scenes[index].icon ?? Assets.logoHorizontal, + isFromScenes: true, + iconInBytes: state.scenes[index].iconInBytes, + ), + ), + ), + ), + const SizedBox(height: 15), + Text( + "Automations", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), ), - if (state.automations.isNotEmpty) - SizedBox( - height: 200, - child: ListView.builder( - shrinkWrap: true, - scrollDirection: Axis.horizontal, - itemCount: state.automations.length, - itemBuilder: (context, index) { - return Padding( - padding: const EdgeInsets.only(right: 8.0), + const SizedBox(height: 10), + if (state.automations.isEmpty) + Text( + "No automations found", + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.grayColor, + ), + ), + if (state.automations.isNotEmpty) + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: isSmallScreenSize(context) ? 160 : 170, + ), + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: state.automations.length, + itemBuilder: (context, index) => Padding( + padding: EdgeInsets.only( + right: isSmallScreenSize(context) ? 4.0 : 8.0, + ), child: RoutineViewCard( onTap: () {}, textString: state.automations[index].name, icon: state.automations[index].icon ?? Assets.automation, ), - ); - }, + ), + ), ), - ), - ], + ], + ), ), ); }, diff --git a/lib/pages/routiens/widgets/main_routine_view/routine_view_card.dart b/lib/pages/routiens/widgets/main_routine_view/routine_view_card.dart index 9d8b2d19..7e446507 100644 --- a/lib/pages/routiens/widgets/main_routine_view/routine_view_card.dart +++ b/lib/pages/routiens/widgets/main_routine_view/routine_view_card.dart @@ -1,84 +1,124 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class RoutineViewCard extends StatelessWidget { +class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout { const RoutineViewCard({ super.key, required this.onTap, required this.icon, required this.textString, + this.isFromScenes, + this.iconInBytes, }); final Function() onTap; final dynamic icon; final String textString; + final bool? isFromScenes; + final Uint8List? iconInBytes; @override Widget build(BuildContext context) { - return SizedBox( - height: 200, - width: 150, - child: GestureDetector( - onTap: () { - onTap(); - }, - child: Card( - elevation: 3, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - color: ColorsManager.whiteColors, - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Center( - child: Container( - decoration: BoxDecoration( - color: ColorsManager.graysColor, - borderRadius: BorderRadius.circular(120), - border: Border.all( - color: ColorsManager.greyColor, - width: 2.0, + final double cardWidth = isSmallScreenSize(context) + ? 120 + : isMediumScreenSize(context) + ? 135 + : 150; + + final double cardHeight = isSmallScreenSize(context) ? 160 : 170; + + final double iconSize = isSmallScreenSize(context) + ? 50 + : isMediumScreenSize(context) + ? 60 + : 70; + + return ConstrainedBox( + constraints: BoxConstraints( + maxWidth: cardWidth, + maxHeight: cardHeight, + ), + child: Card( + elevation: 3, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + color: ColorsManager.whiteColors, + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(10), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Center( + child: Container( + decoration: BoxDecoration( + color: ColorsManager.graysColor, + borderRadius: BorderRadius.circular(120), + border: Border.all( + color: ColorsManager.greyColor, + width: 2.0, + ), + ), + height: iconSize, + width: iconSize, + child: (isFromScenes ?? false) + ? (iconInBytes != null && + iconInBytes?.isNotEmpty == true) + ? Image.memory( + iconInBytes!, + height: iconSize, + width: iconSize, + fit: BoxFit.contain, + errorBuilder: (context, error, stackTrace) => + Image.asset( + Assets.logo, + height: iconSize, + width: iconSize, + fit: BoxFit.contain, + ), + ) + : Image.asset( + Assets.logo, + height: iconSize, + width: iconSize, + fit: BoxFit.contain, + ) + : (icon is String && icon.endsWith('.svg')) + ? SvgPicture.asset( + icon, + fit: BoxFit.contain, + ) + : Icon( + icon, + color: ColorsManager.dialogBlueTitle, + size: isSmallScreenSize(context) ? 30 : 40, + ), + ), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 3), + child: Text( + textString, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontSize: isSmallScreenSize(context) ? 10 : 12, ), ), - height: 70, - width: 70, - child: (icon is String) - ? icon.endsWith('.svg') - ? SvgPicture.asset( - icon, - fit: BoxFit.contain, - ) - : Image.asset( - icon, - fit: BoxFit.contain, - ) - : Icon( - icon, - color: ColorsManager.dialogBlueTitle, - size: 40, - ), ), - ), - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 3), - child: Text( - textString, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 2, - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.blackColor, - fontSize: 12, - ), - ), - ), - ], + ], + ), ), ), ), diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index 048b6d35..1483b8ec 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart'; import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart'; import 'package:syncrow_web/pages/routiens/models/icon_model.dart'; +import 'package:syncrow_web/pages/routiens/models/routine_details_model.dart'; import 'package:syncrow_web/pages/routiens/models/routine_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; @@ -67,9 +68,9 @@ class SceneApi { return response; } - //get scene by unit id + //get scenes by community id and space id - static Future> getScenesByUnitId( + static Future> getScenesByUnitId( String unitId, String communityId, {showInDevice = false}) async { try { @@ -95,25 +96,6 @@ class SceneApi { } } - // static Future> getScenesByUnitId(String unitId) async { - // try { - // final response = await _httpService.get( - // path: ApiEndpoints.getSpaceScenes.replaceAll('{unitUuid}', unitId), - // showServerMessage: false, - // expectedResponseModel: (json) { - // List scenes = []; - // for (var scene in json) { - // scenes.add(ScenesModel.fromJson(scene)); - // } - // return scenes; - // }, - // ); - // return response; - // } catch (e) { - // rethrow; - // } - // } - //getAutomation static Future> getAutomationByUnitId(String unitId) async { @@ -148,21 +130,21 @@ class SceneApi { // } // } -// //automation details -// static Future getAutomationDetails( -// String automationId) async { -// try { -// final response = await _httpService.get( -// path: ApiEndpoints.getAutomationDetails -// .replaceAll('{automationId}', automationId), -// showServerMessage: false, -// expectedResponseModel: (json) => SceneDetailsModel.fromJson(json), -// ); -// return response; -// } catch (e) { -// rethrow; -// } -// } +//automation details + static Future getAutomationDetails( + String automationId) async { + try { + final response = await _httpService.get( + path: ApiEndpoints.getAutomationDetails + .replaceAll('{automationId}', automationId), + showServerMessage: false, + expectedResponseModel: (json) => RoutineDetailsModel.fromJson(json), + ); + return response; + } catch (e) { + rethrow; + } + } // // //updateAutomationStatus // static Future updateAutomationStatus(String automationId, @@ -180,20 +162,19 @@ class SceneApi { // } // } - // //getScene - // - // static Future getSceneDetails(String sceneId) async { - // try { - // final response = await _httpService.get( - // path: ApiEndpoints.getScene.replaceAll('{sceneId}', sceneId), - // showServerMessage: false, - // expectedResponseModel: (json) => SceneDetailsModel.fromJson(json), - // ); - // return response; - // } catch (e) { - // rethrow; - // } - // } + //getScene + static Future getSceneDetails(String sceneId) async { + try { + final response = await _httpService.get( + path: ApiEndpoints.getScene.replaceAll('{sceneId}', sceneId), + showServerMessage: false, + expectedResponseModel: (json) => RoutineDetailsModel.fromJson(json), + ); + return response; + } catch (e) { + rethrow; + } + } // // //update Scene // static updateScene(CreateSceneModel createSceneModel, String sceneId) async { diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 1cb3be32..26af5793 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -46,7 +46,6 @@ abstract class ApiEndpoints { '/schedule/{deviceUuid}/{scheduleUuid}'; static const String updateScheduleByDeviceId = '/schedule/enable/{deviceUuid}'; - static const String factoryReset = '/device/factory/reset/{deviceUuid}'; static const String powerClamp = '/device/{powerClampUuid}/power-clamp/status'; @@ -57,4 +56,7 @@ abstract class ApiEndpoints { static const String createAutomation = '/automation'; static const String getUnitScenes = '/communities/{communityUuid}/spaces/{spaceUuid}/scenes'; + static const String getAutomationDetails = + '/automation/details/{automationId}'; + static const String getScene = '/scene/tap-to-run/{sceneId}'; } From e50a6d4c4b0d2b2512af50852510c1e0a32d65f6 Mon Sep 17 00:00:00 2001 From: ashraf_personal Date: Thu, 28 Nov 2024 01:11:31 +0300 Subject: [PATCH 36/40] added routines details apis --- .../bloc/routine_bloc/routine_bloc.dart | 212 +++++++++++++++--- .../bloc/routine_bloc/routine_event.dart | 33 +++ .../bloc/routine_bloc/routine_state.dart | 26 ++- lib/pages/routiens/models/routine_model.dart | 9 +- .../view/create_new_routine_view.dart | 10 +- .../fetch_routine_scenes_automation.dart | 2 + lib/services/routines_api.dart | 2 +- 7 files changed, 256 insertions(+), 38 deletions(-) diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index d857de76..9870819a 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -6,8 +6,11 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart'; import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/pages/routiens/models/routine_details_model.dart'; import 'package:syncrow_web/pages/routiens/models/routine_model.dart'; import 'package:syncrow_web/services/routines_api.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:uuid/uuid.dart'; part 'routine_event.dart'; part 'routine_state.dart'; @@ -31,6 +34,9 @@ class RoutineBloc extends Bloc { on(_onCreateAutomation); on(_onSetRoutineName); on(_onResetRoutineState); + on(_onGetSceneDetails); + on(_onGetAutomationDetails); + on(_onInitializeRoutineState); // on(_onRemoveFunction); // on(_onClearFunctions); } @@ -39,8 +45,8 @@ class RoutineBloc extends Bloc { final updatedIfItems = List>.from(state.ifItems); // Find the index of the item in teh current itemsList - int index = - updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); + int index = updatedIfItems.indexWhere( + (map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); // Replace the map if the index is valid if (index != -1) { updatedIfItems[index] = event.item; @@ -49,18 +55,21 @@ class RoutineBloc extends Bloc { } if (event.isTabToRun) { - emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false)); + emit(state.copyWith( + ifItems: updatedIfItems, isTabToRun: true, isAutomation: false)); } else { - emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true)); + emit(state.copyWith( + ifItems: updatedIfItems, isTabToRun: false, isAutomation: true)); } } - void _onAddToThenContainer(AddToThenContainer event, Emitter emit) { + void _onAddToThenContainer( + AddToThenContainer event, Emitter emit) { final currentItems = List>.from(state.thenItems); // Find the index of the item in teh current itemsList - int index = - currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); + int index = currentItems.indexWhere( + (map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); // Replace the map if the index is valid if (index != -1) { currentItems[index] = event.item; @@ -71,22 +80,26 @@ class RoutineBloc extends Bloc { emit(state.copyWith(thenItems: currentItems)); } - void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter emit) { + void _onAddFunctionsToRoutine( + AddFunctionToRoutine event, Emitter emit) { try { if (event.functions.isEmpty) return; - List selectedFunction = List.from(event.functions); + List selectedFunction = + List.from(event.functions); Map> currentSelectedFunctions = Map>.from(state.selectedFunctions); if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) { List currentFunctions = - List.from(currentSelectedFunctions[event.uniqueCustomId] ?? []); + List.from( + currentSelectedFunctions[event.uniqueCustomId] ?? []); List functionCode = []; for (int i = 0; i < selectedFunction.length; i++) { for (int j = 0; j < currentFunctions.length; j++) { - if (selectedFunction[i].functionCode == currentFunctions[j].functionCode) { + if (selectedFunction[i].functionCode == + currentFunctions[j].functionCode) { currentFunctions[j] = selectedFunction[i]; if (!functionCode.contains(currentFunctions[j].functionCode)) { functionCode.add(currentFunctions[j].functionCode); @@ -96,13 +109,15 @@ class RoutineBloc extends Bloc { } for (int i = 0; i < functionCode.length; i++) { - selectedFunction.removeWhere((code) => code.functionCode == functionCode[i]); + selectedFunction + .removeWhere((code) => code.functionCode == functionCode[i]); } - currentSelectedFunctions[event.uniqueCustomId] = List.from(currentFunctions) - ..addAll(selectedFunction); + currentSelectedFunctions[event.uniqueCustomId] = + List.from(currentFunctions)..addAll(selectedFunction); } else { - currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions); + currentSelectedFunctions[event.uniqueCustomId] = + List.from(event.functions); } emit(state.copyWith(selectedFunctions: currentSelectedFunctions)); @@ -111,11 +126,13 @@ class RoutineBloc extends Bloc { } } - Future _onLoadScenes(LoadScenes event, Emitter emit) async { + Future _onLoadScenes( + LoadScenes event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { - final scenes = await SceneApi.getScenesByUnitId(event.unitId, event.communityId); + final scenes = + await SceneApi.getScenesByUnitId(event.unitId, event.communityId); emit(state.copyWith( scenes: scenes, isLoading: false, @@ -130,7 +147,8 @@ class RoutineBloc extends Bloc { } } - Future _onLoadAutomation(LoadAutomation event, Emitter emit) async { + Future _onLoadAutomation( + LoadAutomation event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { @@ -158,14 +176,16 @@ class RoutineBloc extends Bloc { } } - FutureOr _onSearchRoutines(SearchRoutines event, Emitter emit) async { + FutureOr _onSearchRoutines( + SearchRoutines event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); await Future.delayed(const Duration(seconds: 1)); emit(state.copyWith(isLoading: false, errorMessage: null)); emit(state.copyWith(searchText: event.query)); } - FutureOr _onAddSelectedIcon(AddSelectedIcon event, Emitter emit) { + FutureOr _onAddSelectedIcon( + AddSelectedIcon event, Emitter emit) { emit(state.copyWith(selectedIcon: event.icon)); } @@ -174,7 +194,8 @@ class RoutineBloc extends Bloc { return actions.first['deviceId'] == 'delay'; } - Future _onCreateScene(CreateSceneEvent event, Emitter emit) async { + Future _onCreateScene( + CreateSceneEvent event, Emitter emit) async { try { // Check if first action is delay if (_isFirstActionDelay(state.thenItems)) { @@ -249,7 +270,8 @@ class RoutineBloc extends Bloc { } } - Future _onCreateAutomation(CreateAutomationEvent event, Emitter emit) async { + Future _onCreateAutomation( + CreateAutomationEvent event, Emitter emit) async { try { if (state.routineName == null || state.routineName!.isEmpty) { emit(state.copyWith( @@ -349,38 +371,165 @@ class RoutineBloc extends Bloc { } } - FutureOr _onRemoveDragCard(RemoveDragCard event, Emitter emit) { + FutureOr _onRemoveDragCard( + RemoveDragCard event, Emitter emit) { if (event.isFromThen) { final thenItems = List>.from(state.thenItems); - final selectedFunctions = Map>.from(state.selectedFunctions); + final selectedFunctions = + Map>.from(state.selectedFunctions); thenItems.removeAt(event.index); selectedFunctions.remove(event.key); - emit(state.copyWith(thenItems: thenItems, selectedFunctions: selectedFunctions)); + emit(state.copyWith( + thenItems: thenItems, selectedFunctions: selectedFunctions)); } else { final ifItems = List>.from(state.ifItems); - final selectedFunctions = Map>.from(state.selectedFunctions); + final selectedFunctions = + Map>.from(state.selectedFunctions); ifItems.removeAt(event.index); selectedFunctions.remove(event.key); - emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions)); + emit(state.copyWith( + ifItems: ifItems, selectedFunctions: selectedFunctions)); } } - FutureOr _changeOperatorOperator(ChangeAutomationOperator event, Emitter emit) { + FutureOr _changeOperatorOperator( + ChangeAutomationOperator event, Emitter emit) { emit(state.copyWith( selectedAutomationOperator: event.operator, )); } - FutureOr _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter emit) { + FutureOr _onEffectiveTimeEvent( + EffectiveTimePeriodEvent event, Emitter emit) { emit(state.copyWith(effectiveTime: event.effectiveTime)); } - FutureOr _onSetRoutineName(SetRoutineName event, Emitter emit) { + FutureOr _onSetRoutineName( + SetRoutineName event, Emitter emit) { emit(state.copyWith(routineName: event.name)); } + Future _onGetSceneDetails( + GetSceneDetails event, Emitter emit) async { + try { + emit(state.copyWith( + isLoading: true, + isTabToRun: event.isTabToRun, + isUpdate: true, + sceneId: event.sceneId, + isAutomation: false)); + final sceneDetails = await SceneApi.getSceneDetails(event.sceneId); + add(InitializeRoutineState(sceneDetails)); + } catch (e) { + emit(state.copyWith( + isLoading: false, + errorMessage: 'Failed to load scene details', + )); + } + } + + Future _onGetAutomationDetails( + GetAutomationDetails event, Emitter emit) async { + try { + emit(state.copyWith( + isLoading: true, + isAutomation: event.isAutomation, + automationId: event.automationId, + isTabToRun: false, + isUpdate: true, + )); + final automationDetails = + await SceneApi.getAutomationDetails(event.automationId); + add(InitializeRoutineState(automationDetails)); + } catch (e) { + emit(state.copyWith( + isLoading: false, + errorMessage: 'Failed to load automation details', + )); + } + } + + void _onInitializeRoutineState( + InitializeRoutineState event, Emitter emit) { + final routineDetails = event.routineDetails; + + // Convert actions to draggable cards for the THEN container + final thenItems = routineDetails.actions.map((action) { + final Map cardData = { + 'entityId': action.entityId, + 'uniqueCustomId': const Uuid().v4(), + 'deviceId': + action.actionExecutor == 'delay' ? 'delay' : action.entityId, + 'title': action.actionExecutor == 'delay' ? 'Delay' : 'Device', + // fix this + 'imagePath': + action.actionExecutor == 'delay' ? Assets.delay : Assets.logo, + }; + + // Add functions to selectedFunctions + if (action.executorProperty != null) { + final functions = [ + DeviceFunctionData( + entityId: action.entityId, + functionCode: action.executorProperty!.functionCode ?? '', + value: action.executorProperty!.functionValue, + + /// fix this + operationName: action.executorProperty?.functionCode ?? ''), + ]; + state.selectedFunctions[cardData['uniqueCustomId']] = functions; + } + + return cardData; + }).toList() ?? + []; + + // Convert conditions to draggable cards for the IF container + final ifItems = routineDetails.conditions?.map((condition) { + final Map cardData = { + 'entityId': condition.entityId, + 'uniqueCustomId': const Uuid().v4(), + 'deviceId': condition.entityId, + + /// fix this + 'title': 'Device', + + /// fix this + 'imagePath': Assets.logo, + }; + + // Add functions to selectedFunctions + final functions = [ + DeviceFunctionData( + entityId: condition.entityId, + functionCode: condition.expr.statusCode, + value: condition.expr.statusValue, + condition: condition.expr.comparator, + operationName: condition.expr.comparator, + ), + ]; + state.selectedFunctions[cardData['uniqueCustomId']] = functions; + + return cardData; + }).toList() ?? + []; + + emit(state.copyWith( + isLoading: false, + routineName: routineDetails.name, + selectedIcon: routineDetails.iconId, + selectedAutomationOperator: routineDetails.decisionExpr, + effectiveTime: routineDetails.effectiveTime, + isAutomation: routineDetails.conditions != null, + isTabToRun: routineDetails.conditions == null, + thenItems: thenItems, + ifItems: ifItems, + selectedFunctions: Map.from(state.selectedFunctions), + )); + } + RoutineState _resetState() { return const RoutineState( ifItems: [], @@ -402,7 +551,8 @@ class RoutineBloc extends Bloc { ); } - FutureOr _onResetRoutineState(ResetRoutineState event, Emitter emit) { + FutureOr _onResetRoutineState( + ResetRoutineState event, Emitter emit) { emit(_resetState()); } } diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart index 909e458d..ad93a5ac 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart @@ -125,6 +125,39 @@ class SetRoutineName extends RoutineEvent { List get props => [name]; } +class GetSceneDetails extends RoutineEvent { + final String sceneId; + final bool isUpdate; + final bool isTabToRun; + const GetSceneDetails({ + required this.sceneId, + required this.isUpdate, + required this.isTabToRun, + }); + @override + List get props => [sceneId]; +} + +class GetAutomationDetails extends RoutineEvent { + final String automationId; + final bool isUpdate; + final bool isAutomation; + const GetAutomationDetails({ + required this.automationId, + this.isUpdate = false, + this.isAutomation = false, + }); + @override + List get props => [automationId]; +} + +class InitializeRoutineState extends RoutineEvent { + final RoutineDetailsModel routineDetails; + const InitializeRoutineState(this.routineDetails); + @override + List get props => [routineDetails]; +} + class ResetRoutineState extends RoutineEvent {} class ClearFunctions extends RoutineEvent {} diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart index b0689f0d..8f8d9b5c 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart @@ -18,6 +18,9 @@ class RoutineState extends Equatable { final bool isAutomation; final String selectedAutomationOperator; final EffectiveTime? effectiveTime; + final String? sceneId; + final String? automationId; + final bool? isUpdate; const RoutineState({ this.ifItems = const [], @@ -37,6 +40,9 @@ class RoutineState extends Equatable { this.isAutomation = false, this.selectedAutomationOperator = 'or', this.effectiveTime, + this.sceneId, + this.automationId, + this.isUpdate, }); RoutineState copyWith({ @@ -56,6 +62,9 @@ class RoutineState extends Equatable { bool? isAutomation, String? selectedAutomationOperator, EffectiveTime? effectiveTime, + String? sceneId, + String? automationId, + bool? isUpdate, }) { return RoutineState( ifItems: ifItems ?? this.ifItems, @@ -67,13 +76,19 @@ class RoutineState extends Equatable { errorMessage: errorMessage ?? this.errorMessage, routineName: routineName ?? this.routineName, selectedIcon: selectedIcon ?? this.selectedIcon, - loadScenesErrorMessage: loadScenesErrorMessage ?? this.loadScenesErrorMessage, - loadAutomationErrorMessage: loadAutomationErrorMessage ?? this.loadAutomationErrorMessage, + loadScenesErrorMessage: + loadScenesErrorMessage ?? this.loadScenesErrorMessage, + loadAutomationErrorMessage: + loadAutomationErrorMessage ?? this.loadAutomationErrorMessage, searchText: searchText ?? this.searchText, isTabToRun: isTabToRun ?? this.isTabToRun, isAutomation: isAutomation ?? this.isAutomation, - selectedAutomationOperator: selectedAutomationOperator ?? this.selectedAutomationOperator, + selectedAutomationOperator: + selectedAutomationOperator ?? this.selectedAutomationOperator, effectiveTime: effectiveTime ?? this.effectiveTime, + sceneId: sceneId ?? this.sceneId, + automationId: automationId ?? this.automationId, + isUpdate: isUpdate ?? this.isUpdate, ); } @@ -94,6 +109,9 @@ class RoutineState extends Equatable { isTabToRun, isAutomation, selectedAutomationOperator, - effectiveTime + effectiveTime, + sceneId, + automationId, + isUpdate ]; } diff --git a/lib/pages/routiens/models/routine_model.dart b/lib/pages/routiens/models/routine_model.dart index fe22a38c..bb3e117b 100644 --- a/lib/pages/routiens/models/routine_model.dart +++ b/lib/pages/routiens/models/routine_model.dart @@ -24,7 +24,14 @@ class ScenesModel { String toRawJson() => json.encode(toJson()); - Uint8List get iconInBytes => base64Decode(icon ?? ''); + Uint8List? get iconInBytes { + if (icon == null || icon?.isEmpty == true) return null; + try { + return base64Decode(icon!); + } catch (e) { + return null; + } + } factory ScenesModel.fromJson(Map json, {bool? isAutomation}) { diff --git a/lib/pages/routiens/view/create_new_routine_view.dart b/lib/pages/routiens/view/create_new_routine_view.dart index 38750991..320eb15d 100644 --- a/lib/pages/routiens/view/create_new_routine_view.dart +++ b/lib/pages/routiens/view/create_new_routine_view.dart @@ -6,8 +6,16 @@ import 'package:syncrow_web/pages/routiens/widgets/then_container.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateNewRoutineView extends StatelessWidget { - const CreateNewRoutineView({super.key}); + final bool isUpdate; + final String? routineId; + final bool isScene; + const CreateNewRoutineView({ + super.key, + this.isUpdate = false, + this.routineId, + this.isScene = true, + }); @override Widget build(BuildContext context) { return Container( diff --git a/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart b/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart index 031b8f63..375491f7 100644 --- a/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart +++ b/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart'; import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/routine_view_card.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index 1483b8ec..7fc3b1bf 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -85,7 +85,7 @@ class SceneApi { List scenes = []; for (var scene in scenesJson) { - scenes.add(ScenesModel.fromJson(scene)); + scenes.add(ScenesModel.fromJson(scene, isAutomation: false)); } return scenes; }, From 3b18c4e8cfd8656504c7c904321e1965b53ccb07 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Thu, 28 Nov 2024 01:12:18 +0300 Subject: [PATCH 37/40] bug fixes --- .../bloc/routine_bloc/routine_bloc.dart | 15 ++++++++++--- lib/pages/routiens/widgets/if_container.dart | 22 +++++++++++-------- lib/pages/routiens/widgets/period_option.dart | 6 ++++- .../routiens/widgets/then_container.dart | 15 ++++++++++++- 4 files changed, 44 insertions(+), 14 deletions(-) diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index d857de76..9186cc78 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -363,11 +363,20 @@ class RoutineBloc extends Bloc { ifItems.removeAt(event.index); selectedFunctions.remove(event.key); - emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions)); + if (ifItems.isEmpty && state.thenItems.isEmpty) { + emit(state.copyWith( + ifItems: ifItems, + selectedFunctions: selectedFunctions, + isAutomation: false, + isTabToRun: false)); + } else { + emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions)); + } } } - FutureOr _changeOperatorOperator(ChangeAutomationOperator event, Emitter emit) { + FutureOr _changeOperatorOperator( + ChangeAutomationOperator event, Emitter emit) { emit(state.copyWith( selectedAutomationOperator: event.operator, )); @@ -396,7 +405,7 @@ class RoutineBloc extends Bloc { selectedIcon: null, isTabToRun: false, isAutomation: false, - selectedAutomationOperator: 'AND', + selectedAutomationOperator: 'or', effectiveTime: null, routineName: null, ); diff --git a/lib/pages/routiens/widgets/if_container.dart b/lib/pages/routiens/widgets/if_container.dart index d0273adc..da2b540b 100644 --- a/lib/pages/routiens/widgets/if_container.dart +++ b/lib/pages/routiens/widgets/if_container.dart @@ -27,7 +27,7 @@ class IfContainer extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('IF', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), - if (state.isAutomation) + if (state.isAutomation && state.ifItems.isNotEmpty) AutomationOperatorSelector( selectedOperator: state.selectedAutomationOperator), ], @@ -88,13 +88,16 @@ class IfContainer extends StatelessWidget { ), ); }, - onWillAccept: (data) => data != null, - onAccept: (data) async { + onAcceptWithDetails: (data) async { final uniqueCustomId = const Uuid().v4(); - final mutableData = Map.from(data); + final mutableData = Map.from(data.data); mutableData['uniqueCustomId'] = uniqueCustomId; + if (state.isAutomation && mutableData['deviceId'] == 'tab_to_run') { + return; + } + if (!state.isTabToRun) { if (mutableData['deviceId'] == 'tab_to_run') { context.read().add(AddToIfContainer(mutableData, true)); @@ -134,7 +137,7 @@ class AutomationOperatorSelector extends StatelessWidget { children: [ TextButton( style: TextButton.styleFrom( - backgroundColor: selectedOperator == 'or' + backgroundColor: selectedOperator.toLowerCase() == 'or' ? ColorsManager.dialogBlueTitle : ColorsManager.whiteColors, shape: RoundedRectangleBorder( @@ -144,8 +147,9 @@ class AutomationOperatorSelector extends StatelessWidget { child: Text( 'Any condition is met', style: context.textTheme.bodyMedium?.copyWith( - color: - selectedOperator == 'or' ? ColorsManager.whiteColors : ColorsManager.blackColor, + color: selectedOperator.toLowerCase() == 'or' + ? ColorsManager.whiteColors + : ColorsManager.blackColor, ), ), onPressed: () { @@ -159,7 +163,7 @@ class AutomationOperatorSelector extends StatelessWidget { ), TextButton( style: TextButton.styleFrom( - backgroundColor: selectedOperator == 'and' + backgroundColor: selectedOperator.toLowerCase() == 'and' ? ColorsManager.dialogBlueTitle : ColorsManager.whiteColors, shape: RoundedRectangleBorder( @@ -169,7 +173,7 @@ class AutomationOperatorSelector extends StatelessWidget { child: Text( 'All condition is met', style: context.textTheme.bodyMedium?.copyWith( - color: selectedOperator == 'and' + color: selectedOperator.toLowerCase() == 'and' ? ColorsManager.whiteColors : ColorsManager.blackColor, ), diff --git a/lib/pages/routiens/widgets/period_option.dart b/lib/pages/routiens/widgets/period_option.dart index 5c1c2d51..1871ebda 100644 --- a/lib/pages/routiens/widgets/period_option.dart +++ b/lib/pages/routiens/widgets/period_option.dart @@ -75,7 +75,11 @@ class PeriodOptions extends StatelessWidget { onTap: () { context.read().add(SetPeriod(value)); }, - title: Text(EffectPeriodHelper.formatEnumValue(value)), + title: Text( + EffectPeriodHelper.formatEnumValue(value), + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.blackColor, fontWeight: FontWeight.w400, fontSize: 12), + ), subtitle: Text( subtitle, style: Theme.of(context).textTheme.bodyMedium!.copyWith( diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index 52749851..8dbc5904 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -128,6 +128,20 @@ class ThenContainer extends StatelessWidget { return; } + if (mutableData['type'] == 'tap_to_run' && state.isAutomation) { + context.read().add(AddToThenContainer({ + ...mutableData, + 'imagePath': Assets.loginLogo, + 'title': mutableData['name'], + })); + + return; + } + + if (mutableData['type'] == 'tap_to_run' && !state.isAutomation) { + return; + } + if (mutableData['deviceId'] == 'delay') { final result = await DelayHelper.showDelayPickerDialog(context, mutableData); @@ -142,7 +156,6 @@ class ThenContainer extends StatelessWidget { } final result = await DeviceDialogHelper.showDeviceDialog(context, mutableData); - if (result != null) { context.read().add(AddToThenContainer(mutableData)); } else if (!['AC', '1G', '2G', '3G'].contains(mutableData['productType'])) { From 4b3392931514ca5d81bbace0fe227a5da2e09537 Mon Sep 17 00:00:00 2001 From: ashraf_personal Date: Thu, 28 Nov 2024 02:02:01 +0300 Subject: [PATCH 38/40] push bugs --- .../dialog_helper/device_dialog_helper.dart | 55 +++++++----- lib/pages/routiens/widgets/if_container.dart | 74 ++++++++++------ .../widgets/routine_dialogs/ac_dialog.dart | 22 +++-- .../one_gang_switch_dialog.dart | 22 +++-- .../three_gang_switch_dialog.dart | 22 +++-- .../two_gang_switch_dialog.dart | 22 +++-- .../routiens/widgets/then_container.dart | 84 ++++++++++--------- 7 files changed, 186 insertions(+), 115 deletions(-) diff --git a/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart index 33cfffee..1dd84c19 100644 --- a/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart @@ -10,8 +10,9 @@ import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; class DeviceDialogHelper { static Future?> showDeviceDialog( BuildContext context, - Map data, - ) async { + Map data, { + required bool removeComparetors, + }) async { final functions = data['functions'] as List; try { @@ -20,6 +21,7 @@ class DeviceDialogHelper { data['productType'], data, functions, + removeComparetors: removeComparetors, ); if (result != null) { @@ -33,34 +35,49 @@ class DeviceDialogHelper { } static Future?> _getDialogForDeviceType( - BuildContext context, - String productType, - Map data, - List functions, - ) async { + BuildContext context, + String productType, + Map data, + List functions, + {required bool removeComparetors}) async { final routineBloc = context.read(); final deviceSelectedFunctions = routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? []; switch (productType) { case 'AC': - return ACHelper.showACFunctionsDialog(context, functions, - data['device'], deviceSelectedFunctions, data['uniqueCustomId']); + return ACHelper.showACFunctionsDialog( + context, + functions, + data['device'], + deviceSelectedFunctions, + data['uniqueCustomId'], + removeComparetors); case '1G': - return OneGangSwitchHelper.showSwitchFunctionsDialog(context, functions, - data['device'], deviceSelectedFunctions, data['uniqueCustomId']); + return OneGangSwitchHelper.showSwitchFunctionsDialog( + context, + functions, + data['device'], + deviceSelectedFunctions, + data['uniqueCustomId'], + removeComparetors); case '2G': - return TwoGangSwitchHelper.showSwitchFunctionsDialog(context, functions, - data['device'], deviceSelectedFunctions, data['uniqueCustomId']); + return TwoGangSwitchHelper.showSwitchFunctionsDialog( + context, + functions, + data['device'], + deviceSelectedFunctions, + data['uniqueCustomId'], + removeComparetors); case '3G': return ThreeGangSwitchHelper.showSwitchFunctionsDialog( - context, - functions, - data['device'], - deviceSelectedFunctions, - data['uniqueCustomId'], - ); + context, + functions, + data['device'], + deviceSelectedFunctions, + data['uniqueCustomId'], + removeComparetors); default: return null; } diff --git a/lib/pages/routiens/widgets/if_container.dart b/lib/pages/routiens/widgets/if_container.dart index da2b540b..9c357a17 100644 --- a/lib/pages/routiens/widgets/if_container.dart +++ b/lib/pages/routiens/widgets/if_container.dart @@ -26,7 +26,9 @@ class IfContainer extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text('IF', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const Text('IF', + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold)), if (state.isAutomation && state.ifItems.isNotEmpty) AutomationOperatorSelector( selectedOperator: state.selectedAutomationOperator), @@ -53,33 +55,44 @@ class IfContainer extends StatelessWidget { (index) => GestureDetector( onTap: () async { if (!state.isTabToRun) { - final result = await DeviceDialogHelper.showDeviceDialog( - context, state.ifItems[index]); + final result = await DeviceDialogHelper + .showDeviceDialog( + context, state.ifItems[index], + removeComparetors: false); if (result != null) { - context - .read() - .add(AddToIfContainer(state.ifItems[index], false)); - } else if (!['AC', '1G', '2G', '3G'] - .contains(state.ifItems[index]['productType'])) { - context - .read() - .add(AddToIfContainer(state.ifItems[index], false)); + context.read().add( + AddToIfContainer( + state.ifItems[index], false)); + } else if (![ + 'AC', + '1G', + '2G', + '3G' + ].contains( + state.ifItems[index]['productType'])) { + context.read().add( + AddToIfContainer( + state.ifItems[index], false)); } } }, child: DraggableCard( - imagePath: state.ifItems[index]['imagePath'] ?? '', + imagePath: + state.ifItems[index]['imagePath'] ?? '', title: state.ifItems[index]['title'] ?? '', deviceData: state.ifItems[index], - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), + padding: const EdgeInsets.symmetric( + horizontal: 4, vertical: 8), isFromThen: false, isFromIf: true, onRemove: () { - context.read().add(RemoveDragCard( - index: index, - isFromThen: false, - key: state.ifItems[index]['uniqueCustomId'])); + context.read().add( + RemoveDragCard( + index: index, + isFromThen: false, + key: state.ifItems[index] + ['uniqueCustomId'])); }, ), )), @@ -100,14 +113,23 @@ class IfContainer extends StatelessWidget { if (!state.isTabToRun) { if (mutableData['deviceId'] == 'tab_to_run') { - context.read().add(AddToIfContainer(mutableData, true)); + context + .read() + .add(AddToIfContainer(mutableData, true)); } else { - final result = await DeviceDialogHelper.showDeviceDialog(context, mutableData); + final result = await DeviceDialogHelper.showDeviceDialog( + context, mutableData, + removeComparetors: false); if (result != null) { - context.read().add(AddToIfContainer(mutableData, false)); - } else if (!['AC', '1G', '2G', '3G'].contains(mutableData['productType'])) { - context.read().add(AddToIfContainer(mutableData, false)); + context + .read() + .add(AddToIfContainer(mutableData, false)); + } else if (!['AC', '1G', '2G', '3G'] + .contains(mutableData['productType'])) { + context + .read() + .add(AddToIfContainer(mutableData, false)); } } } @@ -153,7 +175,9 @@ class AutomationOperatorSelector extends StatelessWidget { ), ), onPressed: () { - context.read().add(const ChangeAutomationOperator(operator: 'or')); + context + .read() + .add(const ChangeAutomationOperator(operator: 'or')); }, ), Container( @@ -179,7 +203,9 @@ class AutomationOperatorSelector extends StatelessWidget { ), ), onPressed: () { - context.read().add(const ChangeAutomationOperator(operator: 'and')); + context + .read() + .add(const ChangeAutomationOperator(operator: 'and')); }, ), ], diff --git a/lib/pages/routiens/widgets/routine_dialogs/ac_dialog.dart b/lib/pages/routiens/widgets/routine_dialogs/ac_dialog.dart index e8b87a16..39e342ff 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/ac_dialog.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/ac_dialog.dart @@ -19,6 +19,7 @@ class ACHelper { AllDevicesModel? device, List? deviceSelectedFunctions, String uniqueCustomId, + bool? removeComparetors, ) async { List acFunctions = functions.whereType().toList(); @@ -84,6 +85,7 @@ class ACHelper { acFunctions: acFunctions, device: device, operationName: selectedOperationName ?? '', + removeComparators: removeComparetors, ), ), ], @@ -179,6 +181,7 @@ class ACHelper { required List acFunctions, AllDevicesModel? device, required String operationName, + bool? removeComparators, }) { if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') { final initialValue = selectedFunctionData?.value ?? 200; @@ -190,6 +193,7 @@ class ACHelper { device: device, operationName: operationName, selectedFunctionData: selectedFunctionData, + removeComparators: removeComparators, ); } @@ -217,18 +221,20 @@ class ACHelper { AllDevicesModel? device, required String operationName, DeviceFunctionData? selectedFunctionData, + bool? removeComparators, }) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - _buildConditionToggle( - context, - currentCondition, - selectCode, - device, - operationName, - selectedFunctionData, - ), + if (removeComparators != true) + _buildConditionToggle( + context, + currentCondition, + selectCode, + device, + operationName, + selectedFunctionData, + ), const SizedBox(height: 20), _buildTemperatureDisplay( context, diff --git a/lib/pages/routiens/widgets/routine_dialogs/one_gang_switch_dialog.dart b/lib/pages/routiens/widgets/routine_dialogs/one_gang_switch_dialog.dart index 00f936d6..e7d7209f 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/one_gang_switch_dialog.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/one_gang_switch_dialog.dart @@ -20,6 +20,7 @@ class OneGangSwitchHelper { AllDevicesModel? device, List? deviceSelectedFunctions, String uniqueCustomId, + bool removeComparetors, ) async { List acFunctions = functions.whereType().toList(); @@ -106,6 +107,7 @@ class OneGangSwitchHelper { acFunctions: acFunctions, device: device, operationName: selectedOperationName ?? '', + removeComparetors: removeComparetors, ), ), ], @@ -162,6 +164,7 @@ class OneGangSwitchHelper { required List acFunctions, AllDevicesModel? device, required String operationName, + required bool removeComparetors, }) { if (selectedFunction == 'countdown_1') { final initialValue = selectedFunctionData?.value ?? 200; @@ -173,6 +176,7 @@ class OneGangSwitchHelper { device: device, operationName: operationName, selectedFunctionData: selectedFunctionData, + removeComparetors: removeComparetors, ); } @@ -199,18 +203,20 @@ class OneGangSwitchHelper { AllDevicesModel? device, required String operationName, DeviceFunctionData? selectedFunctionData, + required bool removeComparetors, }) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - _buildConditionToggle( - context, - currentCondition, - selectCode, - device, - operationName, - selectedFunctionData, - ), + if (removeComparetors != true) + _buildConditionToggle( + context, + currentCondition, + selectCode, + device, + operationName, + selectedFunctionData, + ), const SizedBox(height: 20), _buildCountDownDisplay(context, initialValue, device, operationName, selectedFunctionData, selectCode), diff --git a/lib/pages/routiens/widgets/routine_dialogs/three_gang_switch_dialog.dart b/lib/pages/routiens/widgets/routine_dialogs/three_gang_switch_dialog.dart index ae704ded..bb23ece4 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/three_gang_switch_dialog.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/three_gang_switch_dialog.dart @@ -20,6 +20,7 @@ class ThreeGangSwitchHelper { AllDevicesModel? device, List? deviceSelectedFunctions, String uniqueCustomId, + bool removeComparetors, ) async { List switchFunctions = functions.whereType().toList(); @@ -106,6 +107,7 @@ class ThreeGangSwitchHelper { switchFunctions: switchFunctions, device: device, operationName: selectedOperationName ?? '', + removeComparetors: removeComparetors, ), ), ], @@ -162,6 +164,7 @@ class ThreeGangSwitchHelper { required List switchFunctions, AllDevicesModel? device, required String operationName, + required bool removeComparetors, }) { if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2' || @@ -175,6 +178,7 @@ class ThreeGangSwitchHelper { device: device, operationName: operationName, selectedFunctionData: selectedFunctionData, + removeComparetors: removeComparetors, ); } @@ -201,18 +205,20 @@ class ThreeGangSwitchHelper { AllDevicesModel? device, required String operationName, DeviceFunctionData? selectedFunctionData, + bool? removeComparetors, }) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - _buildConditionToggle( - context, - currentCondition, - selectCode, - device, - operationName, - selectedFunctionData, - ), + if (removeComparetors != true) + _buildConditionToggle( + context, + currentCondition, + selectCode, + device, + operationName, + selectedFunctionData, + ), const SizedBox(height: 20), _buildCountDownDisplay(context, initialValue, device, operationName, selectedFunctionData, selectCode), diff --git a/lib/pages/routiens/widgets/routine_dialogs/two_gang_switch_dialog.dart b/lib/pages/routiens/widgets/routine_dialogs/two_gang_switch_dialog.dart index d017103e..f2a0ebd0 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/two_gang_switch_dialog.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/two_gang_switch_dialog.dart @@ -20,6 +20,7 @@ class TwoGangSwitchHelper { AllDevicesModel? device, List? deviceSelectedFunctions, String uniqueCustomId, + bool removeComparetors, ) async { List switchFunctions = functions.whereType().toList(); @@ -106,6 +107,7 @@ class TwoGangSwitchHelper { switchFunctions: switchFunctions, device: device, operationName: selectedOperationName ?? '', + removeComparetors: removeComparetors, ), ), ], @@ -162,6 +164,7 @@ class TwoGangSwitchHelper { required List switchFunctions, AllDevicesModel? device, required String operationName, + required bool removeComparetors, }) { if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2') { @@ -174,6 +177,7 @@ class TwoGangSwitchHelper { device: device, operationName: operationName, selectedFunctionData: selectedFunctionData, + removeComparetors: removeComparetors, ); } @@ -200,18 +204,20 @@ class TwoGangSwitchHelper { AllDevicesModel? device, required String operationName, DeviceFunctionData? selectedFunctionData, + bool? removeComparetors, }) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - _buildConditionToggle( - context, - currentCondition, - selectCode, - device, - operationName, - selectedFunctionData, - ), + if (removeComparetors != true) + _buildConditionToggle( + context, + currentCondition, + selectCode, + device, + operationName, + selectedFunctionData, + ), const SizedBox(height: 20), _buildCountDownDisplay(context, initialValue, device, operationName, selectedFunctionData, selectCode), diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index 8dbc5904..9491e8fd 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -26,7 +26,9 @@ class ThenContainer extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('THEN', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const Text('THEN', + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 16), Wrap( spacing: 8, @@ -35,12 +37,16 @@ class ThenContainer extends StatelessWidget { state.thenItems.length, (index) => GestureDetector( onTap: () async { - if (state.thenItems[index]['deviceId'] == 'delay') { - final result = await DelayHelper.showDelayPickerDialog( - context, state.thenItems[index]); + if (state.thenItems[index]['deviceId'] == + 'delay') { + final result = await DelayHelper + .showDelayPickerDialog( + context, state.thenItems[index]); if (result != null) { - context.read().add(AddToThenContainer({ + context + .read() + .add(AddToThenContainer({ ...state.thenItems[index], 'imagePath': Assets.delay, 'title': 'Delay', @@ -49,32 +55,41 @@ class ThenContainer extends StatelessWidget { return; } - final result = await DeviceDialogHelper.showDeviceDialog( - context, state.thenItems[index]); + final result = await DeviceDialogHelper + .showDeviceDialog( + context, state.thenItems[index], + removeComparetors: true); if (result != null) { - context - .read() - .add(AddToThenContainer(state.thenItems[index])); + context.read().add( + AddToThenContainer( + state.thenItems[index])); } else if (!['AC', '1G', '2G', '3G'] - .contains(state.thenItems[index]['productType'])) { - context - .read() - .add(AddToThenContainer(state.thenItems[index])); + .contains(state.thenItems[index] + ['productType'])) { + context.read().add( + AddToThenContainer( + state.thenItems[index])); } }, child: DraggableCard( - imagePath: state.thenItems[index]['imagePath'] ?? '', - title: state.thenItems[index]['title'] ?? '', + imagePath: state.thenItems[index] + ['imagePath'] ?? + '', + title: + state.thenItems[index]['title'] ?? '', deviceData: state.thenItems[index], - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), + padding: const EdgeInsets.symmetric( + horizontal: 4, vertical: 8), isFromThen: true, isFromIf: false, onRemove: () { - context.read().add(RemoveDragCard( - index: index, - isFromThen: true, - key: state.thenItems[index]['uniqueCustomId'])); + context.read().add( + RemoveDragCard( + index: index, + isFromThen: true, + key: state.thenItems[index] + ['uniqueCustomId'])); }, ), ))), @@ -83,21 +98,6 @@ class ThenContainer extends StatelessWidget { ), ); }, - // onWillAcceptWithDetails: (data) { - // if (data == null) return false; - // return data.data; - - // // if (state.isTabToRun) { - // // return data.data['type'] == 'automation'; - // // } - - // // if (state.isAutomation) { - // // return data.data['type'] == 'scene' || - // // data.data['type'] == 'automation'; - // // } - - // // return data.data['deviceId'] != null; - // }, onAcceptWithDetails: (data) async { final uniqueCustomId = const Uuid().v4(); final mutableData = Map.from(data.data); @@ -131,7 +131,7 @@ class ThenContainer extends StatelessWidget { if (mutableData['type'] == 'tap_to_run' && state.isAutomation) { context.read().add(AddToThenContainer({ ...mutableData, - 'imagePath': Assets.loginLogo, + 'imagePath': Assets.logo, 'title': mutableData['name'], })); @@ -143,7 +143,8 @@ class ThenContainer extends StatelessWidget { } if (mutableData['deviceId'] == 'delay') { - final result = await DelayHelper.showDelayPickerDialog(context, mutableData); + final result = + await DelayHelper.showDelayPickerDialog(context, mutableData); if (result != null) { context.read().add(AddToThenContainer({ @@ -155,10 +156,13 @@ class ThenContainer extends StatelessWidget { return; } - final result = await DeviceDialogHelper.showDeviceDialog(context, mutableData); + final result = await DeviceDialogHelper.showDeviceDialog( + context, mutableData, + removeComparetors: true); if (result != null) { context.read().add(AddToThenContainer(mutableData)); - } else if (!['AC', '1G', '2G', '3G'].contains(mutableData['productType'])) { + } else if (!['AC', '1G', '2G', '3G'] + .contains(mutableData['productType'])) { context.read().add(AddToThenContainer(mutableData)); } }, From 636c08c95cbdb7bf1ff7f9dcb6b27192cb6169b1 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Thu, 28 Nov 2024 02:02:40 +0300 Subject: [PATCH 39/40] bug fixes --- .../routiens/helper/save_routine_helper.dart | 45 ++--- .../fetch_routine_scenes_automation.dart | 179 +++++++++--------- .../widgets/routine_search_and_buttons.dart | 14 +- 3 files changed, 116 insertions(+), 122 deletions(-) diff --git a/lib/pages/routiens/helper/save_routine_helper.dart b/lib/pages/routiens/helper/save_routine_helper.dart index 2a57baa5..df9e8323 100644 --- a/lib/pages/routiens/helper/save_routine_helper.dart +++ b/lib/pages/routiens/helper/save_routine_helper.dart @@ -8,8 +8,8 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class SaveRoutineHelper { - static Future showSaveRoutineDialog(BuildContext context) async { - return showDialog( + static Future showSaveRoutineDialog(BuildContext context) async { + return showDialog( context: context, builder: (BuildContext context) { return BlocBuilder( @@ -54,27 +54,23 @@ class SaveRoutineHelper { ), if (state.isAutomation) ...state.ifItems.map((item) { - final functions = state.selectedFunctions[ - item['uniqueCustomId']] ?? - []; + final functions = + state.selectedFunctions[item['uniqueCustomId']] ?? []; return ListTile( leading: SvgPicture.asset( item['imagePath'], width: 22, height: 22, ), - title: Text(item['title'], - style: const TextStyle(fontSize: 14)), + title: + Text(item['title'], style: const TextStyle(fontSize: 14)), subtitle: Wrap( children: functions .map((f) => Text( '${f.operationName}: ${f.value}, ', style: const TextStyle( - color: ColorsManager - .grayColor, - fontSize: 8), - overflow: - TextOverflow.ellipsis, + color: ColorsManager.grayColor, fontSize: 8), + overflow: TextOverflow.ellipsis, maxLines: 3, )) .toList(), @@ -99,25 +95,22 @@ class SaveRoutineHelper { ), const SizedBox(height: 8), ...state.thenItems.map((item) { - final functions = state.selectedFunctions[ - item['uniqueCustomId']] ?? - []; + final functions = + state.selectedFunctions[item['uniqueCustomId']] ?? []; return ListTile( leading: SvgPicture.asset( item['imagePath'], width: 22, height: 22, ), - title: Text(item['title'], - style: const TextStyle(fontSize: 14)), + title: + Text(item['title'], style: const TextStyle(fontSize: 14)), subtitle: Wrap( children: functions .map((f) => Text( '${f.operationName}: ${f.value}, ', style: const TextStyle( - color: - ColorsManager.grayColor, - fontSize: 8), + color: ColorsManager.grayColor, fontSize: 8), overflow: TextOverflow.ellipsis, maxLines: 3, )) @@ -140,19 +133,15 @@ class SaveRoutineHelper { ), ), DialogFooter( - onCancel: () => Navigator.pop(context), + onCancel: () => Navigator.pop(context, false), onConfirm: () { if (state.isAutomation) { - context - .read() - .add(const CreateAutomationEvent()); + context.read().add(const CreateAutomationEvent()); } else { - context - .read() - .add(const CreateSceneEvent()); + context.read().add(const CreateSceneEvent()); } - Navigator.pop(context); + Navigator.pop(context, true); }, isConfirmEnabled: true, ), diff --git a/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart b/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart index 375491f7..6f4dfb8f 100644 --- a/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart +++ b/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart'; import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/routine_view_card.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -13,8 +11,7 @@ class FetchRoutineScenesAutomation extends StatefulWidget { const FetchRoutineScenesAutomation({super.key}); @override - State createState() => - _FetchRoutineScenesState(); + State createState() => _FetchRoutineScenesState(); } class _FetchRoutineScenesState extends State @@ -31,96 +28,94 @@ class _FetchRoutineScenesState extends State Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (state.isLoading) - const Center( - child: CircularProgressIndicator(), - ), - Text( - "Scenes (Tab to Run)", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.bold, + return state.isLoading + ? const Center( + child: CircularProgressIndicator(), + ) + : SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Scenes (Tab to Run)", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), ), + const SizedBox(height: 10), + if (state.scenes.isEmpty) + Text( + "No scenes found", + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.grayColor, + ), + ), + if (state.scenes.isNotEmpty) + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: isSmallScreenSize(context) ? 160 : 170, + ), + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: state.scenes.length, + itemBuilder: (context, index) => Padding( + padding: EdgeInsets.only( + right: isSmallScreenSize(context) ? 4.0 : 8.0, + ), + child: RoutineViewCard( + onTap: () {}, + textString: state.scenes[index].name, + icon: state.scenes[index].icon ?? Assets.logoHorizontal, + isFromScenes: true, + iconInBytes: state.scenes[index].iconInBytes, + ), + ), + ), + ), + const SizedBox(height: 15), + Text( + "Automations", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + if (state.automations.isEmpty) + Text( + "No automations found", + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.grayColor, + ), + ), + if (state.automations.isNotEmpty) + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: isSmallScreenSize(context) ? 160 : 170, + ), + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: state.automations.length, + itemBuilder: (context, index) => Padding( + padding: EdgeInsets.only( + right: isSmallScreenSize(context) ? 4.0 : 8.0, + ), + child: RoutineViewCard( + onTap: () {}, + textString: state.automations[index].name, + icon: state.automations[index].icon ?? Assets.automation, + ), + ), + ), + ), + ], + ), ), - const SizedBox(height: 10), - if (state.scenes.isEmpty) - Text( - "No scenes found", - style: context.textTheme.bodyMedium?.copyWith( - color: ColorsManager.grayColor, - ), - ), - if (state.scenes.isNotEmpty) - ConstrainedBox( - constraints: BoxConstraints( - maxHeight: isSmallScreenSize(context) ? 160 : 170, - ), - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: state.scenes.length, - itemBuilder: (context, index) => Padding( - padding: EdgeInsets.only( - right: isSmallScreenSize(context) ? 4.0 : 8.0, - ), - child: RoutineViewCard( - onTap: () {}, - textString: state.scenes[index].name, - icon: - state.scenes[index].icon ?? Assets.logoHorizontal, - isFromScenes: true, - iconInBytes: state.scenes[index].iconInBytes, - ), - ), - ), - ), - const SizedBox(height: 15), - Text( - "Automations", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 10), - if (state.automations.isEmpty) - Text( - "No automations found", - style: context.textTheme.bodyMedium?.copyWith( - color: ColorsManager.grayColor, - ), - ), - if (state.automations.isNotEmpty) - ConstrainedBox( - constraints: BoxConstraints( - maxHeight: isSmallScreenSize(context) ? 160 : 170, - ), - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: state.automations.length, - itemBuilder: (context, index) => Padding( - padding: EdgeInsets.only( - right: isSmallScreenSize(context) ? 4.0 : 8.0, - ), - child: RoutineViewCard( - onTap: () {}, - textString: state.automations[index].name, - icon: state.automations[index].icon ?? - Assets.automation, - ), - ), - ), - ), - ], - ), - ), - ); + ); }, ); } diff --git a/lib/pages/routiens/widgets/routine_search_and_buttons.dart b/lib/pages/routiens/widgets/routine_search_and_buttons.dart index 6696225f..cdc560f6 100644 --- a/lib/pages/routiens/widgets/routine_search_and_buttons.dart +++ b/lib/pages/routiens/widgets/routine_search_and_buttons.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/helper/save_routine_helper.dart'; import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/discard_dialog.dart'; @@ -173,7 +174,7 @@ class RoutineSearchAndButtons extends StatelessWidget { width: 200, child: Center( child: DefaultButton( - onPressed: () { + onPressed: () async { if (state.routineName == null || state.routineName!.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( @@ -207,7 +208,16 @@ class RoutineSearchAndButtons extends StatelessWidget { ); return; } - SaveRoutineHelper.showSaveRoutineDialog(context); + final result = + await SaveRoutineHelper.showSaveRoutineDialog(context); + if (result != null && result) { + BlocProvider.of(context).add( + const CreateNewRoutineViewEvent(false), + ); + BlocProvider.of(context).add( + const TriggerSwitchTabsEvent(true), + ); + } }, borderRadius: 15, elevation: 0, From 57dec8af08821ebfc36968cc754b8702a6ae3791 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Thu, 28 Nov 2024 10:43:35 +0300 Subject: [PATCH 40/40] fix image on tab to run, and add delete for automation and scene --- .../bloc/routine_bloc/routine_bloc.dart | 258 +++++++++++------- .../bloc/routine_bloc/routine_event.dart | 16 ++ lib/pages/routiens/widgets/dragable_card.dart | 6 +- .../fetch_routine_scenes_automation.dart | 96 ++++++- lib/services/routines_api.dart | 69 ++--- lib/utils/constants/api_const.dart | 3 + 6 files changed, 302 insertions(+), 146 deletions(-) diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index ea7428bd..841027b3 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -9,8 +9,6 @@ import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/routine_details_model.dart'; import 'package:syncrow_web/pages/routiens/models/routine_model.dart'; import 'package:syncrow_web/services/routines_api.dart'; -import 'package:syncrow_web/utils/constants/assets.dart'; -import 'package:uuid/uuid.dart'; part 'routine_event.dart'; part 'routine_state.dart'; @@ -36,7 +34,9 @@ class RoutineBloc extends Bloc { on(_onResetRoutineState); on(_onGetSceneDetails); on(_onGetAutomationDetails); - on(_onInitializeRoutineState); + // on(_onInitializeRoutineState); + on(_deleteScene); + on(_deleteAutomation); // on(_onRemoveFunction); // on(_onClearFunctions); } @@ -45,8 +45,8 @@ class RoutineBloc extends Bloc { final updatedIfItems = List>.from(state.ifItems); // Find the index of the item in teh current itemsList - int index = - updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); + int index = updatedIfItems.indexWhere( + (map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); // Replace the map if the index is valid if (index != -1) { updatedIfItems[index] = event.item; @@ -55,18 +55,21 @@ class RoutineBloc extends Bloc { } if (event.isTabToRun) { - emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false)); + emit(state.copyWith( + ifItems: updatedIfItems, isTabToRun: true, isAutomation: false)); } else { - emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true)); + emit(state.copyWith( + ifItems: updatedIfItems, isTabToRun: false, isAutomation: true)); } } - void _onAddToThenContainer(AddToThenContainer event, Emitter emit) { + void _onAddToThenContainer( + AddToThenContainer event, Emitter emit) { final currentItems = List>.from(state.thenItems); // Find the index of the item in teh current itemsList - int index = - currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); + int index = currentItems.indexWhere( + (map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); // Replace the map if the index is valid if (index != -1) { currentItems[index] = event.item; @@ -77,22 +80,26 @@ class RoutineBloc extends Bloc { emit(state.copyWith(thenItems: currentItems)); } - void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter emit) { + void _onAddFunctionsToRoutine( + AddFunctionToRoutine event, Emitter emit) { try { if (event.functions.isEmpty) return; - List selectedFunction = List.from(event.functions); + List selectedFunction = + List.from(event.functions); Map> currentSelectedFunctions = Map>.from(state.selectedFunctions); if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) { List currentFunctions = - List.from(currentSelectedFunctions[event.uniqueCustomId] ?? []); + List.from( + currentSelectedFunctions[event.uniqueCustomId] ?? []); List functionCode = []; for (int i = 0; i < selectedFunction.length; i++) { for (int j = 0; j < currentFunctions.length; j++) { - if (selectedFunction[i].functionCode == currentFunctions[j].functionCode) { + if (selectedFunction[i].functionCode == + currentFunctions[j].functionCode) { currentFunctions[j] = selectedFunction[i]; if (!functionCode.contains(currentFunctions[j].functionCode)) { functionCode.add(currentFunctions[j].functionCode); @@ -102,13 +109,15 @@ class RoutineBloc extends Bloc { } for (int i = 0; i < functionCode.length; i++) { - selectedFunction.removeWhere((code) => code.functionCode == functionCode[i]); + selectedFunction + .removeWhere((code) => code.functionCode == functionCode[i]); } - currentSelectedFunctions[event.uniqueCustomId] = List.from(currentFunctions) - ..addAll(selectedFunction); + currentSelectedFunctions[event.uniqueCustomId] = + List.from(currentFunctions)..addAll(selectedFunction); } else { - currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions); + currentSelectedFunctions[event.uniqueCustomId] = + List.from(event.functions); } emit(state.copyWith(selectedFunctions: currentSelectedFunctions)); @@ -117,11 +126,13 @@ class RoutineBloc extends Bloc { } } - Future _onLoadScenes(LoadScenes event, Emitter emit) async { + Future _onLoadScenes( + LoadScenes event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { - final scenes = await SceneApi.getScenesByUnitId(event.unitId, event.communityId); + final scenes = + await SceneApi.getScenesByUnitId(event.unitId, event.communityId); emit(state.copyWith( scenes: scenes, isLoading: false, @@ -136,7 +147,8 @@ class RoutineBloc extends Bloc { } } - Future _onLoadAutomation(LoadAutomation event, Emitter emit) async { + Future _onLoadAutomation( + LoadAutomation event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { @@ -164,14 +176,16 @@ class RoutineBloc extends Bloc { } } - FutureOr _onSearchRoutines(SearchRoutines event, Emitter emit) async { + FutureOr _onSearchRoutines( + SearchRoutines event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); await Future.delayed(const Duration(seconds: 1)); emit(state.copyWith(isLoading: false, errorMessage: null)); emit(state.copyWith(searchText: event.query)); } - FutureOr _onAddSelectedIcon(AddSelectedIcon event, Emitter emit) { + FutureOr _onAddSelectedIcon( + AddSelectedIcon event, Emitter emit) { emit(state.copyWith(selectedIcon: event.icon)); } @@ -180,7 +194,8 @@ class RoutineBloc extends Bloc { return actions.first['deviceId'] == 'delay'; } - Future _onCreateScene(CreateSceneEvent event, Emitter emit) async { + Future _onCreateScene( + CreateSceneEvent event, Emitter emit) async { try { // Check if first action is delay if (_isFirstActionDelay(state.thenItems)) { @@ -255,7 +270,8 @@ class RoutineBloc extends Bloc { } } - Future _onCreateAutomation(CreateAutomationEvent event, Emitter emit) async { + Future _onCreateAutomation( + CreateAutomationEvent event, Emitter emit) async { try { if (state.routineName == null || state.routineName!.isEmpty) { emit(state.copyWith( @@ -355,17 +371,21 @@ class RoutineBloc extends Bloc { } } - FutureOr _onRemoveDragCard(RemoveDragCard event, Emitter emit) { + FutureOr _onRemoveDragCard( + RemoveDragCard event, Emitter emit) { if (event.isFromThen) { final thenItems = List>.from(state.thenItems); - final selectedFunctions = Map>.from(state.selectedFunctions); + final selectedFunctions = + Map>.from(state.selectedFunctions); thenItems.removeAt(event.index); selectedFunctions.remove(event.key); - emit(state.copyWith(thenItems: thenItems, selectedFunctions: selectedFunctions)); + emit(state.copyWith( + thenItems: thenItems, selectedFunctions: selectedFunctions)); } else { final ifItems = List>.from(state.ifItems); - final selectedFunctions = Map>.from(state.selectedFunctions); + final selectedFunctions = + Map>.from(state.selectedFunctions); ifItems.removeAt(event.index); selectedFunctions.remove(event.key); @@ -376,7 +396,8 @@ class RoutineBloc extends Bloc { isAutomation: false, isTabToRun: false)); } else { - emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions)); + emit(state.copyWith( + ifItems: ifItems, selectedFunctions: selectedFunctions)); } } } @@ -388,15 +409,18 @@ class RoutineBloc extends Bloc { )); } - FutureOr _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter emit) { + FutureOr _onEffectiveTimeEvent( + EffectiveTimePeriodEvent event, Emitter emit) { emit(state.copyWith(effectiveTime: event.effectiveTime)); } - FutureOr _onSetRoutineName(SetRoutineName event, Emitter emit) { + FutureOr _onSetRoutineName( + SetRoutineName event, Emitter emit) { emit(state.copyWith(routineName: event.name)); } - Future _onGetSceneDetails(GetSceneDetails event, Emitter emit) async { + Future _onGetSceneDetails( + GetSceneDetails event, Emitter emit) async { try { emit(state.copyWith( isLoading: true, @@ -424,7 +448,8 @@ class RoutineBloc extends Bloc { isTabToRun: false, isUpdate: true, )); - final automationDetails = await SceneApi.getAutomationDetails(event.automationId); + final automationDetails = + await SceneApi.getAutomationDetails(event.automationId); add(InitializeRoutineState(automationDetails)); } catch (e) { emit(state.copyWith( @@ -434,81 +459,83 @@ class RoutineBloc extends Bloc { } } - void _onInitializeRoutineState(InitializeRoutineState event, Emitter emit) { - final routineDetails = event.routineDetails; + // void _onInitializeRoutineState( + // InitializeRoutineState event, Emitter emit) { + // final routineDetails = event.routineDetails; - // Convert actions to draggable cards for the THEN container - final thenItems = routineDetails.actions.map((action) { - final Map cardData = { - 'entityId': action.entityId, - 'uniqueCustomId': const Uuid().v4(), - 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId, - 'title': action.actionExecutor == 'delay' ? 'Delay' : 'Device', - // fix this - 'imagePath': action.actionExecutor == 'delay' ? Assets.delay : Assets.logo, - }; + // // Convert actions to draggable cards for the THEN container + // final thenItems = routineDetails.actions.map((action) { + // final Map cardData = { + // 'entityId': action.entityId, + // 'uniqueCustomId': const Uuid().v4(), + // 'deviceId': + // action.actionExecutor == 'delay' ? 'delay' : action.entityId, + // 'title': action.actionExecutor == 'delay' ? 'Delay' : 'Device', + // // fix this + // 'imagePath': + // action.actionExecutor == 'delay' ? Assets.delay : Assets.logo, + // }; - // Add functions to selectedFunctions - if (action.executorProperty != null) { - final functions = [ - DeviceFunctionData( - entityId: action.entityId, - functionCode: action.executorProperty!.functionCode ?? '', - value: action.executorProperty!.functionValue, + // // Add functions to selectedFunctions + // if (action.executorProperty != null) { + // final functions = [ + // DeviceFunctionData( + // entityId: action.entityId, + // functionCode: action.executorProperty!.functionCode ?? '', + // value: action.executorProperty!.functionValue, - /// fix this - operationName: action.executorProperty?.functionCode ?? ''), - ]; - state.selectedFunctions[cardData['uniqueCustomId']] = functions; - } + // /// fix this + // operationName: action.executorProperty?.functionCode ?? ''), + // ]; + // state.selectedFunctions[cardData['uniqueCustomId']] = functions; + // } - return cardData; - }).toList() ?? - []; + // return cardData; + // }).toList(); - // Convert conditions to draggable cards for the IF container - final ifItems = routineDetails.conditions?.map((condition) { - final Map cardData = { - 'entityId': condition.entityId, - 'uniqueCustomId': const Uuid().v4(), - 'deviceId': condition.entityId, + // // Convert conditions to draggable cards for the IF container + // final ifItems = routineDetails.conditions?.map((condition) { + // final Map cardData = { + // 'entityId': condition.entityId, + // 'uniqueCustomId': const Uuid().v4(), + // 'deviceId': condition.entityId, - /// fix this - 'title': 'Device', + // /// fix this + // 'title': 'Device', - /// fix this - 'imagePath': Assets.logo, - }; + // /// fix this + // 'imagePath': Assets.logo, + // }; - // Add functions to selectedFunctions - final functions = [ - DeviceFunctionData( - entityId: condition.entityId, - functionCode: condition.expr.statusCode, - value: condition.expr.statusValue, - condition: condition.expr.comparator, - operationName: condition.expr.comparator, - ), - ]; - state.selectedFunctions[cardData['uniqueCustomId']] = functions; + // // Add functions to selectedFunctions + // final functions = [ + // DeviceFunctionData( + // entityId: condition.entityId, + // functionCode: condition.expr.statusCode, + // value: condition.expr.statusValue, + // condition: condition.expr.comparator, + // operationName: condition.expr.comparator, + // ), + // ]; + // state.selectedFunctions[cardData['uniqueCustomId']] = functions; - return cardData; - }).toList() ?? - []; + // return cardData; + // }).toList() ?? + // []; - emit(state.copyWith( - isLoading: false, - routineName: routineDetails.name, - selectedIcon: routineDetails.iconId, - selectedAutomationOperator: routineDetails.decisionExpr, - effectiveTime: routineDetails.effectiveTime, - isAutomation: routineDetails.conditions != null, - isTabToRun: routineDetails.conditions == null, - thenItems: thenItems, - ifItems: ifItems, - selectedFunctions: Map.from(state.selectedFunctions), - )); - } + // emit(state.copyWith( + // isLoading: false, + // routineName: routineDetails.name, + // selectedIcon: routineDetails.iconId, + // selectedAutomationOperator: routineDetails.decisionExpr, + // effectiveTime: routineDetails.effectiveTime, + // isAutomation: routineDetails.conditions != null, + // isTabToRun: routineDetails.conditions == null, + // thenItems: thenItems, + // ifItems: ifItems, + // selectedFunctions: Map.from(state.selectedFunctions), + // )); + // } RoutineState _resetState() { return const RoutineState( @@ -531,7 +558,38 @@ class RoutineBloc extends Bloc { ); } - FutureOr _onResetRoutineState(ResetRoutineState event, Emitter emit) { + FutureOr _onResetRoutineState( + ResetRoutineState event, Emitter emit) { emit(_resetState()); } + + FutureOr _deleteScene(DeleteScene event, Emitter emit) { + try { + // emit(state.copyWith(isLoading: true)); + SceneApi.deleteScene(unitUuid: spaceId, sceneId: event.sceneId); + add(const LoadScenes(spaceId, communityId)); + //emit(_resetState()); + } catch (e) { + emit(state.copyWith( + isLoading: false, + errorMessage: 'Failed to delete scene', + )); + } + } + + FutureOr _deleteAutomation( + DeleteAutomation event, Emitter emit) { + try { + //emit(state.copyWith(isLoading: true)); + SceneApi.deleteAutomation( + unitUuid: spaceId, automationId: event.automationId); + add(const LoadAutomation(spaceId)); + // emit(_resetState()); + } catch (e) { + emit(state.copyWith( + isLoading: false, + errorMessage: 'Failed to delete automation', + )); + } + } } diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart index ad93a5ac..82e7875b 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart @@ -158,6 +158,22 @@ class InitializeRoutineState extends RoutineEvent { List get props => [routineDetails]; } +class DeleteScene extends RoutineEvent { + final String sceneId; + final String unitUuid; + const DeleteScene({required this.sceneId, required this.unitUuid}); + @override + List get props => [sceneId]; +} + +class DeleteAutomation extends RoutineEvent { + final String automationId; + final String unitUuid; + const DeleteAutomation({required this.automationId, required this.unitUuid}); + @override + List get props => [automationId]; +} + class ResetRoutineState extends RoutineEvent {} class ClearFunctions extends RoutineEvent {} diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index db8ad1c9..5b4de81f 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -81,7 +83,9 @@ class DraggableCard extends StatelessWidget { ? SvgPicture.asset( imagePath, ) - : Image.network(imagePath), + : Image.memory( + base64Decode(imagePath), + ), ), const SizedBox(height: 8), Padding( diff --git a/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart b/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart index 6f4dfb8f..c9a5114f 100644 --- a/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart +++ b/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart @@ -11,7 +11,8 @@ class FetchRoutineScenesAutomation extends StatefulWidget { const FetchRoutineScenesAutomation({super.key}); @override - State createState() => _FetchRoutineScenesState(); + State createState() => + _FetchRoutineScenesState(); } class _FetchRoutineScenesState extends State @@ -66,12 +67,49 @@ class _FetchRoutineScenesState extends State padding: EdgeInsets.only( right: isSmallScreenSize(context) ? 4.0 : 8.0, ), - child: RoutineViewCard( - onTap: () {}, - textString: state.scenes[index].name, - icon: state.scenes[index].icon ?? Assets.logoHorizontal, - isFromScenes: true, - iconInBytes: state.scenes[index].iconInBytes, + child: Stack( + children: [ + RoutineViewCard( + onTap: () {}, + textString: state.scenes[index].name, + icon: state.scenes[index].icon ?? + Assets.logoHorizontal, + isFromScenes: true, + iconInBytes: + state.scenes[index].iconInBytes, + ), + Positioned( + top: 0, + right: 0, + child: InkWell( + onTap: () => context + .read() + .add( + DeleteScene( + sceneId: state.scenes[index].id, + unitUuid: spaceId, + ), + ), + child: Container( + height: 20, + width: 20, + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + shape: BoxShape.circle, + border: Border.all( + color: ColorsManager.grayColor, + width: 2.0, + ), + ), + child: const Center( + child: Icon(Icons.delete, + size: 15, + color: ColorsManager.grayColor), + ), + ), + ), + ), + ], ), ), ), @@ -104,10 +142,46 @@ class _FetchRoutineScenesState extends State padding: EdgeInsets.only( right: isSmallScreenSize(context) ? 4.0 : 8.0, ), - child: RoutineViewCard( - onTap: () {}, - textString: state.automations[index].name, - icon: state.automations[index].icon ?? Assets.automation, + child: Stack( + children: [ + RoutineViewCard( + onTap: () {}, + textString: state.automations[index].name, + icon: state.automations[index].icon ?? + Assets.automation, + ), + Positioned( + top: 0, + right: 0, + child: InkWell( + onTap: () => + context.read().add( + DeleteAutomation( + automationId: state + .automations[index].id, + unitUuid: spaceId, + ), + ), + child: Container( + height: 20, + width: 20, + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + shape: BoxShape.circle, + border: Border.all( + color: ColorsManager.grayColor, + width: 2.0, + ), + ), + child: const Center( + child: Icon(Icons.delete, + size: 15, + color: ColorsManager.grayColor), + ), + ), + ), + ), + ], ), ), ), diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index 7fc3b1bf..333156ba 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -212,38 +212,39 @@ class SceneApi { // } // } // - // //delete Scene - // - // static Future deleteScene( - // {required String unitUuid, required String sceneId}) async { - // try { - // final response = await _httpService.delete( - // path: ApiEndpoints.deleteScene - // .replaceAll('{sceneId}', sceneId) - // .replaceAll('{unitUuid}', unitUuid), - // showServerMessage: false, - // expectedResponseModel: (json) => json['statusCode'] == 200, - // ); - // return response; - // } catch (e) { - // rethrow; - // } - // } - // - // // delete automation - // static Future deleteAutomation( - // {required String unitUuid, required String automationId}) async { - // try { - // final response = await _httpService.delete( - // path: ApiEndpoints.deleteAutomation - // .replaceAll('{automationId}', automationId) - // .replaceAll('{unitUuid}', unitUuid), - // showServerMessage: false, - // expectedResponseModel: (json) => json['statusCode'] == 200, - // ); - // return response; - // } catch (e) { - // rethrow; - // } - // } + + + //delete Scene + static Future deleteScene( + {required String unitUuid, required String sceneId}) async { + try { + final response = await _httpService.delete( + path: ApiEndpoints.deleteScene + .replaceAll('{sceneId}', sceneId) + .replaceAll('{unitUuid}', unitUuid), + showServerMessage: false, + expectedResponseModel: (json) => json['statusCode'] == 200, + ); + return response; + } catch (e) { + rethrow; + } + } + + // delete automation + static Future deleteAutomation( + {required String unitUuid, required String automationId}) async { + try { + final response = await _httpService.delete( + path: ApiEndpoints.deleteAutomation + .replaceAll('{automationId}', automationId) + .replaceAll('{unitUuid}', unitUuid), + showServerMessage: false, + expectedResponseModel: (json) => json['statusCode'] == 200, + ); + return response; + } catch (e) { + rethrow; + } + } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 26af5793..dcf6b367 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -59,4 +59,7 @@ abstract class ApiEndpoints { static const String getAutomationDetails = '/automation/details/{automationId}'; static const String getScene = '/scene/tap-to-run/{sceneId}'; + static const String deleteScene = '/scene/tap-to-run/{sceneId}'; + + static const String deleteAutomation = '/automation/{automationId}'; }