diff --git a/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart b/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart index 7b133d45..8c9820f9 100644 --- a/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart +++ b/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart @@ -4,8 +4,8 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; -import 'package:syncrow_web/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart'; -import 'package:syncrow_web/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/schedule_view/opening_clsoing_time_dialog_body.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/schedule_view/time_out_alarm_dialog_body.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_display_data.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart'; diff --git a/lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart b/lib/pages/device_managment/garage_door/schedule_view/opening_clsoing_time_dialog_body.dart similarity index 88% rename from lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart rename to lib/pages/device_managment/garage_door/schedule_view/opening_clsoing_time_dialog_body.dart index 843bac9b..0cc9485a 100644 --- a/lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart +++ b/lib/pages/device_managment/garage_door/schedule_view/opening_clsoing_time_dialog_body.dart @@ -1,14 +1,13 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; -import 'package:syncrow_web/pages/device_managment/garage_door/widgets/seconds_picker.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/schedule_view/seconds_picker.dart'; class OpeningAndClosingTimeDialogBody extends StatefulWidget { final ValueChanged onDurationChanged; final GarageDoorBloc bloc; - OpeningAndClosingTimeDialogBody({ + const OpeningAndClosingTimeDialogBody({ required this.onDurationChanged, required this.bloc, }); diff --git a/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart b/lib/pages/device_managment/garage_door/schedule_view/schedule__garage_table.dart similarity index 80% rename from lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart rename to lib/pages/device_managment/garage_door/schedule_view/schedule__garage_table.dart index 07cd9c7a..525e79c9 100644 --- a/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart +++ b/lib/pages/device_managment/garage_door/schedule_view/schedule__garage_table.dart @@ -26,7 +26,7 @@ class ScheduleGarageTableWidget extends StatelessWidget { Table( border: TableBorder.all( color: ColorsManager.graysColor, - borderRadius: const BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20)), + borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), ), children: [ TableRow( @@ -50,17 +50,20 @@ class ScheduleGarageTableWidget extends StatelessWidget { BlocBuilder( builder: (context, state) { if (state is ScheduleGarageLoadingState) { - return const SizedBox(height: 200, child: Center(child: CircularProgressIndicator())); + return const SizedBox( + height: 200, + child: Center(child: CircularProgressIndicator())); } - if (state is GarageDoorLoadedState && state.status.schedules?.isEmpty == true) { + if (state is GarageDoorLoadedState && + state.status.schedules!.isEmpty) { return _buildEmptyState(context); } else if (state is GarageDoorLoadedState) { return Container( height: 200, decoration: BoxDecoration( border: Border.all(color: ColorsManager.graysColor), - borderRadius: - const BorderRadius.only(bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)), + borderRadius: const BorderRadius.vertical( + bottom: Radius.circular(20)), ), child: _buildTableBody(state, context)); } @@ -78,7 +81,7 @@ class ScheduleGarageTableWidget extends StatelessWidget { height: 200, decoration: BoxDecoration( border: Border.all(color: ColorsManager.graysColor), - borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)), + borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), ), child: Center( child: Column( @@ -112,7 +115,8 @@ class ScheduleGarageTableWidget extends StatelessWidget { children: [ if (state.status.schedules != null) for (int i = 0; i < state.status.schedules!.length; i++) - _buildScheduleRow(state.status.schedules![i], i, context, state), + _buildScheduleRow( + state.status.schedules![i], i, context, state), ], ), ), @@ -134,7 +138,8 @@ class ScheduleGarageTableWidget extends StatelessWidget { ); } - TableRow _buildScheduleRow(ScheduleModel schedule, int index, BuildContext context, GarageDoorLoadedState state) { + TableRow _buildScheduleRow(ScheduleModel schedule, int index, + BuildContext context, GarageDoorLoadedState state) { return TableRow( children: [ Center( @@ -152,7 +157,8 @@ class ScheduleGarageTableWidget extends StatelessWidget { width: 24, height: 24, child: schedule.enable - ? const Icon(Icons.radio_button_checked, color: ColorsManager.blueColor) + ? const Icon(Icons.radio_button_checked, + color: ColorsManager.blueColor) : const Icon( Icons.radio_button_unchecked, color: ColorsManager.grayColor, @@ -160,7 +166,9 @@ class ScheduleGarageTableWidget extends StatelessWidget { ), ), ), - Center(child: Text(_getSelectedDays(ScheduleModel.parseSelectedDays(schedule.days)))), + Center( + child: Text(_getSelectedDays( + ScheduleModel.parseSelectedDays(schedule.days)))), Center(child: Text(formatIsoStringToTime(schedule.time, context))), Center(child: Text(schedule.function.value ? 'On' : 'Off')), Center( @@ -170,18 +178,24 @@ class ScheduleGarageTableWidget extends StatelessWidget { TextButton( style: TextButton.styleFrom(padding: EdgeInsets.zero), onPressed: () { - GarageDoorDialogHelper.showAddGarageDoorScheduleDialog(context, - schedule: schedule, index: index, isEdit: true); + GarageDoorDialogHelper.showAddGarageDoorScheduleDialog( + context, + schedule: schedule, + index: index, + isEdit: true); }, child: Text( 'Edit', - style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blueColor), + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blueColor), ), ), TextButton( style: TextButton.styleFrom(padding: EdgeInsets.zero), onPressed: () { - context.read().add(DeleteGarageDoorScheduleEvent( + context + .read() + .add(DeleteGarageDoorScheduleEvent( index: index, scheduleId: schedule.scheduleId, deviceId: state.status.uuid, @@ -189,7 +203,8 @@ class ScheduleGarageTableWidget extends StatelessWidget { }, child: Text( 'Delete', - style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blueColor), + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blueColor), ), ), ], diff --git a/lib/pages/device_managment/garage_door/widgets/schedule_garage_header.dart b/lib/pages/device_managment/garage_door/schedule_view/schedule_garage_header.dart similarity index 100% rename from lib/pages/device_managment/garage_door/widgets/schedule_garage_header.dart rename to lib/pages/device_managment/garage_door/schedule_view/schedule_garage_header.dart diff --git a/lib/pages/device_managment/garage_door/widgets/schedule_garage_managment_ui.dart b/lib/pages/device_managment/garage_door/schedule_view/schedule_garage_managment_ui.dart similarity index 93% rename from lib/pages/device_managment/garage_door/widgets/schedule_garage_managment_ui.dart rename to lib/pages/device_managment/garage_door/schedule_view/schedule_garage_managment_ui.dart index e5819e89..9b2c2b3f 100644 --- a/lib/pages/device_managment/garage_door/widgets/schedule_garage_managment_ui.dart +++ b/lib/pages/device_managment/garage_door/schedule_view/schedule_garage_managment_ui.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; -import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule__garage_table.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/schedule_view/schedule__garage_table.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; diff --git a/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart b/lib/pages/device_managment/garage_door/schedule_view/schedule_garage_mode_buttons.dart similarity index 100% rename from lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart rename to lib/pages/device_managment/garage_door/schedule_view/schedule_garage_mode_buttons.dart diff --git a/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_selector.dart b/lib/pages/device_managment/garage_door/schedule_view/schedule_garage_mode_selector.dart similarity index 100% rename from lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_selector.dart rename to lib/pages/device_managment/garage_door/schedule_view/schedule_garage_mode_selector.dart diff --git a/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart b/lib/pages/device_managment/garage_door/schedule_view/schedule_garage_view.dart similarity index 91% rename from lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart rename to lib/pages/device_managment/garage_door/schedule_view/schedule_garage_view.dart index 107c8e0a..7b772135 100644 --- a/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart +++ b/lib/pages/device_managment/garage_door/schedule_view/schedule_garage_view.dart @@ -4,9 +4,9 @@ import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_ import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/helper/garage_door_helper.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart'; -import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_header.dart'; -import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_managment_ui.dart'; -import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/schedule_view/schedule_garage_header.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/schedule_view/schedule_garage_managment_ui.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/schedule_view/schedule_garage_mode_buttons.dart'; class BuildGarageDoorScheduleView extends StatefulWidget { const BuildGarageDoorScheduleView({super.key, required this.status}); diff --git a/lib/pages/device_managment/garage_door/widgets/seconds_picker.dart b/lib/pages/device_managment/garage_door/schedule_view/seconds_picker.dart similarity index 100% rename from lib/pages/device_managment/garage_door/widgets/seconds_picker.dart rename to lib/pages/device_managment/garage_door/schedule_view/seconds_picker.dart diff --git a/lib/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart b/lib/pages/device_managment/garage_door/schedule_view/time_out_alarm_dialog_body.dart similarity index 100% rename from lib/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart rename to lib/pages/device_managment/garage_door/schedule_view/time_out_alarm_dialog_body.dart diff --git a/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart b/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart index ae2fc9e4..30d9bf5d 100644 --- a/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart +++ b/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart @@ -5,7 +5,7 @@ import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_ import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/helper/garage_door_helper.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart'; -import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_view.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/schedule_view/schedule_garage_view.dart'; import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart'; import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; diff --git a/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_switch_control_view.dart b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_switch_control_view.dart index 997be513..1ad5d43b 100644 --- a/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_switch_control_view.dart +++ b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_switch_control_view.dart @@ -3,11 +3,14 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/factories/one_gang_glass_switch_bloc_factory.dart'; import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_control_button.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class OneGangGlassSwitchControlView extends StatelessWidget with HelperResponsiveLayout { +class OneGangGlassSwitchControlView extends StatelessWidget + with HelperResponsiveLayout { final String deviceId; const OneGangGlassSwitchControlView({required this.deviceId, super.key}); @@ -16,7 +19,8 @@ class OneGangGlassSwitchControlView extends StatelessWidget with HelperResponsiv Widget build(BuildContext context) { return BlocProvider( create: (context) => - OneGangGlassSwitchBlocFactory.create(deviceId: deviceId)..add(OneGangGlassSwitchFetchDeviceEvent(deviceId)), + OneGangGlassSwitchBlocFactory.create(deviceId: deviceId) + ..add(OneGangGlassSwitchFetchDeviceEvent(deviceId)), child: BlocBuilder( builder: (context, state) { if (state is OneGangGlassSwitchLoading) { @@ -33,7 +37,8 @@ class OneGangGlassSwitchControlView extends StatelessWidget with HelperResponsiv ); } - Widget _buildStatusControls(BuildContext context, OneGangGlassStatusModel status) { + Widget _buildStatusControls( + BuildContext context, OneGangGlassStatusModel status) { final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); @@ -76,14 +81,21 @@ class OneGangGlassSwitchControlView extends StatelessWidget with HelperResponsiv onChange: (value) {}, showToggle: false, ), - ToggleWidget( - value: false, - code: '', - deviceId: deviceId, - label: 'Scheduling', - icon: Assets.scheduling, - onChange: (value) {}, - showToggle: false, + ScheduleControlButton( + onTap: () { + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: BlocProvider.of(context), + child: BuildScheduleView( + category: 'switch_1', + deviceUuid: deviceId, + ), + )); + }, + mainText: '', + subtitle: 'Scheduling', + iconPath: Assets.scheduling, ), ], ); diff --git a/lib/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart b/lib/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart index f1861c55..2f6008d2 100644 --- a/lib/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart +++ b/lib/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart @@ -5,7 +5,10 @@ import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_lig import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/factories/wall_light_switch_bloc_factory.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_control_button.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; class WallLightDeviceControl extends StatelessWidget @@ -55,7 +58,6 @@ class WallLightDeviceControl extends StatelessWidget mainAxisSpacing: 12, ), children: [ - const SizedBox(), ToggleWidget( value: status.switch1, code: 'switch_1', @@ -69,7 +71,22 @@ class WallLightDeviceControl extends StatelessWidget )); }, ), - const SizedBox(), + ScheduleControlButton( + onTap: () { + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: BlocProvider.of(context), + child: BuildScheduleView( + category: 'switch_1', + deviceUuid: deviceId, + ), + )); + }, + mainText: '', + subtitle: 'Scheduling', + iconPath: Assets.scheduling, + ), ], ); } diff --git a/lib/pages/device_managment/schedule_device/bloc/schedule_bloc.dart b/lib/pages/device_managment/schedule_device/bloc/schedule_bloc.dart new file mode 100644 index 00000000..c4e731db --- /dev/null +++ b/lib/pages/device_managment/schedule_device/bloc/schedule_bloc.dart @@ -0,0 +1,620 @@ +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; +import 'package:syncrow_web/services/control_device_service.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; +part 'schedule_event.dart'; +part 'schedule_state.dart'; + +class ScheduleBloc extends Bloc { + final String deviceId; + + ScheduleBloc({ + required this.deviceId, + }) : super(ScheduleInitial()) { + on(_initializeAddSchedule); + on(_updateSelectedTime); + on(_updateSelectedDay); + on(_updateFunctionOn); + on(_getSchedule); + on(_onAddSchedule); + on(_onEditSchedule); + on(_onUpdateSchedule); + on(_onUpdateScheduleMode); + on(_onUpdateCountdownTime); + on(_onUpdateInchingTime); + on(_onStartScheduleEvent); + on(_onStopScheduleEvent); + on(_onDecrementCountdown); + on(_fetchStatus); + on(_onDeleteSchedule); + } + Timer? _countdownTimer; + Duration countdownRemaining = Duration.zero; + + Future _onStopScheduleEvent( + StopScheduleEvent event, + Emitter emit, + ) async { + if (state is ScheduleLoaded) { + final currentState = state as ScheduleLoaded; + + final success = await RemoteControlDeviceService().controlDevice( + deviceUuid: deviceId, + status: Status( + code: 'countdown_1', + value: 0, + ), + ); + if (success) { + _countdownTimer?.cancel(); + if (event.mode == ScheduleModes.countdown) { + emit(currentState.copyWith( + countdownHours: 0, + countdownMinutes: 0, + isCountdownActive: false, + countdownRemaining: Duration.zero, + )); + } else if (event.mode == ScheduleModes.inching) { + emit(currentState.copyWith( + inchingHours: 0, + inchingMinutes: 0, + isInchingActive: false, + countdownRemaining: Duration.zero, + )); + } + } else { + emit(const ScheduleError('Failed to stop schedule')); + } + } + } + + void _onUpdateScheduleMode( + UpdateScheduleModeEvent event, + Emitter emit, + ) { + if (state is ScheduleLoaded) { + final currentState = state as ScheduleLoaded; + emit(currentState.copyWith( + scheduleMode: event.scheduleMode, + countdownRemaining: Duration.zero, + )); + } + } + + void _onUpdateCountdownTime( + UpdateCountdownTimeEvent event, + Emitter emit, + ) { + if (state is ScheduleLoaded) { + final currentState = state as ScheduleLoaded; + emit(currentState.copyWith( + countdownHours: event.hours, + countdownMinutes: event.minutes, + inchingHours: 0, + inchingMinutes: 0, + countdownRemaining: Duration.zero, + )); + } + } + + void _onUpdateInchingTime( + UpdateInchingTimeEvent event, + Emitter emit, + ) { + if (state is ScheduleLoaded) { + final currentState = state as ScheduleLoaded; + emit(currentState.copyWith( + inchingHours: event.hours, + inchingMinutes: event.minutes, + countdownRemaining: Duration.zero, + )); + } + } + + void _initializeAddSchedule( + ScheduleInitializeAddEvent event, + Emitter emit, + ) { + if (state is ScheduleLoaded) { + final currentState = state as ScheduleLoaded; + emit(currentState.copyWith( + selectedTime: event.selectedTime, + selectedDays: event.selectedDays ?? List.filled(7, false), + functionOn: event.functionOn ?? false, + isEditing: event.isEditing, + scheduleMode: event.scheduleMode, + countdownRemaining: Duration.zero, + )); + } else { + emit(ScheduleLoaded( + schedules: const [], + selectedTime: event.selectedTime, + selectedDays: event.selectedDays ?? List.filled(7, false), + functionOn: event.functionOn ?? false, + isEditing: event.isEditing, + deviceId: deviceId, + scheduleMode: event.scheduleMode, + countdownHours: 0, + countdownMinutes: 0, + inchingHours: 0, + inchingMinutes: 0, + isCountdownActive: false, + isInchingActive: false, + )); + } + } + + void _updateSelectedTime( + ScheduleUpdateSelectedTimeEvent event, + Emitter emit, + ) { + if (state is ScheduleLoaded) { + final currentState = state as ScheduleLoaded; + emit(currentState.copyWith( + selectedTime: event.selectedTime, + countdownHours: 0, + countdownMinutes: 0, + inchingHours: 0, + inchingMinutes: 0, + countdownRemaining: Duration.zero, + )); + } + } + + void _updateSelectedDay( + ScheduleUpdateSelectedDayEvent event, + Emitter emit, + ) { + if (state is ScheduleLoaded) { + final currentState = state as ScheduleLoaded; + final updatedDays = List.from(currentState.selectedDays); + updatedDays[event.index] = event.value; + emit(currentState.copyWith( + selectedDays: updatedDays, + countdownHours: 0, + countdownMinutes: 0, + inchingHours: 0, + inchingMinutes: 0, + countdownRemaining: Duration.zero, + )); + } + } + + void _updateFunctionOn( + ScheduleUpdateFunctionOnEvent event, + Emitter emit, + ) { + if (state is ScheduleLoaded) { + final currentState = state as ScheduleLoaded; + emit(currentState.copyWith( + functionOn: event.isOn, + countdownHours: 0, + countdownMinutes: 0, + inchingHours: 0, + inchingMinutes: 0, + countdownRemaining: Duration.zero, + )); + } + } + + Future _getSchedule( + ScheduleGetEvent event, + Emitter emit, + ) async { + try { + emit(ScheduleLoading()); + final schedules = await DevicesManagementApi().getDeviceSchedules( + deviceId, + event.category, + ); + + if (state is ScheduleLoaded) { + final currentState = state as ScheduleLoaded; + emit(currentState.copyWith( + schedules: schedules, + selectedTime: null, + selectedDays: List.filled(7, false), + functionOn: false, + isEditing: false, + countdownRemaining: Duration.zero, + )); + } else { + emit(ScheduleLoaded( + schedules: schedules, + selectedTime: null, + selectedDays: List.filled(7, false), + functionOn: false, + isEditing: false, + deviceId: deviceId, + scheduleMode: ScheduleModes.schedule, + countdownHours: 0, + countdownMinutes: 0, + inchingHours: 0, + inchingMinutes: 0, + isCountdownActive: false, + isInchingActive: false, + )); + } + } catch (e) { + emit(ScheduleError('Failed to load schedules: $e')); + } + } + + Future _onAddSchedule( + ScheduleAddEvent event, + Emitter emit, + ) async { + try { + if (state is ScheduleLoaded) { + final dateTime = DateTime.parse(event.time); + final success = await DevicesManagementApi().postSchedule( + category: event.category, + deviceId: deviceId, + time: getTimeStampWithoutSeconds(dateTime).toString(), + code: 'switch_1', + value: event.functionOn, + days: event.selectedDays); + if (success) { + add(const ScheduleGetEvent(category: 'switch_1')); + } else { + emit(const ScheduleError('Failed to add schedule')); + } + } + } catch (e) { + emit(ScheduleError('Failed to add schedule: $e')); + } + } + + Future _onEditSchedule( + ScheduleEditEvent event, + Emitter emit, + ) async { + try { + if (state is ScheduleLoaded) { + final dateTime = DateTime.parse(event.time); + final updatedSchedule = ScheduleEntry( + scheduleId: event.scheduleId, + category: event.category, + time: getTimeStampWithoutSeconds(dateTime).toString(), + function: Status(code: 'switch_1', value: event.functionOn), + days: event.selectedDays, + ); + final success = await DevicesManagementApi().editScheduleRecord( + deviceId, + updatedSchedule, + ); + + if (success) { + add(const ScheduleGetEvent(category: 'switch_1')); + } else { + emit(const ScheduleError('Failed to update schedule')); + } + } + } catch (e) { + emit(ScheduleError('Failed to update schedule: $e')); + } + } + + Future _onUpdateSchedule( + ScheduleUpdateEntryEvent event, + Emitter emit, + ) async { + try { + if (state is ScheduleLoaded) { + final currentState = state as ScheduleLoaded; + + final updatedSchedules = currentState.schedules.map((schedule) { + if (schedule.scheduleId == event.scheduleId) { + return schedule.copyWith( + function: Status(code: 'switch_1', value: event.functionOn), + enable: event.enable, + ); + } + return schedule; + }).toList(); + + final success = await DevicesManagementApi().updateScheduleRecord( + enable: event.enable, + uuid: deviceId, + scheduleId: event.scheduleId, + ); + + if (success) { + emit(currentState.copyWith( + schedules: updatedSchedules, + countdownHours: 0, + countdownMinutes: 0, + inchingHours: 0, + inchingMinutes: 0, + countdownRemaining: Duration.zero, + )); + } else { + emit(const ScheduleError('Failed to update schedule status')); + } + } + } catch (e) { + emit(ScheduleError('Failed to update schedule: $e')); + } + } + + Future _onDeleteSchedule( + ScheduleDeleteEvent event, + Emitter emit, + ) async { + try { + if (state is ScheduleLoaded) { + final currentState = state as ScheduleLoaded; + final success = await DevicesManagementApi().deleteScheduleRecord( + deviceId, + event.scheduleId, + ); + + if (success) { + final updatedSchedules = currentState.schedules + .where((s) => s.scheduleId != event.scheduleId) + .toList(); + emit(currentState.copyWith( + schedules: updatedSchedules, + countdownHours: 0, + countdownMinutes: 0, + inchingHours: 0, + inchingMinutes: 0, + countdownRemaining: Duration.zero, + )); + } else { + emit(const ScheduleError('Failed to delete schedule')); + } + } + } catch (e) { + emit(ScheduleError('Failed to delete schedule: $e')); + } + } + + Duration? _currentCountdown; + + Future _onStartScheduleEvent( + StartScheduleEvent event, + Emitter emit, + ) async { + if (state is ScheduleLoaded) { + final totalSeconds = + Duration(hours: event.hours, minutes: event.minutes).inSeconds; + final code = event.mode == ScheduleModes.countdown + ? 'countdown_1' + : 'switch_inching'; + final currentState = state as ScheduleLoaded; + final duration = Duration(seconds: totalSeconds); + _currentCountdown = duration; + emit(currentState.copyWith( + countdownRemaining: duration, + schedules: currentState.schedules.map((schedule) { + if (schedule.function.code == code) { + return schedule.copyWith( + function: Status(code: code, value: totalSeconds), + ); + } + return schedule; + }).toList(), + countdownHours: event.mode == ScheduleModes.countdown ? event.hours : 0, + )); + + final success = await RemoteControlDeviceService().controlDevice( + deviceUuid: deviceId, + status: Status( + code: code, + value: totalSeconds, + ), + ); + + if (success) { + if (code == 'countdown_1') { + final countdownDuration = Duration(seconds: totalSeconds); + + emit( + currentState.copyWith( + countdownHours: countdownDuration.inHours, + countdownMinutes: countdownDuration.inMinutes % 60, + countdownRemaining: countdownDuration, + isCountdownActive: true, + ), + ); + + if (countdownDuration.inSeconds > 0) { + _startCountdownTimer(emit, countdownDuration); + } else { + _countdownTimer?.cancel(); + emit( + currentState.copyWith( + countdownHours: 0, + countdownMinutes: 0, + countdownRemaining: Duration.zero, + isCountdownActive: false, + ), + ); + } + } else if (code == 'switch_inching') { + final inchingDuration = Duration(seconds: totalSeconds); + emit( + currentState.copyWith( + inchingHours: inchingDuration.inHours, + inchingMinutes: inchingDuration.inMinutes % 60, + isInchingActive: true, + countdownRemaining: inchingDuration, + ), + ); + } + } + } + } + + void _startCountdownTimer( + Emitter emit, + Duration duration, + ) { + _countdownTimer?.cancel(); + _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { + if (_currentCountdown != null && _currentCountdown! > Duration.zero) { + _currentCountdown = _currentCountdown! - const Duration(seconds: 1); + countdownRemaining = _currentCountdown!; + add(const ScheduleDecrementCountdownEvent()); + } else { + timer.cancel(); + add(StopScheduleEvent( + mode: _currentCountdown == null + ? ScheduleModes.countdown + : ScheduleModes.inching, + deviceId: deviceId, + )); + } + }); + } + + void _onDecrementCountdown( + ScheduleDecrementCountdownEvent event, + Emitter emit, + ) { + if (state is ScheduleLoaded) { + final currentState = state as ScheduleLoaded; + emit(currentState.copyWith( + countdownRemaining: countdownRemaining, + )); + } + } + + @override + Future close() { + _countdownTimer?.cancel(); + return super.close(); + } + + Future _fetchStatus( + ScheduleFetchStatusEvent event, + Emitter emit, + ) async { + emit(ScheduleLoading()); + + try { + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + print(status.status); + final deviceStatus = + WaterHeaterStatusModel.fromJson(event.deviceId, status.status); + + final scheduleMode = + deviceStatus.countdownHours > 0 || deviceStatus.countdownMinutes > 0 + ? ScheduleModes.countdown + : deviceStatus.inchingHours > 0 || deviceStatus.inchingMinutes > 0 + ? ScheduleModes.inching + : ScheduleModes.schedule; + final isCountdown = scheduleMode == ScheduleModes.countdown; + final isInching = scheduleMode == ScheduleModes.inching; + + Duration? countdownRemaining; + var isCountdownActive = false; + var isInchingActive = false; + + if (isCountdown) { + countdownRemaining = Duration( + hours: deviceStatus.countdownHours, + minutes: deviceStatus.countdownMinutes, + ); + isCountdownActive = countdownRemaining > Duration.zero; + } else if (isInching) { + isInchingActive = Duration( + hours: deviceStatus.inchingHours, + minutes: deviceStatus.inchingMinutes, + ) > + Duration.zero; + } + if (state is ScheduleLoaded) { + print('Updating existing state with fetched status'); + print('scheduleMode: $scheduleMode'); + print('countdownRemaining: $countdownRemaining'); + print('isCountdownActive: $isCountdownActive'); + final currentState = state as ScheduleLoaded; + emit(currentState.copyWith( + scheduleMode: scheduleMode, + countdownHours: deviceStatus.countdownHours, + countdownMinutes: deviceStatus.countdownMinutes, + inchingHours: deviceStatus.inchingHours, + inchingMinutes: deviceStatus.inchingMinutes, + isCountdownActive: isCountdownActive, + isInchingActive: isInchingActive, + countdownRemaining: countdownRemaining ?? Duration.zero, + )); + } else { + emit(ScheduleLoaded( + schedules: const [], + selectedTime: null, + selectedDays: List.filled(7, false), + functionOn: false, + isEditing: false, + deviceId: deviceId, + scheduleMode: scheduleMode, + countdownHours: deviceStatus.countdownHours, + countdownMinutes: deviceStatus.countdownMinutes, + inchingHours: deviceStatus.inchingHours, + inchingMinutes: deviceStatus.inchingMinutes, + isCountdownActive: isCountdownActive, + isInchingActive: isInchingActive, + countdownRemaining: countdownRemaining ?? Duration.zero, + )); + } + + // if (isCountdownActive && countdownRemaining != null) { + // _startCountdownTimer(emit, countdownRemaining); + // } + } catch (e) { + emit(ScheduleError('Failed to fetch device status: $e')); + } + } + + String extractTime(String isoDateTime) { + // Example input: "2025-06-19T15:45:00.000" + return isoDateTime.split('T')[1].split('.')[0]; // gives "15:45:00" + } + + int? getTimeStampWithoutSeconds(DateTime? dateTime) { + if (dateTime == null) return null; + DateTime dateTimeWithoutSeconds = DateTime(dateTime.year, dateTime.month, + dateTime.day, dateTime.hour, dateTime.minute); + return dateTimeWithoutSeconds.millisecondsSinceEpoch ~/ 1000; + } + + // Future _updateScheduleEvent( + // StatusUpdatedScheduleEvent event, + // Emitter emit, + // ) async { + // if (state is ScheduleLoaded) { + // final currentState = state as ScheduleLoaded; + + // final updatedSchedules = currentState.schedules.map((schedule) { + // if (schedule.scheduleId == event.scheduleId) { + // return schedule.copyWith( + // function: Status(code: 'switch_1', value: event.functionOn), + // enable: event.enable, + // ); + // } + // return schedule; + // }).toList(); + + // bool success = await DevicesManagementApi().updateScheduleRecord( + // enable: event.enable, + // uuid: currentState.status.uuid, + // scheduleId: event.scheduleId, + // ); + + // if (success) { + // emit(currentState.copyWith(schedules: updatedSchedules)); + // } else { + // emit(currentState); + // } + // } + // } +} diff --git a/lib/pages/device_managment/schedule_device/bloc/schedule_event.dart b/lib/pages/device_managment/schedule_device/bloc/schedule_event.dart new file mode 100644 index 00000000..5099679c --- /dev/null +++ b/lib/pages/device_managment/schedule_device/bloc/schedule_event.dart @@ -0,0 +1,230 @@ +part of 'schedule_bloc.dart'; + +abstract class ScheduleEvent extends Equatable { + const ScheduleEvent(); +} + +class ScheduleInitializeAddEvent extends ScheduleEvent { + final bool isEditing; + final ScheduleModes scheduleMode; + final TimeOfDay? selectedTime; + final List? selectedDays; + final bool? functionOn; + + const ScheduleInitializeAddEvent({ + required this.isEditing, + required this.scheduleMode, + this.selectedTime, + this.selectedDays, + this.functionOn, + }); + + @override + List get props => [ + isEditing, + scheduleMode, + selectedTime, + selectedDays, + functionOn, + ]; +} + +class ScheduleUpdateSelectedTimeEvent extends ScheduleEvent { + final TimeOfDay selectedTime; + + const ScheduleUpdateSelectedTimeEvent(this.selectedTime); + + @override + List get props => [selectedTime]; +} + +class ScheduleUpdateSelectedDayEvent extends ScheduleEvent { + final int index; + final bool value; + + const ScheduleUpdateSelectedDayEvent(this.index, this.value); + + @override + List get props => [index, value]; +} + +class ScheduleUpdateFunctionOnEvent extends ScheduleEvent { + final bool isOn; + + const ScheduleUpdateFunctionOnEvent(this.isOn); + + @override + List get props => [isOn]; +} + +class ScheduleGetEvent extends ScheduleEvent { + final String category; + + const ScheduleGetEvent({required this.category}); + + @override + List get props => [category]; +} + +class ScheduleAddEvent extends ScheduleEvent { + final String category; + final String time; + final List selectedDays; + final bool functionOn; + + const ScheduleAddEvent({ + required this.category, + required this.time, + required this.selectedDays, + required this.functionOn, + }); + + @override + List get props => [category, time, selectedDays, functionOn]; +} + +class ScheduleEditEvent extends ScheduleEvent { + final String scheduleId; + final String category; + final String time; + final List selectedDays; + final bool functionOn; + + const ScheduleEditEvent({ + required this.scheduleId, + required this.category, + required this.time, + required this.selectedDays, + required this.functionOn, + }); + + @override + List get props => [ + scheduleId, + category, + time, + selectedDays, + functionOn, + ]; +} + +class ScheduleDeleteEvent extends ScheduleEvent { + final String scheduleId; + + const ScheduleDeleteEvent(this.scheduleId); + + @override + List get props => [scheduleId]; +} + +class ScheduleUpdateEntryEvent extends ScheduleEvent { + final String scheduleId; + final bool functionOn; + final bool enable; + + const ScheduleUpdateEntryEvent({ + required this.scheduleId, + required this.functionOn, + required this.enable, + }); + + @override + List get props => [scheduleId, functionOn, enable]; +} + +class UpdateScheduleModeEvent extends ScheduleEvent { + final ScheduleModes scheduleMode; + + const UpdateScheduleModeEvent({required this.scheduleMode}); + + @override + List get props => [scheduleMode]; +} + +class UpdateCountdownTimeEvent extends ScheduleEvent { + final int hours; + final int minutes; + + const UpdateCountdownTimeEvent({ + required this.hours, + required this.minutes, + }); + + @override + List get props => [hours, minutes]; +} + +class UpdateInchingTimeEvent extends ScheduleEvent { + final int hours; + final int minutes; + + const UpdateInchingTimeEvent({ + required this.hours, + required this.minutes, + }); + + @override + List get props => [hours, minutes]; +} + +class StartScheduleEvent extends ScheduleEvent { + final ScheduleModes mode; + final int hours; + final int minutes; + + const StartScheduleEvent({ + required this.mode, + required this.hours, + required this.minutes, + }); + + @override + List get props => [mode, hours, minutes]; +} + +class StopScheduleEvent extends ScheduleEvent { + final ScheduleModes mode; + final String deviceId; + + const StopScheduleEvent({ + required this.mode, + required this.deviceId, + }); + + @override + List get props => [mode, deviceId]; +} + +class ScheduleDecrementCountdownEvent extends ScheduleEvent { + const ScheduleDecrementCountdownEvent(); + + @override + List get props => []; +} + +class ScheduleFetchStatusEvent extends ScheduleEvent { + final String deviceId; + + const ScheduleFetchStatusEvent(this.deviceId); + + @override + List get props => [deviceId]; +} + +class DeleteScheduleEvent extends ScheduleEvent { + final String scheduleId; + + const DeleteScheduleEvent(this.scheduleId); + + @override + List get props => [scheduleId]; +} + +class StatusUpdatedScheduleEvent extends ScheduleEvent { + final String id; + + const StatusUpdatedScheduleEvent(this.id); + + @override + List get props => [id]; +} diff --git a/lib/pages/device_managment/schedule_device/bloc/schedule_state.dart b/lib/pages/device_managment/schedule_device/bloc/schedule_state.dart new file mode 100644 index 00000000..10cd7611 --- /dev/null +++ b/lib/pages/device_managment/schedule_device/bloc/schedule_state.dart @@ -0,0 +1,109 @@ +part of 'schedule_bloc.dart'; + +abstract class ScheduleState extends Equatable { + const ScheduleState(); +} + +class ScheduleInitial extends ScheduleState { + @override + List get props => []; +} + +class ScheduleLoading extends ScheduleState { + @override + List get props => []; +} + +class ScheduleLoaded extends ScheduleState { + final List schedules; + final TimeOfDay? selectedTime; + final List selectedDays; + final bool functionOn; + final bool isEditing; + final String deviceId; + final int countdownHours; + final int countdownMinutes; + final bool isCountdownActive; + final int inchingHours; + final int inchingMinutes; + final bool isInchingActive; + final ScheduleModes scheduleMode; + final Duration? countdownRemaining; + + const ScheduleLoaded({ + required this.schedules, + this.selectedTime, + required this.selectedDays, + required this.functionOn, + required this.isEditing, + required this.deviceId, + this.countdownHours = 0, + this.countdownMinutes = 0, + this.isCountdownActive = false, + this.inchingHours = 0, + this.inchingMinutes = 0, + this.isInchingActive = false, + this.scheduleMode = ScheduleModes.countdown, + this.countdownRemaining, + }); + + ScheduleLoaded copyWith({ + List? schedules, + TimeOfDay? selectedTime, + List? selectedDays, + bool? functionOn, + bool? isEditing, + int? countdownHours, + int? countdownMinutes, + bool? isCountdownActive, + int? inchingHours, + int? inchingMinutes, + bool? isInchingActive, + ScheduleModes? scheduleMode, + Duration? countdownRemaining, + }) { + return ScheduleLoaded( + schedules: schedules ?? this.schedules, + selectedTime: selectedTime ?? this.selectedTime, + selectedDays: selectedDays ?? this.selectedDays, + functionOn: functionOn ?? this.functionOn, + isEditing: isEditing ?? this.isEditing, + deviceId: deviceId, + countdownHours: countdownHours ?? this.countdownHours, + countdownMinutes: countdownMinutes ?? this.countdownMinutes, + isCountdownActive: isCountdownActive ?? this.isCountdownActive, + inchingHours: inchingHours ?? this.inchingHours, + inchingMinutes: inchingMinutes ?? this.inchingMinutes, + isInchingActive: isInchingActive ?? this.isInchingActive, + scheduleMode: scheduleMode ?? this.scheduleMode, + countdownRemaining: countdownRemaining ?? this.countdownRemaining, + ); + } + + @override + List get props => [ + schedules, + selectedTime, + selectedDays, + functionOn, + isEditing, + deviceId, + countdownHours, + countdownMinutes, + isCountdownActive, + inchingHours, + inchingMinutes, + isInchingActive, + scheduleMode, + countdownRemaining, + ]; +} + +class ScheduleError extends ScheduleState { + final String error; + + const ScheduleError(this.error); + + @override + List get props => [error]; +} diff --git a/lib/pages/device_managment/water_heater/widgets/count_down_button.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/count_down_button.dart similarity index 72% rename from lib/pages/device_managment/water_heater/widgets/count_down_button.dart rename to lib/pages/device_managment/schedule_device/schedule_widgets/count_down_button.dart index e60c7def..4919018c 100644 --- a/lib/pages/device_managment/water_heater/widgets/count_down_button.dart +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/count_down_button.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/bloc/schedule_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class CountdownModeButtons extends StatelessWidget { @@ -38,14 +39,10 @@ class CountdownModeButtons extends StatelessWidget { ? DefaultButton( height: 40, onPressed: () { - context - .read() - .add(StopScheduleEvent(deviceId)); - context.read().add( - ToggleWaterHeaterEvent( + context.read().add( + StopScheduleEvent( + mode: ScheduleModes.countdown, deviceId: deviceId, - code: 'countdown_1', - value: 0, ), ); }, @@ -55,12 +52,11 @@ class CountdownModeButtons extends StatelessWidget { : DefaultButton( height: 40, onPressed: () { - context.read().add( - ToggleWaterHeaterEvent( - deviceId: deviceId, - code: 'countdown_1', - value: Duration(hours: hours, minutes: minutes) - .inSeconds, + context.read().add( + StartScheduleEvent( + mode: ScheduleModes.countdown, + hours: hours, + minutes: minutes, ), ); }, diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/count_down_inching_view.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/count_down_inching_view.dart new file mode 100644 index 00000000..d45073ec --- /dev/null +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/count_down_inching_view.dart @@ -0,0 +1,185 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/bloc/schedule_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class CountdownInchingView extends StatefulWidget { + const CountdownInchingView({super.key}); + + @override + State createState() => _CountdownInchingViewState(); +} + +class _CountdownInchingViewState extends State { + late FixedExtentScrollController _hoursController; + late FixedExtentScrollController _minutesController; + + int _lastHours = -1; + int _lastMinutes = -1; + + @override + void initState() { + super.initState(); + _hoursController = FixedExtentScrollController(); + _minutesController = FixedExtentScrollController(); + } + + @override + void dispose() { + _hoursController.dispose(); + _minutesController.dispose(); + super.dispose(); + } + + void _updateControllers(int displayHours, int displayMinutes) { + if (_lastHours != displayHours) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_hoursController.hasClients) { + _hoursController.jumpToItem(displayHours); + } + }); + _lastHours = displayHours; + } + if (_lastMinutes != displayMinutes) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_minutesController.hasClients) { + _minutesController.jumpToItem(displayMinutes); + } + }); + _lastMinutes = displayMinutes; + } + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is! ScheduleLoaded) return const SizedBox.shrink(); + + final isCountDown = state.scheduleMode == ScheduleModes.countdown; + final isActive = + isCountDown ? state.isCountdownActive : state.isInchingActive; + final displayHours = isActive && state.countdownRemaining != null + ? state.countdownRemaining!.inHours + : (isCountDown ? state.countdownHours : state.inchingHours); + final displayMinutes = isActive && state.countdownRemaining != null + ? state.countdownRemaining!.inMinutes.remainder(60) + : (isCountDown ? state.countdownMinutes : state.inchingMinutes); + + _updateControllers(displayHours, displayMinutes); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + isCountDown ? 'Countdown:' : 'Inching:', + style: context.textTheme.bodySmall!.copyWith( + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), + const SizedBox(height: 8), + Visibility( + visible: !isCountDown, + child: const Text( + 'Once enabled this feature, each time the device is turned on, ' + 'it will automatically turn off after a preset time.', + ), + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + _buildPickerColumn( + context, + 'h', + displayHours, + 100, + _hoursController, + (value) { + if (!isActive) { + context.read().add(UpdateCountdownTimeEvent( + hours: value, minutes: displayMinutes)); + } + }, + isActive: isActive, + ), + const SizedBox(width: 10), + _buildPickerColumn( + context, + 'm', + displayMinutes, + 60, + _minutesController, + (value) { + if (!isActive) { + context.read().add(UpdateCountdownTimeEvent( + hours: displayHours, minutes: value)); + } + }, + isActive: isActive, + ), + ], + ), + ], + ); + }, + ); + } + + Widget _buildPickerColumn( + BuildContext context, + String label, + int initialValue, + int itemCount, + FixedExtentScrollController controller, + ValueChanged onSelected, { + required bool isActive, + }) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + height: 40, + width: 80, + padding: const EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.circular(8), + ), + child: ListWheelScrollView.useDelegate( + controller: controller, + itemExtent: 40.0, + physics: isActive + ? const NeverScrollableScrollPhysics() + : const FixedExtentScrollPhysics(), + onSelectedItemChanged: isActive ? null : onSelected, + childDelegate: ListWheelChildBuilderDelegate( + builder: (context, index) { + return Center( + child: Text( + index.toString().padLeft(2, '0'), + style: TextStyle( + fontSize: 24, + color: isActive ? ColorsManager.grayColor : Colors.black, + ), + ), + ); + }, + childCount: itemCount, + ), + ), + ), + const SizedBox(width: 8), + Text( + label, + style: const TextStyle( + color: ColorsManager.grayColor, + fontSize: 18, + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/water_heater/widgets/inching_mode_buttons.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/inching_mode_buttons.dart similarity index 82% rename from lib/pages/device_managment/water_heater/widgets/inching_mode_buttons.dart rename to lib/pages/device_managment/schedule_device/schedule_widgets/inching_mode_buttons.dart index 8eec5cca..e75c5d46 100644 --- a/lib/pages/device_managment/water_heater/widgets/inching_mode_buttons.dart +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/inching_mode_buttons.dart @@ -1,8 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/bloc/schedule_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart' + hide StopScheduleEvent; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class InchingModeButtons extends StatelessWidget { @@ -38,15 +41,9 @@ class InchingModeButtons extends StatelessWidget { ? DefaultButton( height: 40, onPressed: () { - context - .read() - .add(StopScheduleEvent(deviceId)); - context.read().add( - ToggleWaterHeaterEvent( - deviceId: deviceId, - code: 'switch_inching', - value: 0, - ), + context.read().add( + StopScheduleEvent( + deviceId: deviceId, mode: ScheduleModes.inching), ); }, backgroundColor: Colors.red, diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart new file mode 100644 index 00000000..2ae5b869 --- /dev/null +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/bloc/schedule_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/count_down_button.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/count_down_inching_view.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/inching_mode_buttons.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_header.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_managment_ui.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_buttons.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_selector.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; + +class BuildScheduleView extends StatelessWidget { + const BuildScheduleView( + {super.key, required this.deviceUuid, required this.category}); + final String deviceUuid; + final String category; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => ScheduleBloc( + deviceId: deviceUuid, + ) + ..add(ScheduleGetEvent(category: category)) + ..add(ScheduleFetchStatusEvent(deviceUuid)), + child: Dialog( + backgroundColor: Colors.white, + insetPadding: const EdgeInsets.all(20), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + child: SizedBox( + width: 700, + child: SingleChildScrollView( + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 40.0, vertical: 20), + child: BlocBuilder( + builder: (context, state) { + if (state is ScheduleLoaded) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const ScheduleHeader(), + const SizedBox(height: 20), + ScheduleModeSelector( + currentMode: state.scheduleMode, + ), + const SizedBox(height: 20), + if (state.scheduleMode == ScheduleModes.schedule) + ScheduleManagementUI( + deviceUuid: deviceUuid, + onAddSchedule: () async { + final entry = await ScheduleDialogHelper + .showAddScheduleDialog( + context, + schedule: null, + isEdit: false, + ); + if (entry != null) { + context.read().add( + ScheduleAddEvent( + category: entry.category, + time: entry.time, + functionOn: entry.function.value, + selectedDays: entry.days, + ), + ); + } + }, + ), + if (state.scheduleMode == ScheduleModes.countdown || + state.scheduleMode == ScheduleModes.inching) + const CountdownInchingView(), + const SizedBox(height: 20), + if (state.scheduleMode == ScheduleModes.countdown) + CountdownModeButtons( + isActive: state.isCountdownActive, + deviceId: deviceUuid, + hours: state.countdownHours, + minutes: state.countdownMinutes, + ), + if (state.scheduleMode == ScheduleModes.inching) + InchingModeButtons( + isActive: state.isInchingActive, + deviceId: deviceUuid, + hours: state.inchingHours, + minutes: state.inchingMinutes, + ), + if (state.scheduleMode != ScheduleModes.countdown && + state.scheduleMode != ScheduleModes.inching) + ScheduleModeButtons( + onSave: () => Navigator.pop(context), + ), + ], + ); + } + return const Center(child: CircularProgressIndicator()); + }, + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_control_button.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_control_button.dart new file mode 100644 index 00000000..86fc5ba5 --- /dev/null +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_control_button.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class ScheduleControlButton extends StatelessWidget { + final VoidCallback onTap; + final String mainText; + final String subtitle; + final String iconPath; + + const ScheduleControlButton({ + super.key, + required this.onTap, + required this.mainText, + required this.subtitle, + required this.iconPath, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: DeviceControlsContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 60, + height: 60, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.whiteColors, + ), + margin: const EdgeInsets.symmetric(horizontal: 4), + padding: const EdgeInsets.all(12), + child: ClipOval( + child: SvgPicture.asset( + iconPath, + fit: BoxFit.fill, + ), + ), + ), + const Spacer(), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + mainText, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.w200, + fontSize: 12, + color: ColorsManager.blackColor, + ), + ), + Text( + subtitle, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor, + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/water_heater/widgets/schedule_header.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_header.dart similarity index 100% rename from lib/pages/device_managment/water_heater/widgets/schedule_header.dart rename to lib/pages/device_managment/schedule_device/schedule_widgets/schedule_header.dart diff --git a/lib/pages/device_managment/water_heater/widgets/schedule_managment_ui.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_managment_ui.dart similarity index 76% rename from lib/pages/device_managment/water_heater/widgets/schedule_managment_ui.dart rename to lib/pages/device_managment/schedule_device/schedule_widgets/schedule_managment_ui.dart index 1710c439..b60f00b9 100644 --- a/lib/pages/device_managment/water_heater/widgets/schedule_managment_ui.dart +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_managment_ui.dart @@ -1,17 +1,16 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/widgets/schedule_table.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class ScheduleManagementUI extends StatelessWidget { - final WaterHeaterDeviceStatusLoaded state; - final Function onAddSchedule; + final String deviceUuid; + final VoidCallback onAddSchedule; const ScheduleManagementUI({ super.key, - required this.state, + required this.deviceUuid, required this.onAddSchedule, }); @@ -28,7 +27,7 @@ class ScheduleManagementUI extends StatelessWidget { padding: 2, backgroundColor: ColorsManager.graysColor, borderRadius: 15, - onPressed: () => onAddSchedule(), + onPressed: onAddSchedule, child: Row( children: [ const Icon(Icons.add, color: ColorsManager.primaryColor), @@ -43,7 +42,7 @@ class ScheduleManagementUI extends StatelessWidget { ), ), const SizedBox(height: 20), - ScheduleTableWidget(state: state), + ScheduleTableWidget(deviceUuid: deviceUuid), ], ); } diff --git a/lib/pages/device_managment/water_heater/widgets/schedule_mode_buttons.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_buttons.dart similarity index 100% rename from lib/pages/device_managment/water_heater/widgets/schedule_mode_buttons.dart rename to lib/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_buttons.dart diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_selector.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_selector.dart new file mode 100644 index 00000000..2bcc0957 --- /dev/null +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_selector.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/bloc/schedule_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class ScheduleModeSelector extends StatelessWidget { + final ScheduleModes currentMode; + + const ScheduleModeSelector({ + super.key, + required this.currentMode, + }); + + @override + Widget build(BuildContext context) { + final currentMode = context.select( + (bloc) => bloc.state is ScheduleLoaded && + (bloc.state as ScheduleLoaded).scheduleMode != null + ? (bloc.state as ScheduleLoaded).scheduleMode + : ScheduleModes.schedule, + ); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Type:', + style: context.textTheme.bodySmall!.copyWith( + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), + const SizedBox(height: 4), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _buildRadioTile( + context, 'Countdown', ScheduleModes.countdown, currentMode), + _buildRadioTile( + context, 'Schedule', ScheduleModes.schedule, currentMode), + _buildRadioTile( + context, 'Circulate', ScheduleModes.circulate, currentMode), + _buildRadioTile( + context, 'Inching', ScheduleModes.inching, currentMode), + ], + ), + ], + ); + } + + Widget _buildRadioTile( + BuildContext context, + String label, + ScheduleModes mode, + ScheduleModes currentMode, + ) { + return Flexible( + child: ListTile( + contentPadding: EdgeInsets.zero, + title: Text( + label, + style: context.textTheme.bodySmall!.copyWith( + fontSize: 13, + color: ColorsManager.blackColor, + ), + ), + leading: Radio( + value: mode, + groupValue: currentMode, + onChanged: (ScheduleModes? value) { + if (value != null) { + context.read().add( + UpdateScheduleModeEvent(scheduleMode: value), + ); + if (value == ScheduleModes.schedule) { + context.read().add( + const ScheduleGetEvent(category: 'switch_1'), + ); + } + } + }, + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart new file mode 100644 index 00000000..97ca03e1 --- /dev/null +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart @@ -0,0 +1,282 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/bloc/schedule_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/format_date_time.dart'; + +class ScheduleTableWidget extends StatelessWidget { + final String deviceUuid; + final String category; + + const ScheduleTableWidget({ + super.key, + required this.deviceUuid, + this.category = 'switch_1', + }); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => ScheduleBloc( + deviceId: deviceUuid, + )..add(ScheduleGetEvent(category: category)), + child: _ScheduleTableView(), + ); + } +} + +class _ScheduleTableView extends StatelessWidget { + const _ScheduleTableView(); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Table( + border: TableBorder.all( + color: ColorsManager.graysColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(20), topRight: Radius.circular(20)), + ), + children: [ + TableRow( + decoration: const BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + children: [ + _buildTableHeader('Active'), + _buildTableHeader('Days'), + _buildTableHeader('Time'), + _buildTableHeader('Function'), + _buildTableHeader('Action'), + ], + ), + ], + ), + BlocBuilder( + builder: (context, state) { + if (state is ScheduleLoading) { + return const SizedBox( + height: 200, + child: Center(child: CircularProgressIndicator())); + } + if (state is ScheduleLoaded && state.schedules.isEmpty) { + return _buildEmptyState(context); + } + if (state is ScheduleLoaded) { + return Container( + height: 200, + decoration: BoxDecoration( + border: Border.all(color: ColorsManager.graysColor), + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(20), + bottomRight: Radius.circular(20)), + ), + child: _buildTableBody(state.schedules, context)); + } + if (state is ScheduleError) { + return Center(child: Text(state.error)); + } + return const SizedBox(height: 200); + }, + ), + ], + ); + } + + Widget _buildEmptyState(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + border: Border.all(color: ColorsManager.graysColor), + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)), + ), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset(Assets.emptyRecords, width: 40, height: 40), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'No schedules added yet', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildTableBody(List schedules, BuildContext context) { + return SizedBox( + height: 200, + child: SingleChildScrollView( + child: Table( + border: TableBorder.all(color: ColorsManager.graysColor), + defaultVerticalAlignment: TableCellVerticalAlignment.middle, + children: [ + for (int i = 0; i < schedules.length; i++) + _buildScheduleRow(schedules[i], i, context), + ], + ), + ), + ); + } + + Widget _buildTableHeader(String label) { + return TableCell( + child: Padding( + padding: const EdgeInsets.all(12), + child: Text( + label, + style: const TextStyle( + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), + ), + ); + } + + TableRow _buildScheduleRow( + ScheduleModel schedule, int index, BuildContext context) { + return TableRow( + children: [ + Center( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + context.read().add( + ScheduleUpdateEntryEvent( + scheduleId: schedule.scheduleId, + functionOn: schedule.function.value, + enable: !schedule.enable, + ), + ); + }, + child: Center( + child: SizedBox( + width: 24, + height: 24, + child: schedule.enable + ? const Icon(Icons.radio_button_checked, + color: ColorsManager.blueColor) + : const Icon(Icons.radio_button_unchecked, + color: ColorsManager.grayColor), + ), + ), + ), + ), + Center( + child: Text(_getSelectedDays( + ScheduleModel.parseSelectedDays(schedule.days)))), + Center(child: Text(formatIsoStringToTime(schedule.time, context))), + Center(child: Text(schedule.function.value ? 'On' : 'Off')), + Center( + child: Wrap( + runAlignment: WrapAlignment.center, + children: [ + TextButton( + style: TextButton.styleFrom(padding: EdgeInsets.zero), + onPressed: () { + ScheduleDialogHelper.showAddScheduleDialog( + context, + schedule: ScheduleEntry.fromScheduleModel(schedule), + isEdit: true, + ).then((updatedSchedule) { + if (updatedSchedule != null) { + context.read().add( + ScheduleEditEvent( + scheduleId: schedule.scheduleId, + category: schedule.category, + time: updatedSchedule.time, + functionOn: updatedSchedule.function.value, + selectedDays: updatedSchedule.days), + ); + } + }); + }, + child: Text( + 'Edit', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: ColorsManager.blueColor), + ), + ), + TextButton( + style: TextButton.styleFrom(padding: EdgeInsets.zero), + onPressed: () async { + final confirmed = await showDialog( + context: context, + builder: (BuildContext dialogContext) { + return AlertDialog( + title: const Text('Confirm Delete'), + content: const Text( + 'Are you sure you want to delete this schedule?'), + actions: [ + TextButton( + onPressed: () => + Navigator.of(dialogContext).pop(false), + child: Text('Cancel'), + ), + TextButton( + onPressed: () => + Navigator.of(dialogContext).pop(true), + child: const Text( + 'Delete', + style: TextStyle(color: Colors.red), + ), + ), + ], + ); + }, + ); + + if (confirmed == true) { + context.read().add( + ScheduleDeleteEvent(schedule.scheduleId), + ); + } + }, + child: Text( + 'Delete', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: ColorsManager.blueColor), + ), + ) + ], + ), + ), + ], + ); + } + + String _getSelectedDays(List selectedDays) { + const days = ScheduleDialogHelper.allDays; + return selectedDays + .asMap() + .entries + .where((entry) => entry.value) + .map((entry) => days[entry.key]) + .join(', '); + } +} diff --git a/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_control_view.dart b/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_control_view.dart index 21a81df0..72435b74 100644 --- a/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_control_view.dart +++ b/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_control_view.dart @@ -1,14 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_control_button.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/factories/three_gang_glass_switch_bloc_factory.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; - import '../models/three_gang_glass_switch.dart'; -class ThreeGangGlassSwitchControlView extends StatelessWidget with HelperResponsiveLayout { +class ThreeGangGlassSwitchControlView extends StatelessWidget + with HelperResponsiveLayout { final String deviceId; const ThreeGangGlassSwitchControlView({required this.deviceId, super.key}); @@ -17,7 +19,8 @@ class ThreeGangGlassSwitchControlView extends StatelessWidget with HelperRespons Widget build(BuildContext context) { return BlocProvider( create: (context) => - ThreeGangGlassSwitchBlocFactory.create(deviceId: deviceId)..add(ThreeGangGlassSwitchFetchDeviceEvent(deviceId)), + ThreeGangGlassSwitchBlocFactory.create(deviceId: deviceId) + ..add(ThreeGangGlassSwitchFetchDeviceEvent(deviceId)), child: BlocBuilder( builder: (context, state) { if (state is ThreeGangGlassSwitchLoading) { @@ -34,7 +37,8 @@ class ThreeGangGlassSwitchControlView extends StatelessWidget with HelperRespons ); } - Widget _buildStatusControls(BuildContext context, ThreeGangGlassStatusModel status) { + Widget _buildStatusControls( + BuildContext context, ThreeGangGlassStatusModel status) { final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); @@ -98,6 +102,54 @@ class ThreeGangGlassSwitchControlView extends StatelessWidget with HelperRespons ); }, ), + ScheduleControlButton( + onTap: () { + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: BlocProvider.of(context), + child: BuildScheduleView( + category: 'switch_1', + deviceUuid: deviceId, + ), + )); + }, + mainText: 'Wall Light', + subtitle: 'Scheduling', + iconPath: Assets.scheduling, + ), + ScheduleControlButton( + onTap: () { + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: BlocProvider.of(context), + child: BuildScheduleView( + category: 'switch_2', + deviceUuid: deviceId, + ), + )); + }, + mainText: 'Ceiling Light', + subtitle: 'Scheduling', + iconPath: Assets.scheduling, + ), + ScheduleControlButton( + onTap: () { + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: BlocProvider.of(context), + child: BuildScheduleView( + category: 'switch_3', + deviceUuid: deviceId, + ), + )); + }, + mainText: 'SpotLight', + subtitle: 'Scheduling', + iconPath: Assets.scheduling, + ), ToggleWidget( value: false, code: '', @@ -107,15 +159,6 @@ class ThreeGangGlassSwitchControlView extends StatelessWidget with HelperRespons onChange: (value) {}, showToggle: false, ), - ToggleWidget( - value: false, - code: '', - deviceId: deviceId, - label: 'Scheduling', - icon: Assets.scheduling, - onChange: (value) {}, - showToggle: false, - ), ], ); } diff --git a/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart b/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart index 731b354c..66784bd5 100644 --- a/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart +++ b/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart @@ -1,9 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_control_button.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/factories/living_room_bloc_factory.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; class LivingRoomDeviceControlsView extends StatelessWidget @@ -90,6 +93,54 @@ class LivingRoomDeviceControlsView extends StatelessWidget ); }, ), + ScheduleControlButton( + onTap: () { + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: BlocProvider.of(context), + child: BuildScheduleView( + deviceUuid: deviceId, + category: 'switch_1', + ), + )); + }, + mainText: 'Wall Light', + subtitle: 'Scheduling', + iconPath: Assets.scheduling, + ), + ScheduleControlButton( + onTap: () { + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: BlocProvider.of(context), + child: BuildScheduleView( + deviceUuid: deviceId, + category: 'switch_2', + ), + )); + }, + mainText: 'Ceiling Light', + subtitle: 'Scheduling', + iconPath: Assets.scheduling, + ), + ScheduleControlButton( + onTap: () { + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: BlocProvider.of(context), + child: BuildScheduleView( + deviceUuid: deviceId, + category: 'switch_3', + ), + )); + }, + mainText: 'Spotlight', + subtitle: 'Scheduling', + iconPath: Assets.scheduling, + ), ], ); } diff --git a/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart b/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart index 575deeac..34b30dd3 100644 --- a/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart +++ b/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_control_button.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/factories/two_gang_glass_switch_bloc_factory.dart'; @@ -16,8 +18,9 @@ class TwoGangGlassSwitchControlView extends StatelessWidget @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => TwoGangGlassSwitchBlocFactory.create(deviceId: deviceId) - ..add(TwoGangGlassSwitchFetchDeviceEvent(deviceId)), + create: (context) => + TwoGangGlassSwitchBlocFactory.create(deviceId: deviceId) + ..add(TwoGangGlassSwitchFetchDeviceEvent(deviceId)), child: BlocBuilder( builder: (context, state) { if (state is TwoGangGlassSwitchLoading) { @@ -92,14 +95,37 @@ class TwoGangGlassSwitchControlView extends StatelessWidget onChange: (value) {}, showToggle: false, ), - ToggleWidget( - value: false, - code: '', - deviceId: deviceId, - label: 'Scheduling', - icon: Assets.scheduling, - onChange: (value) {}, - showToggle: false, + ScheduleControlButton( + onTap: () { + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: BlocProvider.of(context), + child: BuildScheduleView( + deviceUuid: deviceId, + category: 'switch_1', + ), + )); + }, + mainText: 'Wall Light', + subtitle: 'Scheduling', + iconPath: Assets.scheduling, + ), + ScheduleControlButton( + onTap: () { + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: BlocProvider.of(context), + child: BuildScheduleView( + deviceUuid: deviceId, + category: 'switch_2', + ), + )); + }, + mainText: 'Ceiling Light', + subtitle: 'Scheduling', + iconPath: Assets.scheduling, ), ], ); diff --git a/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart b/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart index e8346cb2..849412f2 100644 --- a/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart +++ b/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.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/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_control_button.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart'; @@ -8,9 +10,11 @@ import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/factories/two_gang_switch_bloc_factory.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class TwoGangBatchControlView extends StatelessWidget with HelperResponsiveLayout { +class TwoGangBatchControlView extends StatelessWidget + with HelperResponsiveLayout { const TwoGangBatchControlView({super.key, required this.deviceIds}); final List deviceIds; @@ -18,15 +22,17 @@ class TwoGangBatchControlView extends StatelessWidget with HelperResponsiveLayou @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => TwoGangSwitchBlocFactory.create(deviceId: deviceIds.first) - ..add(TwoGangSwitchFetchBatchEvent(deviceIds)), + create: (context) => + TwoGangSwitchBlocFactory.create(deviceId: deviceIds.first) + ..add(TwoGangSwitchFetchBatchEvent(deviceIds)), child: BlocBuilder( builder: (context, state) { if (state is TwoGangSwitchLoading) { return const Center(child: CircularProgressIndicator()); } else if (state is TwoGangSwitchStatusLoaded) { return _buildStatusControls(context, state.status); - } else if (state is TwoGangSwitchError || state is TwoGangSwitchControlError) { + } else if (state is TwoGangSwitchError || + state is TwoGangSwitchControlError) { return const Center(child: Text('Error fetching status')); } else { return const Center(child: CircularProgressIndicator()); @@ -82,6 +88,39 @@ class TwoGangBatchControlView extends StatelessWidget with HelperResponsiveLayou )); }, ), + ScheduleControlButton( + onTap: () { + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: BlocProvider.of(context), + child: BuildScheduleView( + category: 'switch_1', + deviceUuid: deviceIds.first, + ), + )); + }, + mainText: 'Wall Light', + subtitle: 'Scheduling', + iconPath: Assets.scheduling, + ), + + ScheduleControlButton( + onTap: () { + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: BlocProvider.of(context), + child: BuildScheduleView( + category: 'switch_2', + deviceUuid: deviceIds.first, + ), + )); + }, + mainText: 'Ceiling Light', + subtitle: 'Scheduling', + iconPath: Assets.scheduling, + ), // FirmwareUpdateWidget( // deviceId: deviceIds.first, // version: 12, diff --git a/lib/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart b/lib/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart index 882aac3e..ac3fe579 100644 --- a/lib/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart +++ b/lib/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart @@ -1,11 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_control_button.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/factories/two_gang_switch_bloc_factory.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; class TwoGangDeviceControlView extends StatelessWidget @@ -37,43 +40,101 @@ class TwoGangDeviceControlView extends StatelessWidget Widget _buildStatusControls(BuildContext context, TwoGangStatusModel status) { return Center( - child: Wrap( - alignment: WrapAlignment.center, - spacing: 12, - runSpacing: 12, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, children: [ - SizedBox( - width: 200, - child: ToggleWidget( - value: status.switch1, - code: 'switch_1', - deviceId: deviceId, - label: 'Wall Light', - onChange: (value) { - context.read().add(TwoGangSwitchControl( - deviceId: deviceId, - code: 'switch_1', - value: value, - )); - }, - ), - ), - SizedBox( - width: 200, - child: ToggleWidget( - value: status.switch2, - code: 'switch_2', - deviceId: deviceId, - label: 'Ceiling Light', - onChange: (value) { - context.read().add(TwoGangSwitchControl( - deviceId: deviceId, - code: 'switch_2', - value: value, - )); - }, - ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 200, + height: 150, + child: ToggleWidget( + value: status.switch1, + code: 'switch_1', + deviceId: deviceId, + label: 'Wall Light', + onChange: (value) { + context.read().add(TwoGangSwitchControl( + deviceId: deviceId, + code: 'switch_1', + value: value, + )); + }, + ), + ), + const SizedBox(width: 10), + SizedBox( + width: 200, + height: 150, + child: ToggleWidget( + value: status.switch2, + code: 'switch_2', + deviceId: deviceId, + label: 'Ceiling Light', + onChange: (value) { + context.read().add(TwoGangSwitchControl( + deviceId: deviceId, + code: 'switch_2', + value: value, + )); + }, + ), + ), + ], ), + const SizedBox(height: 20), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 200, + height: 150, + child: ScheduleControlButton( + onTap: () { + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: + BlocProvider.of(context), + child: BuildScheduleView( + deviceUuid: deviceId, + category: 'switch_1', + ), + )); + }, + mainText: 'Wall Light', + subtitle: 'Scheduling', + iconPath: Assets.scheduling, + ), + ), + const SizedBox(width: 10), + SizedBox( + width: 200, + height: 150, + child: ScheduleControlButton( + onTap: () { + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: + BlocProvider.of(context), + child: BuildScheduleView( + deviceUuid: deviceId, + category: 'switch_2', + ), + )); + }, + mainText: 'Ceiling Light', + subtitle: 'Scheduling', + iconPath: Assets.scheduling, + ), + ), + ], + ) ], ), ); diff --git a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart index 9278e396..ae7feac9 100644 --- a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart +++ b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart @@ -1,240 +1,210 @@ 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/water_heater/bloc/water_heater_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; class ScheduleDialogHelper { - static void showAddScheduleDialog(BuildContext context, {ScheduleModel? schedule, int? index, bool? isEdit}) { - final bloc = context.read(); + static const List allDays = [ + 'Sun', + 'Mon', + 'Tue', + 'Wed', + 'Thu', + 'Fri', + 'Sat' + ]; - if (schedule == null) { - bloc.add((const UpdateSelectedTimeEvent(null))); - bloc.add(InitializeAddScheduleEvent( - selectedTime: null, - selectedDays: List.filled(7, false), - functionOn: false, - isEditing: false, - )); - } else { - final time = _convertStringToTimeOfDay(schedule.time); - final selectedDays = _convertDaysStringToBooleans(schedule.days); + static Future showAddScheduleDialog( + BuildContext context, { + ScheduleEntry? schedule, + bool isEdit = false, + }) { + final initialTime = schedule != null + ? _convertStringToTimeOfDay(schedule.time) + : TimeOfDay.now(); + final initialDays = schedule != null + ? _convertDaysStringToBooleans(schedule.days) + : List.filled(7, false); + bool? functionOn = schedule?.function.value ?? true; + TimeOfDay selectedTime = initialTime; + List selectedDays = List.of(initialDays); - bloc.add(InitializeAddScheduleEvent( - selectedTime: time, - selectedDays: selectedDays, - functionOn: schedule.function.value, - isEditing: true, - index: index, - )); - } - - showDialog( + return showDialog( context: context, builder: (ctx) { - return BlocProvider.value( - value: bloc, - child: BlocBuilder( - builder: (context, state) { - if (state is WaterHeaterDeviceStatusLoaded) { - return AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + return StatefulBuilder( + builder: (ctx, setState) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const SizedBox(), - Text( - 'Scheduling', - style: context.textTheme.titleLarge!.copyWith( - color: ColorsManager.dialogBlueTitle, + const SizedBox(), + Text( + isEdit ? 'Edit Schedule' : 'Add Schedule', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + color: Colors.blue, fontWeight: FontWeight.bold, ), - ), - const SizedBox(), - ], ), - const SizedBox(height: 24), - SizedBox( - width: 150, - height: 40, - child: DefaultButton( - padding: 8, - backgroundColor: ColorsManager.boxColor, - borderRadius: 15, - onPressed: () async { - TimeOfDay? time = await showTimePicker( - context: context, - initialTime: state.selectedTime ?? TimeOfDay.now(), - builder: (context, child) { - return Theme( - data: Theme.of(context).copyWith( - colorScheme: const ColorScheme.light( - primary: ColorsManager.primaryColor, - ), - ), - child: child!, - ); - }, - ); - if (time != null) { - bloc.add(UpdateSelectedTimeEvent(time)); - } - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - state.selectedTime == null ? 'Time' : state.selectedTime!.format(context), - style: context.textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor, - ), - ), - const Icon( - Icons.access_time, - color: ColorsManager.grayColor, - size: 18, - ), - ], - ), - ), - ), - const SizedBox(height: 16), - _buildDayCheckboxes(context, state.selectedDays, isEdit: isEdit), - const SizedBox(height: 16), - _buildFunctionSwitch(context, state.functionOn, isEdit), + const SizedBox(), ], ), - actions: [ - SizedBox( - width: 200, - child: DefaultButton( - height: 40, - onPressed: () { - Navigator.pop(context); - }, - backgroundColor: ColorsManager.boxColor, - child: Text( - 'Cancel', - style: context.textTheme.bodyMedium, + const SizedBox(height: 24), + SizedBox( + width: 150, + height: 40, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.grey[200], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), ), ), - ), - SizedBox( - width: 200, - child: DefaultButton( - height: 40, - onPressed: () { - if (state.selectedTime != null) { - if (state.isEditing && index != null) { - bloc.add(EditWaterHeaterScheduleEvent( - scheduleId: schedule?.scheduleId ?? '', - category: 'switch_1', - time: state.selectedTime!, - selectedDays: state.selectedDays, - functionOn: state.functionOn, - )); - } else { - bloc.add(AddScheduleEvent( - category: 'switch_1', - time: state.selectedTime!, - selectedDays: state.selectedDays, - functionOn: state.functionOn, - )); - } - Navigator.pop(context); - } - }, - backgroundColor: ColorsManager.primaryColor, - child: const Text('Save'), + onPressed: () async { + TimeOfDay? time = await showTimePicker( + context: ctx, + initialTime: selectedTime, + ); + if (time != null) { + setState(() => selectedTime = time); + } + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + selectedTime.format(context), + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.grey), + ), + const Icon(Icons.access_time, + color: Colors.grey, size: 18), + ], ), ), - ], - ); - } - return const SizedBox(); - }, - ), + ), + const SizedBox(height: 16), + _buildDayCheckboxes(ctx, selectedDays, (i, v) { + setState(() => selectedDays[i] = v); + }), + const SizedBox(height: 16), + _buildFunctionSwitch(ctx, functionOn!, (v) { + setState(() => functionOn = v); + }), + ], + ), + actions: [ + SizedBox( + width: 100, + child: OutlinedButton( + onPressed: () { + Navigator.pop(ctx, null); + }, + child: const Text('Cancel'), + ), + ), + SizedBox( + width: 100, + child: ElevatedButton( + onPressed: () { + final entry = ScheduleEntry( + category: schedule?.category ?? 'switch_1', + time: _formatTimeOfDayToISO(selectedTime), + function: Status(code: 'switch_1', value: functionOn), + days: _convertSelectedDaysToStrings(selectedDays), + scheduleId: schedule?.scheduleId, + ); + Navigator.pop(ctx, entry); + }, + child: const Text('Save'), + ), + ), + ], + ); + }, ); }, ); } - static TimeOfDay _convertStringToTimeOfDay(String timeString) { - final regex = RegExp(r'^(\d{2}):(\d{2})$'); - final match = regex.firstMatch(timeString); - if (match != null) { - final hour = int.parse(match.group(1)!); - final minute = int.parse(match.group(2)!); - return TimeOfDay(hour: hour, minute: minute); - } else { - throw const FormatException('Invalid time format'); - } + static TimeOfDay _convertStringToTimeOfDay(String iso) { + final dt = DateTime.tryParse(iso); + if (dt != null) return TimeOfDay(hour: dt.hour, minute: dt.minute); + return const TimeOfDay(hour: 9, minute: 0); } static List _convertDaysStringToBooleans(List selectedDays) { final daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; - List daysBoolean = List.filled(7, false); - - for (int i = 0; i < daysOfWeek.length; i++) { - if (selectedDays.contains(daysOfWeek[i])) { - daysBoolean[i] = true; - } - } - - return daysBoolean; + return daysOfWeek + .map((d) => + selectedDays.map((e) => e.toLowerCase()).contains(d.toLowerCase())) + .toList(); } - static Widget _buildDayCheckboxes(BuildContext context, List selectedDays, {bool? isEdit}) { - final dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + static String _formatTimeOfDayToISO(TimeOfDay t) { + final now = DateTime.now(); + final dt = DateTime(now.year, now.month, now.day, t.hour, t.minute); + return dt.toIso8601String(); + } + static List _convertSelectedDaysToStrings(List selectedDays) { + const allDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + List result = []; + for (int i = 0; i < selectedDays.length; i++) { + if (selectedDays[i]) result.add(allDays[i]); + } + return result; + } + + static Widget _buildDayCheckboxes(BuildContext ctx, List selectedDays, + Function(int, bool) onChanged) { + final dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; return Row( - children: List.generate(7, (index) { - return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: List.generate( + 7, + (index) => Row( children: [ Checkbox( value: selectedDays[index], - onChanged: (bool? value) { - context.read().add(UpdateSelectedDayEvent(index, value!)); - }, + onChanged: (val) => onChanged(index, val!), ), Text(dayLabels[index]), ], - ); - }), + ), + ), ); } - static Widget _buildFunctionSwitch(BuildContext context, bool isOn, bool? isEdit) { + static Widget _buildFunctionSwitch( + BuildContext ctx, bool isOn, Function(bool) onChanged) { return Row( children: [ Text( 'Function:', - style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.grayColor), + style: + Theme.of(ctx).textTheme.bodySmall!.copyWith(color: Colors.grey), ), const SizedBox(width: 10), Radio( value: true, groupValue: isOn, - onChanged: (bool? value) { - context.read().add(const UpdateFunctionOnEvent(true)); - }, + onChanged: (val) => onChanged(true), ), const Text('On'), const SizedBox(width: 10), Radio( value: false, groupValue: isOn, - onChanged: (bool? value) { - context.read().add(const UpdateFunctionOnEvent(false)); - }, + onChanged: (val) => onChanged(false), ), const Text('Off'), ], diff --git a/lib/pages/device_managment/water_heater/models/schedule_entry.dart b/lib/pages/device_managment/water_heater/models/schedule_entry.dart index a2a109af..d6a530bb 100644 --- a/lib/pages/device_managment/water_heater/models/schedule_entry.dart +++ b/lib/pages/device_managment/water_heater/models/schedule_entry.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart'; class ScheduleEntry { final String category; @@ -58,7 +59,8 @@ class ScheduleEntry { String toJson() => json.encode(toMap()); - factory ScheduleEntry.fromJson(String source) => ScheduleEntry.fromMap(json.decode(source)); + factory ScheduleEntry.fromJson(String source) => + ScheduleEntry.fromMap(json.decode(source)); @override bool operator ==(Object other) { @@ -73,6 +75,23 @@ class ScheduleEntry { @override int get hashCode { - return category.hashCode ^ time.hashCode ^ function.hashCode ^ days.hashCode; + return category.hashCode ^ + time.hashCode ^ + function.hashCode ^ + days.hashCode; + } + + // Existing properties and methods + + // Add the fromScheduleModel method + + static ScheduleEntry fromScheduleModel(ScheduleModel scheduleModel) { + return ScheduleEntry( + days: scheduleModel.days, + time: scheduleModel.time, + function: scheduleModel.function, + category: scheduleModel.category, + scheduleId: scheduleModel.scheduleId, + ); } } diff --git a/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart b/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart index c535bda2..bf9ab2cd 100644 --- a/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart +++ b/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart @@ -16,7 +16,7 @@ class WaterHeaterStatusModel extends Equatable { final String cycleTiming; final List schedules; - const WaterHeaterStatusModel({ + const WaterHeaterStatusModel({ required this.uuid, required this.heaterSwitch, required this.countdownHours, diff --git a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart index f1e56136..16eff86a 100644 --- a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart +++ b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart @@ -2,12 +2,13 @@ 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/device_managment/schedule_device/schedule_widgets/schedule_control_button.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/factories/water_heater_bloc_factory.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/widgets/schedual_view.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedual_view.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'; @@ -35,7 +36,8 @@ class WaterHeaterDeviceControlView extends StatelessWidget state is WaterHeaterBatchFailedState) { return const Center(child: Text('Error fetching status')); } else { - return const SizedBox(height: 200, child: Center(child: SizedBox())); + return const SizedBox( + height: 200, child: Center(child: SizedBox())); } }, )); @@ -73,48 +75,22 @@ class WaterHeaterDeviceControlView extends StatelessWidget )); }, ), - GestureDetector( + ScheduleControlButton( onTap: () { - showDialog( + showDialog( context: context, builder: (ctx) => BlocProvider.value( value: BlocProvider.of(context), - child: BuildScheduleView(status: status), + child: BuildScheduleView( + deviceUuid: device.uuid ?? '', + category: 'switch_1', + ), )); }, - child: DeviceControlsContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: 60, - height: 60, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: ColorsManager.whiteColors, - ), - margin: const EdgeInsets.symmetric(horizontal: 4), - padding: const EdgeInsets.all(12), - child: ClipOval( - child: SvgPicture.asset( - Assets.scheduling, - fit: BoxFit.fill, - ), - ), - ), - const Spacer(), - Text( - 'Scheduling', - textAlign: TextAlign.center, - style: context.textTheme.titleMedium!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.blackColor, - ), - ), - ], - ), - ), - ) + mainText: '', + subtitle: 'Scheduling', + iconPath: Assets.scheduling, + ), ], ); } diff --git a/lib/pages/device_managment/water_heater/widgets/count_down_inching_view.dart b/lib/pages/device_managment/water_heater/widgets/count_down_inching_view.dart deleted file mode 100644 index 9c28d4d6..00000000 --- a/lib/pages/device_managment/water_heater/widgets/count_down_inching_view.dart +++ /dev/null @@ -1,223 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/extension/build_context_x.dart'; - -class CountdownInchingView extends StatelessWidget { - final WaterHeaterDeviceStatusLoaded state; - - const CountdownInchingView({ - super.key, - required this.state, - }); - - @override - Widget build(BuildContext context) { - final isCountDown = - state.scheduleMode?.name == ScheduleModes.countdown.name; - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - isCountDown ? 'Countdown:' : 'Inching:', - style: context.textTheme.bodySmall!.copyWith( - fontSize: 13, - color: ColorsManager.grayColor, - ), - ), - const SizedBox(height: 8), - Visibility( - visible: !isCountDown, - child: const Text( - 'Once enabled this feature, each time the device is turned on, it will automatically turn off after a preset time.'), - ), - const SizedBox(height: 8), - _hourMinutesWheel(context, state), - ], - ); - } - - Row _hourMinutesWheel( - BuildContext context, WaterHeaterDeviceStatusLoaded state) { - final isCountDown = - state.scheduleMode?.name == ScheduleModes.countdown.name; - late bool isActive; - if (isCountDown && - state.countdownRemaining != null && - state.isCountdownActive == true) { - isActive = true; - } else if (!isCountDown && - state.countdownRemaining != null && - state.isInchingActive == true) { - isActive = true; - } else { - isActive = false; - } - - return Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - _buildPickerColumn( - context, - 'h', - isCountDown - ? (state.countdownHours ?? 0) - : (state.inchingHours ?? 0), - 24, (value) { - context.read().add(UpdateScheduleEvent( - scheduleMode: state.scheduleMode ?? ScheduleModes.countdown, - hours: value, - minutes: isCountDown - ? (state.countdownMinutes ?? 0) - : (state.inchingMinutes ?? 0), - )); - }, isActive: isActive), - const SizedBox(width: 10), - _buildPickerColumn( - context, - 'm', - isCountDown - ? (state.countdownMinutes ?? 0) - : (state.inchingMinutes ?? 0), - 60, (value) { - context.read().add(UpdateScheduleEvent( - scheduleMode: state.scheduleMode ?? ScheduleModes.countdown, - hours: isCountDown - ? (state.countdownHours ?? 0) - : (state.inchingHours ?? 0), - minutes: value, - )); - }, isActive: isActive), - ], - ); - } - - Row _hourMinutesSecondWheel( - BuildContext context, WaterHeaterDeviceStatusLoaded state) { - final isCountDown = - state.scheduleMode?.name == ScheduleModes.countdown.name; - late bool isActive; - if (isCountDown && - state.countdownRemaining != null && - state.isCountdownActive == true) { - isActive = true; - } else if (!isCountDown && - state.countdownRemaining != null && - state.isInchingActive == true) { - isActive = true; - } else { - isActive = false; - } - - return Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - _buildPickerColumn( - context, - 'h', - isCountDown - ? (state.countdownHours ?? 0) - : (state.inchingHours ?? 0), - 24, (value) { - context.read().add(UpdateScheduleEvent( - scheduleMode: state.scheduleMode ?? ScheduleModes.countdown, - hours: value, - minutes: isCountDown - ? (state.countdownMinutes ?? 0) - : (state.inchingMinutes ?? 0), - )); - }, isActive: isActive), - const SizedBox(width: 10), - _buildPickerColumn( - context, - 'm', - isCountDown - ? (state.countdownMinutes ?? 0) - : (state.inchingMinutes ?? 0), - 60, (value) { - context.read().add(UpdateScheduleEvent( - scheduleMode: state.scheduleMode ?? ScheduleModes.countdown, - hours: isCountDown - ? (state.countdownHours ?? 0) - : (state.inchingHours ?? 0), - minutes: value, - )); - }, isActive: isActive), - const SizedBox(width: 10), - _buildPickerColumn( - context, - 'S', - isCountDown - ? (state.countdownMinutes ?? 0) - : (state.inchingMinutes ?? 0), - 60, (value) { - context.read().add(UpdateScheduleEvent( - scheduleMode: state.scheduleMode ?? ScheduleModes.countdown, - hours: isCountDown - ? (state.countdownHours ?? 0) - : (state.inchingHours ?? 0), - minutes: value, - )); - }, isActive: isActive), - ], - ); - } - - Widget _buildPickerColumn( - BuildContext context, - String label, - int initialValue, - int itemCount, - ValueChanged onSelected, { - required bool isActive, - }) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - height: 40, - width: 80, - padding: const EdgeInsets.symmetric(horizontal: 16), - decoration: BoxDecoration( - color: ColorsManager.boxColor, - borderRadius: BorderRadius.circular(8), - ), - child: ListWheelScrollView.useDelegate( - key: ValueKey('$label-$initialValue'), - controller: FixedExtentScrollController( - initialItem: initialValue, - ), - itemExtent: 40.0, - physics: const FixedExtentScrollPhysics(), - onSelectedItemChanged: onSelected, - childDelegate: ListWheelChildBuilderDelegate( - builder: (context, index) { - return Center( - child: Text( - index.toString().padLeft(2, '0'), - style: TextStyle( - fontSize: 24, - color: isActive ? ColorsManager.grayColor : Colors.black, - ), - ), - ); - }, - childCount: itemCount, - ), - ), - ), - const SizedBox(width: 8), - Text( - label, - style: const TextStyle( - color: ColorsManager.grayColor, - fontSize: 18, - ), - ), - ], - ); - } -} diff --git a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart deleted file mode 100644 index 9d4a2497..00000000 --- a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart +++ /dev/null @@ -1,117 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/widgets/count_down_button.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/widgets/count_down_inching_view.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/widgets/inching_mode_buttons.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/widgets/schedule_header.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/widgets/schedule_managment_ui.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/widgets/schedule_mode_buttons.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/widgets/schedule_mode_selector.dart'; - -class BuildScheduleView extends StatefulWidget { - const BuildScheduleView({super.key, required this.status}); - - final WaterHeaterStatusModel status; - - @override - State createState() => _BuildScheduleViewState(); -} - -class _BuildScheduleViewState extends State { - @override - Widget build(BuildContext context) { - final bloc = BlocProvider.of(context); - - return BlocProvider.value( - value: bloc, - child: Dialog( - backgroundColor: Colors.white, - insetPadding: const EdgeInsets.all(20), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - child: SizedBox( - width: 700, - child: SingleChildScrollView( - child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: 40.0, vertical: 20), - child: BlocBuilder( - builder: (context, state) { - if (state is WaterHeaterDeviceStatusLoaded) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const ScheduleHeader(), - const SizedBox(height: 20), - ScheduleModeSelector(state: state), - const SizedBox(height: 20), - if (state.scheduleMode == ScheduleModes.schedule) - ScheduleManagementUI( - state: state, - onAddSchedule: () { - ScheduleDialogHelper.showAddScheduleDialog( - context, - schedule: null, - index: null, - isEdit: false); - }, - ), - if (state.scheduleMode == ScheduleModes.countdown || - state.scheduleMode == ScheduleModes.inching) - CountdownInchingView(state: state), - const SizedBox(height: 20), - if (state.scheduleMode == ScheduleModes.countdown) - CountdownModeButtons( - isActive: state.isCountdownActive ?? false, - deviceId: widget.status.uuid, - hours: state.countdownHours ?? 0, - minutes: state.countdownMinutes ?? 0, - ), - if (state.scheduleMode == ScheduleModes.inching) - InchingModeButtons( - isActive: state.isInchingActive ?? false, - deviceId: widget.status.uuid, - hours: state.inchingHours ?? 0, - minutes: state.inchingMinutes ?? 0, - ), - if (state.scheduleMode != ScheduleModes.countdown && - state.scheduleMode != ScheduleModes.inching) - ScheduleModeButtons( - onSave: () { - Navigator.pop(context); - }, - ), - ], - ); - } - if (state is WaterHeaterLoadingState) { - return const SizedBox( - height: 200, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ScheduleHeader(), - SizedBox( - height: 20, - ), - Center(child: CircularProgressIndicator()), - ], - )); - } - return const SizedBox( - height: 200, - child: ScheduleHeader(), - ); - }, - ), - ), - ), - ), - ), - ); - } -} diff --git a/lib/pages/device_managment/water_heater/widgets/schedule_mode_selector.dart b/lib/pages/device_managment/water_heater/widgets/schedule_mode_selector.dart deleted file mode 100644 index bb9ddc8f..00000000 --- a/lib/pages/device_managment/water_heater/widgets/schedule_mode_selector.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/extension/build_context_x.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; - -class ScheduleModeSelector extends StatelessWidget { - final WaterHeaterDeviceStatusLoaded state; - - const ScheduleModeSelector({super.key, required this.state}); - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Type:', - style: context.textTheme.bodySmall!.copyWith( - fontSize: 13, - color: ColorsManager.grayColor, - ), - ), - const SizedBox(height: 4), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - _buildRadioTile( - context, 'Countdown', ScheduleModes.countdown, state), - _buildRadioTile(context, 'Schedule', ScheduleModes.schedule, state), - _buildRadioTile( - context, 'Circulate', ScheduleModes.circulate, state), - _buildRadioTile(context, 'Inching', ScheduleModes.inching, state), - ], - ), - ], - ); - } - - Widget _buildRadioTile(BuildContext context, String label, ScheduleModes mode, - WaterHeaterDeviceStatusLoaded state) { - return Flexible( - child: ListTile( - contentPadding: EdgeInsets.zero, - title: Text( - label, - style: context.textTheme.bodySmall!.copyWith( - fontSize: 13, - color: ColorsManager.blackColor, - ), - ), - leading: Radio( - value: mode, - groupValue: state.scheduleMode, - onChanged: (ScheduleModes? value) { - if (value != null) { - if (value == ScheduleModes.countdown) { - context.read().add(UpdateScheduleEvent( - scheduleMode: value, - hours: state.countdownHours ?? 0, - minutes: state.countdownMinutes ?? 0, - )); - } else if (value == ScheduleModes.inching) { - context.read().add(UpdateScheduleEvent( - scheduleMode: value, - hours: state.inchingHours ?? 0, - minutes: state.inchingMinutes ?? 0, - )); - } - - if (value == ScheduleModes.schedule) { - context.read().add( - GetSchedulesEvent( - category: 'switch_1', - uuid: state.status.uuid, - ), - ); - } - } - }, - ), - ), - ); - } -} diff --git a/lib/pages/device_managment/water_heater/widgets/schedule_table.dart b/lib/pages/device_managment/water_heater/widgets/schedule_table.dart deleted file mode 100644 index 18cbbe5a..00000000 --- a/lib/pages/device_managment/water_heater/widgets/schedule_table.dart +++ /dev/null @@ -1,222 +0,0 @@ -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/water_heater/bloc/water_heater_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.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/format_date_time.dart'; - -import '../helper/add_schedule_dialog_helper.dart'; - -class ScheduleTableWidget extends StatelessWidget { - final WaterHeaterDeviceStatusLoaded state; - - const ScheduleTableWidget({ - super.key, - required this.state, - }); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Table( - border: TableBorder.all( - color: ColorsManager.graysColor, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(20), topRight: Radius.circular(20)), - ), - children: [ - TableRow( - decoration: const BoxDecoration( - color: ColorsManager.boxColor, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), - ), - children: [ - _buildTableHeader('Active'), - _buildTableHeader('Days'), - _buildTableHeader('Time'), - _buildTableHeader('Function'), - _buildTableHeader('Action'), - ], - ), - ], - ), - BlocBuilder( - builder: (context, state) { - if (state is ScheduleLoadingState) { - return const SizedBox( - height: 200, - child: Center(child: CircularProgressIndicator())); - } - if (state is WaterHeaterDeviceStatusLoaded && - state.schedules.isEmpty) { - return _buildEmptyState(context); - } else if (state is WaterHeaterDeviceStatusLoaded) { - return Container( - height: 200, - decoration: BoxDecoration( - border: Border.all(color: ColorsManager.graysColor), - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(20), - bottomRight: Radius.circular(20)), - ), - child: _buildTableBody(state, context)); - } - return const SizedBox( - height: 200, - ); - }, - ), - ], - ); - } - - Widget _buildEmptyState(BuildContext context) { - return Container( - height: 200, - decoration: BoxDecoration( - border: Border.all(color: ColorsManager.graysColor), - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)), - ), - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - SvgPicture.asset(Assets.emptyRecords, width: 40, height: 40), - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - 'No schedules added yet', - style: context.textTheme.bodySmall!.copyWith( - fontSize: 13, - color: ColorsManager.grayColor, - ), - ), - ), - ], - ), - ), - ); - } - - Widget _buildTableBody( - WaterHeaterDeviceStatusLoaded state, BuildContext context) { - return SizedBox( - height: 200, - child: SingleChildScrollView( - child: Table( - border: TableBorder.all(color: ColorsManager.graysColor), - defaultVerticalAlignment: TableCellVerticalAlignment.middle, - children: [ - for (int i = 0; i < state.schedules.length; i++) - _buildScheduleRow(state.schedules[i], i, context, state), - ], - ), - ), - ); - } - - Widget _buildTableHeader(String label) { - return TableCell( - child: Padding( - padding: const EdgeInsets.all(12), - child: Text( - label, - style: const TextStyle( - fontSize: 13, - color: ColorsManager.grayColor, - ), - ), - ), - ); - } - - TableRow _buildScheduleRow(ScheduleModel schedule, int index, - BuildContext context, WaterHeaterDeviceStatusLoaded state) { - return TableRow( - children: [ - Center( - child: GestureDetector( - onTap: () { - context.read().add(UpdateScheduleEntryEvent( - index: index, - enable: !schedule.enable, - scheduleId: schedule.scheduleId, - deviceId: state.status.uuid, - functionOn: schedule.function.value, - )); - }, - child: SizedBox( - width: 24, - height: 24, - child: schedule.enable - ? const Icon(Icons.radio_button_checked, - color: ColorsManager.blueColor) - : const Icon( - Icons.radio_button_unchecked, - color: ColorsManager.grayColor, - ), - ), - ), - ), - Center( - child: Text(_getSelectedDays( - ScheduleModel.parseSelectedDays(schedule.days)))), - Center(child: Text(formatIsoStringToTime(schedule.time, context))), - Center(child: Text(schedule.function.value ? 'On' : 'Off')), - Center( - child: Wrap( - runAlignment: WrapAlignment.center, - children: [ - TextButton( - style: TextButton.styleFrom(padding: EdgeInsets.zero), - onPressed: () { - ScheduleDialogHelper.showAddScheduleDialog(context, - schedule: schedule, index: index, isEdit: true); - }, - child: Text( - 'Edit', - style: context.textTheme.bodySmall! - .copyWith(color: ColorsManager.blueColor), - ), - ), - TextButton( - style: TextButton.styleFrom(padding: EdgeInsets.zero), - onPressed: () { - context.read().add(DeleteScheduleEvent( - index: index, - scheduleId: schedule.scheduleId, - )); - }, - child: Text( - 'Delete', - style: context.textTheme.bodySmall! - .copyWith(color: ColorsManager.blueColor), - ), - ), - ], - ), - ), - ], - ); - } - - String _getSelectedDays(List selectedDays) { - final days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; - List selectedDaysStr = []; - for (int i = 0; i < selectedDays.length; i++) { - if (selectedDays[i]) { - selectedDaysStr.add(days[i]); - } - } - return selectedDaysStr.join(', '); - } -} diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 6f60e34f..6fb27daf 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:core'; import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; @@ -386,4 +387,34 @@ class DevicesManagementApi { ); return response; } + + Future postSchedule({ + required String category, + required String deviceId, + required String time, + required String code, + required bool value, + required List days, + }) async { + final response = await HTTPService().post( + path: ApiEndpoints.saveSchedule.replaceAll('{deviceUuid}', deviceId), + showServerMessage: false, + body: jsonEncode( + { + 'category': category, + 'time': time, + 'function': { + 'code': code, + 'value': value, + }, + 'days': days + }, + ), + expectedResponseModel: (json) { + return json['success'] ?? false; + }, + ); + return response; + } + } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index d58d0f28..7e5942e3 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -136,4 +136,5 @@ abstract class ApiEndpoints { static const String assignDeviceToRoom = '/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/subspaces/{subSpaceUuid}/devices/{deviceUuid}'; + static const String saveSchedule = '/schedule/{deviceUuid}'; }