diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart index c3096216..fcf52732 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart @@ -3,6 +3,7 @@ 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/water_heater_status_model.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; @@ -17,6 +18,9 @@ class WaterHeaterBloc extends Bloc { on(_updateScheduleEvent); on(_stopScheduleEvent); on(_onDecrementCountdown); + on(_onAddSchedule); + on(_onDeleteSchedule); + on(_onUpdateSchedule); } late WaterHeaterStatusModel deviceStatus; @@ -179,7 +183,7 @@ class WaterHeaterBloc extends Bloc { } } - _onDecrementCountdown( + _onDecrementCountdown( DecrementCountdownEvent event, Emitter emit, ) { @@ -294,6 +298,54 @@ class WaterHeaterBloc extends Bloc { } } + FutureOr _onAddSchedule( + AddScheduleEvent event, + Emitter emit, + ) { + if (state is WaterHeaterDeviceStatusLoaded) { + final currentState = state as WaterHeaterDeviceStatusLoaded; + final newSchedule = ScheduleEntry( + selectedDays: event.selectedDays, + time: event.time, + functionOn: event.functionOn, + ); + final updatedSchedules = List.from(currentState.schedules) + ..add(newSchedule); + + emit(currentState.copyWith(schedules: updatedSchedules)); + } + } + + FutureOr _onDeleteSchedule( + DeleteScheduleEvent event, + Emitter emit, + ) { + if (state is WaterHeaterDeviceStatusLoaded) { + final currentState = state as WaterHeaterDeviceStatusLoaded; + final updatedSchedules = List.from(currentState.schedules) + ..removeAt(event.index); + + emit(currentState.copyWith(schedules: updatedSchedules)); + } + } + + FutureOr _onUpdateSchedule( + UpdateScheduleEntryEvent event, + Emitter emit, + ) { + if (state is WaterHeaterDeviceStatusLoaded) { + final currentState = state as WaterHeaterDeviceStatusLoaded; + final updatedSchedules = List.from(currentState.schedules); + updatedSchedules[event.index] = ScheduleEntry( + selectedDays: event.selectedDays, + time: event.time, + functionOn: event.functionOn, + ); + + emit(currentState.copyWith(schedules: updatedSchedules)); + } + } + @override Future close() { _countdownTimer?.cancel(); diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart index 1b9c3f11..360f4a4f 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart @@ -1,4 +1,3 @@ - part of 'water_heater_bloc.dart'; sealed class WaterHeaterEvent extends Equatable { @@ -63,3 +62,44 @@ final class WaterHeaterFetchBatchStatusEvent extends WaterHeaterEvent { } final class DecrementCountdownEvent extends WaterHeaterEvent {} + +final class AddScheduleEvent extends WaterHeaterEvent { + final List selectedDays; + final TimeOfDay time; + final bool functionOn; + + const AddScheduleEvent({ + required this.selectedDays, + required this.time, + required this.functionOn, + }); + + @override + List get props => [selectedDays, time, functionOn]; +} + +final class DeleteScheduleEvent extends WaterHeaterEvent { + final int index; + + const DeleteScheduleEvent(this.index); + + @override + List get props => [index]; +} + +final class UpdateScheduleEntryEvent extends WaterHeaterEvent { + final int index; + final List selectedDays; + final TimeOfDay time; + final bool functionOn; + + const UpdateScheduleEntryEvent({ + required this.index, + required this.selectedDays, + required this.time, + required this.functionOn, + }); + + @override + List get props => [index, selectedDays, time, functionOn]; +} diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart index 092cd90e..95c68f24 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart @@ -20,6 +20,7 @@ final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { final int? minutes; final bool? isActive; final Duration? countdownRemaining; + final List schedules; const WaterHeaterDeviceStatusLoaded( this.status, { @@ -28,13 +29,20 @@ final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { this.minutes, this.isActive, this.countdownRemaining, + this.schedules = const [], }); @override - List get props => - [status, scheduleMode, hours, minutes, isActive, countdownRemaining]; + List get props => [ + status, + scheduleMode, + hours, + minutes, + isActive, + countdownRemaining, + schedules, + ]; - /// Creates a new instance with updated fields. WaterHeaterDeviceStatusLoaded copyWith({ WaterHeaterStatusModel? status, ScheduleModes? scheduleMode, @@ -42,6 +50,7 @@ final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { int? minutes, bool? isActive, Duration? countdownRemaining, + List? schedules, }) { return WaterHeaterDeviceStatusLoaded( status ?? this.status, @@ -50,10 +59,27 @@ final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { minutes: minutes ?? this.minutes, isActive: isActive ?? this.isActive, countdownRemaining: countdownRemaining ?? this.countdownRemaining, + schedules: schedules ?? this.schedules, ); } } +class ScheduleEntry { + final List selectedDays; + final TimeOfDay time; + final bool functionOn; + + ScheduleEntry({ + required this.selectedDays, + required this.time, + required this.functionOn, + }); + + @override + String toString() => + 'ScheduleEntry(selectedDays: $selectedDays, time: $time, functionOn: $functionOn)'; +} + final class WaterHeaterFailedState extends WaterHeaterState { final String error; diff --git a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart index 5010eaf4..74cdb94b 100644 --- a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart +++ b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart @@ -1,4 +1,3 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; @@ -17,339 +16,46 @@ class BuildScheduleView extends StatefulWidget { } class _BuildScheduleViewState extends State { - // late FixedExtentScrollController hoursController; - // late FixedExtentScrollController minutesController; - - // @override - // void initState() { - // super.initState(); - // _initializeControllers(); - // } - - // @override - // void didUpdateWidget(covariant BuildScheduleView oldWidget) { - // super.didUpdateWidget(oldWidget); - // final state = context.read().state; - // if (state is WaterHeaterDeviceStatusLoaded) { - // _initializeControllers(); - // } - // } - - // void _initializeControllers() { - // final state = context.read().state; - // if (state is WaterHeaterDeviceStatusLoaded) { - // hoursController = - // FixedExtentScrollController(initialItem: state.hours ?? 0); - // minutesController = - // FixedExtentScrollController(initialItem: state.minutes ?? 0); - // } else { - // hoursController = FixedExtentScrollController(initialItem: 0); - // minutesController = FixedExtentScrollController(initialItem: 0); - // } - // } - - // void _updateControllers(int hours, int minutes) { - // if (hoursController.hasClients) { - // hoursController.jumpToItem(hours); - // } - // if (minutesController.hasClients) { - // minutesController.jumpToItem(minutes); - // } - // } - - // @override - // void dispose() { - // hoursController.dispose(); - // minutesController.dispose(); - // super.dispose(); - // } - @override Widget build(BuildContext context) { - return 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) { - //_updateControllers(state.hours ?? 0, state.minutes ?? 0); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const SizedBox(), - Text( - 'Scheduling', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 22, - color: ColorsManager.dialogBlueTitle, - ), - ), - Container( - width: 25, - decoration: BoxDecoration( - color: Colors.transparent, - shape: BoxShape.circle, - border: Border.all( - color: Colors.grey, - width: 1.0, - ), - ), - child: IconButton( - padding: EdgeInsets.all(1), - icon: const Icon( - Icons.close, - color: Colors.grey, - size: 18, - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ), - ], - ), - const SizedBox(height: 20), - Text( - 'Type:', - style: context.textTheme.bodySmall!.copyWith( - fontSize: 13, - color: ColorsManager.grayColor, - ), - ), - const SizedBox(height: 4), - SizedBox( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Flexible( - child: ListTile( - contentPadding: EdgeInsets.zero, - title: Text( - 'Countdown', - style: context.textTheme.bodySmall!.copyWith( - fontSize: 13, - color: ColorsManager.blackColor, - ), - ), - leading: Radio( - value: ScheduleModes.countdown, - groupValue: state.scheduleMode, - onChanged: (ScheduleModes? value) { - if (value != null) { - context - .read() - .add(UpdateScheduleEvent( - scheduleMode: value, - hours: state.hours ?? 0, - minutes: state.minutes ?? 0, - )); - } - }, - ), - ), - ), - Flexible( - child: ListTile( - contentPadding: EdgeInsets.zero, - title: Text( - 'Schedule', - style: context.textTheme.bodySmall!.copyWith( - fontSize: 13, - color: ColorsManager.blackColor, - ), - ), - leading: Radio( - value: ScheduleModes.schedule, - groupValue: state.scheduleMode, - onChanged: (ScheduleModes? value) { - if (value != null) { - context - .read() - .add(UpdateScheduleEvent( - scheduleMode: value, - hours: state.hours ?? 0, - minutes: state.minutes ?? 0, - )); - } - }, - ), - ), - ), - Flexible( - child: ListTile( - title: Text( - 'Circulate', - style: context.textTheme.bodySmall!.copyWith( - fontSize: 13, - color: ColorsManager.blackColor, - ), - ), - leading: Radio( - value: ScheduleModes.circulate, - groupValue: state.scheduleMode, - onChanged: (ScheduleModes? value) { - if (value != null) { - context - .read() - .add(UpdateScheduleEvent( - scheduleMode: value, - hours: state.hours ?? 0, - minutes: state.minutes ?? 0, - )); - } - }, - ), - ), - ), - Flexible( - child: ListTile( - title: Text( - 'Inching', - style: context.textTheme.bodySmall!.copyWith( - fontSize: 13, - color: ColorsManager.blackColor, - ), - ), - leading: Radio( - value: ScheduleModes.inching, - groupValue: state.scheduleMode, - onChanged: (ScheduleModes? value) { - if (value != null) { - context - .read() - .add(UpdateScheduleEvent( - scheduleMode: value, - hours: state.hours ?? 0, - minutes: state.minutes ?? 0, - )); - } - }, - ), - ), - ), - ], - ), - ), - const SizedBox(height: 20), - if (state.scheduleMode == ScheduleModes.countdown || - state.scheduleMode == ScheduleModes.inching) ...[ - Text( - 'Countdown:', - style: context.textTheme.bodySmall!.copyWith( - fontSize: 13, - color: ColorsManager.grayColor, - ), - ), - const SizedBox(height: 4), - _hourMinutesWheel(state, 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: [ + _scheduleHeader(context), + const SizedBox(height: 20), + _buildScheduleModeSelector(context, state), + const SizedBox(height: 20), + if (state.scheduleMode == ScheduleModes.schedule) + _buildScheduleManagementUI(state), + if (state.scheduleMode == ScheduleModes.countdown || + state.scheduleMode == ScheduleModes.inching) + ..._buildCountDownAngInchingView(context, state), + const SizedBox(height: 20), + _buildSaveStopCancelButtons(context, state), ], - const SizedBox(height: 20), - Center( - child: SizedBox( - width: 400, - height: 50, - child: Center( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.max, - children: [ - Expanded( - child: DefaultButton( - height: 40, - onPressed: () { - Navigator.pop(context); - }, - backgroundColor: ColorsManager.boxColor, - child: Text( - 'Cancel', - style: context.textTheme.bodyMedium, - ), - ), - ), - const SizedBox(width: 20), - Expanded( - child: (state.countdownRemaining != null && - state.isActive == true) - ? DefaultButton( - height: 40, - onPressed: () { - late String code; - if (state.scheduleMode == - ScheduleModes.countdown) { - code = 'countdown_1'; - } else if (state.scheduleMode == - ScheduleModes.inching) { - code = 'switch_inching'; - } - context.read().add( - StopScheduleEvent( - widget.status.uuid)); - context.read().add( - ToggleWaterHeaterEvent( - deviceId: - widget.status.uuid, - code: code, - value: 0, - ), - ); - }, - backgroundColor: Colors.red, - child: const Text('Stop'), - ) - : DefaultButton( - height: 40, - onPressed: () { - late String code; - if (state.scheduleMode == - ScheduleModes.countdown) { - code = 'countdown_1'; - } else if (state.scheduleMode == - ScheduleModes.inching) { - code = 'switch_inching'; - } - context.read().add( - ToggleWaterHeaterEvent( - deviceId: - widget.status.uuid, - code: code, - value: Duration( - hours: - state.hours ?? - 0, - minutes: - state.minutes ?? - 0) - .inSeconds, - ), - ); - }, - backgroundColor: - ColorsManager.primaryColor, - child: const Text('Save'), - ), - ), - ], - ), - ), - ), - ), - ], - ); - } - return const SizedBox(); - }, + ); + } + return const SizedBox(); + }, + ), ), ), ), @@ -357,117 +63,579 @@ class _BuildScheduleViewState extends State { ); } - Row _hourMinutesWheel( - WaterHeaterDeviceStatusLoaded state, BuildContext context) { + Row _scheduleHeader(BuildContext context) { return Row( - mainAxisAlignment: MainAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - // Hours Picker - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - height: 50, - width: 80, - padding: const EdgeInsets.symmetric(horizontal: 16), - decoration: BoxDecoration( - color: ColorsManager.boxColor, - borderRadius: BorderRadius.circular(8), - ), - child: ListWheelScrollView.useDelegate( - key: ValueKey('hours_${state.hours}'), - controller: FixedExtentScrollController( - initialItem: state.hours ?? 0, - ), - itemExtent: 40.0, - physics: const FixedExtentScrollPhysics(), - onSelectedItemChanged: (int value) { - context.read().add( - UpdateScheduleEvent( - scheduleMode: - state.scheduleMode ?? ScheduleModes.countdown, - hours: value, - minutes: state.minutes ?? 0, - ), - ); - }, - childDelegate: ListWheelChildBuilderDelegate( - builder: (context, index) { - return Center( - child: Text( - index.toString().padLeft(2, '0'), - style: const TextStyle(fontSize: 24), - ), - ); - }, - childCount: 24, - ), - ), - ), - const SizedBox(height: 8), - const Text( - 'h', - style: TextStyle( - color: ColorsManager.grayColor, - fontSize: 18, - ), - ), - ], + const SizedBox(), + Text( + 'Scheduling', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 22, + color: ColorsManager.dialogBlueTitle, + ), ), - const SizedBox(width: 10), - // Minutes Picker + Container( + width: 25, + decoration: BoxDecoration( + color: Colors.transparent, + shape: BoxShape.circle, + border: Border.all( + color: Colors.grey, + width: 1.0, + ), + ), + child: IconButton( + padding: EdgeInsets.all(1), + icon: const Icon( + Icons.close, + color: Colors.grey, + size: 18, + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + ], + ); + } + + Widget _buildScheduleModeSelector( + BuildContext context, WaterHeaterDeviceStatusLoaded state) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Type:', + style: context.textTheme.bodySmall!.copyWith( + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), + const SizedBox(height: 4), Row( - mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Container( - height: 50, - width: 80, - padding: const EdgeInsets.symmetric(horizontal: 16), - decoration: BoxDecoration( - color: ColorsManager.boxColor, - borderRadius: BorderRadius.circular(8), - ), - child: ListWheelScrollView.useDelegate( - key: ValueKey('minutes_${state.minutes}'), - controller: FixedExtentScrollController( - initialItem: state.minutes ?? 0, - ), - itemExtent: 40.0, - physics: const FixedExtentScrollPhysics(), - onSelectedItemChanged: (int value) { - context.read().add( - UpdateScheduleEvent( - scheduleMode: - state.scheduleMode ?? ScheduleModes.countdown, - hours: state.hours ?? 0, - minutes: value, - ), - ); - }, - childDelegate: ListWheelChildBuilderDelegate( - builder: (context, index) { - return Center( - child: Text( - index.toString().padLeft(2, '0'), - style: const TextStyle(fontSize: 24), - ), - ); - }, - childCount: 60, - ), - ), - ), - const SizedBox(height: 8), - const Text( - 'm', - style: TextStyle( - color: ColorsManager.grayColor, - fontSize: 18, - ), - ), + _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) { + context.read().add(UpdateScheduleEvent( + scheduleMode: value, + hours: state.hours ?? 0, + minutes: state.minutes ?? 0, + )); + } + }, + ), + ), + ); + } + + Widget _buildScheduleManagementUI(WaterHeaterDeviceStatusLoaded state) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ElevatedButton( + onPressed: () => _showAddScheduleDialog(context), + child: Text('+ Add new schedule'), + ), + const SizedBox(height: 20), + _buildScheduleTable(state), + ], + ); + } + + Widget _buildScheduleTable(WaterHeaterDeviceStatusLoaded state) { + return Table( + border: TableBorder.all(color: Colors.grey), + children: [ + TableRow( + children: [ + _buildTableHeader('Active'), + _buildTableHeader('Days'), + _buildTableHeader('Time'), + _buildTableHeader('Function'), + _buildTableHeader('Action'), + ], + ), + if (state.schedules.isEmpty) + TableRow( + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'No schedules added yet', + style: context.textTheme.bodySmall!.copyWith( + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), + ), + ), + const SizedBox(), + const SizedBox(), + const SizedBox(), + const SizedBox(), + ], + ), + for (int i = 0; i < state.schedules.length; i++) + _buildScheduleRow(state.schedules[i], i, context), + ], + ); + } + + TableRow _buildScheduleRow( + ScheduleEntry schedule, int index, BuildContext context) { + return TableRow( + children: [ + Center( + child: schedule.functionOn + ? Icon(Icons.radio_button_checked) + : Icon(Icons.radio_button_unchecked)), + Center(child: Text(_getSelectedDays(schedule.selectedDays))), + Center(child: Text(schedule.time.format(context))), + Center(child: Text(schedule.functionOn ? 'On' : 'Off')), + Center( + child: Wrap( + runAlignment: WrapAlignment.center, + children: [ + TextButton( + onPressed: () { + _showEditScheduleDialog(context, schedule, index); + }, + child: Text( + 'Edit', + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blueColor), + ), + ), + TextButton( + onPressed: () { + context + .read() + .add(DeleteScheduleEvent(index)); + }, + child: Text( + 'Delete', + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blueColor), + ), + ), + ], + ), + ), + ], + ); + } + + String _getSelectedDays(List selectedDays) { + final days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + List selectedDaysStr = []; + for (int i = 0; i < selectedDays.length; i++) { + if (selectedDays[i]) { + selectedDaysStr.add(days[i]); + } + } + return selectedDaysStr.join(', '); + } + + void _showEditScheduleDialog( + BuildContext context, ScheduleEntry schedule, int index) { + List selectedDays = List.from(schedule.selectedDays); + TimeOfDay selectedTime = schedule.time; + bool isOn = schedule.functionOn; + + showDialog( + context: context, + builder: (ctx) { + return StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + title: const Text('Edit Schedule'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton( + onPressed: () async { + TimeOfDay? time = await showTimePicker( + context: context, + initialTime: selectedTime, + ); + if (time != null) { + setState(() { + selectedTime = time; + }); + } + }, + child: Text(selectedTime.format(context)), + ), + // Same checkboxes and function on/off logic as before + ], + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + context.read().add( + UpdateScheduleEntryEvent( + index: index, + selectedDays: selectedDays, + time: selectedTime, + functionOn: isOn, + ), + ); + Navigator.pop(context); + }, + child: const Text('Save'), + ), + ], + ); + }, + ); + }, + ); + } + + Widget _buildTableHeader(String label) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + label, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ); + } + + void _showAddScheduleDialog(BuildContext context) { + showDialog( + context: context, + builder: (ctx) { + List selectedDays = [ + false, + false, + false, + false, + false, + false, + false + ]; // Mon - Sun + TimeOfDay? selectedTime; + bool isOn = false; + + return BlocProvider.value( + value: BlocProvider.of(context), + child: StatefulBuilder( + builder: (ctx, setState) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20)), + title: const Text('Scheduling'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton( + onPressed: () async { + TimeOfDay? time = await showTimePicker( + context: context, + initialTime: TimeOfDay.now(), + ); + if (time != null) { + setState(() { + selectedTime = time; + }); + } + }, + child: Text( + selectedTime == null + ? 'Time' + : '${selectedTime!.format(context)}', + ), + ), + const SizedBox(height: 10), + Row( + children: [ + _buildDayCheckbox(setState, 'Mon', 0, selectedDays), + _buildDayCheckbox(setState, 'Tue', 1, selectedDays), + _buildDayCheckbox(setState, 'Wed', 2, selectedDays), + _buildDayCheckbox(setState, 'Thu', 3, selectedDays), + _buildDayCheckbox(setState, 'Fri', 4, selectedDays), + _buildDayCheckbox(setState, 'Sat', 5, selectedDays), + _buildDayCheckbox(setState, 'Sun', 6, selectedDays), + ], + ), + const SizedBox(height: 10), + Row( + children: [ + const Text('Function:'), + Radio( + value: true, + groupValue: isOn, + onChanged: (bool? value) { + setState(() { + isOn = value!; + }); + }, + ), + const Text('On'), + Radio( + value: false, + groupValue: isOn, + onChanged: (bool? value) { + setState(() { + isOn = value!; + }); + }, + ), + const Text('Off'), + ], + ), + ], + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + // Dispatch Bloc Event to Add Schedule + if (selectedTime != null) { + context.read().add(AddScheduleEvent( + time: selectedTime!, + selectedDays: selectedDays, + functionOn: isOn, + )); + Navigator.pop(context); // Close the dialog + } + }, + child: const Text('Save'), + ), + ], + ); + }, + ), + ); + }, + ); + } + + Widget _buildDayCheckbox(void Function(void Function()) setState, String day, + int index, List selectedDays) { + return Row( + children: [ + Checkbox( + value: selectedDays[index], + onChanged: (bool? value) { + setState(() { + selectedDays[index] = value!; + }); + }, + ), + Text(day), + ], + ); + } + + Center _buildSaveStopCancelButtons( + BuildContext context, WaterHeaterDeviceStatusLoaded state) { + return Center( + child: SizedBox( + width: 400, + height: 50, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: DefaultButton( + height: 40, + onPressed: () { + Navigator.pop(context); + }, + backgroundColor: ColorsManager.boxColor, + child: Text( + 'Cancel', + style: context.textTheme.bodyMedium, + ), + ), + ), + const SizedBox(width: 20), + Expanded( + child: + (state.countdownRemaining != null && state.isActive == true) + ? DefaultButton( + height: 40, + onPressed: () { + late String code; + if (state.scheduleMode == ScheduleModes.countdown) { + code = 'countdown_1'; + } else if (state.scheduleMode == + ScheduleModes.inching) { + code = 'switch_inching'; + } + context + .read() + .add(StopScheduleEvent(widget.status.uuid)); + context.read().add( + ToggleWaterHeaterEvent( + deviceId: widget.status.uuid, + code: code, + value: 0, + ), + ); + }, + backgroundColor: Colors.red, + child: const Text('Stop'), + ) + : DefaultButton( + height: 40, + onPressed: () { + late String code; + if (state.scheduleMode == ScheduleModes.countdown) { + code = 'countdown_1'; + } else if (state.scheduleMode == + ScheduleModes.inching) { + code = 'switch_inching'; + } + context.read().add( + ToggleWaterHeaterEvent( + deviceId: widget.status.uuid, + code: code, + value: Duration( + hours: state.hours ?? 0, + minutes: state.minutes ?? 0) + .inSeconds, + ), + ); + }, + backgroundColor: ColorsManager.primaryColor, + child: const Text('Save'), + ), + ), + ], + ), + ), + ); + } + + List _buildCountDownAngInchingView( + BuildContext context, WaterHeaterDeviceStatusLoaded state) { + return [ + Text( + 'Countdown:', + style: context.textTheme.bodySmall!.copyWith( + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), + const SizedBox(height: 4), + _hourMinutesWheel(state, context) + ]; + } + + Row _hourMinutesWheel( + WaterHeaterDeviceStatusLoaded state, BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + _buildPickerColumn(context, 'h', state.hours ?? 0, 24, (value) { + context.read().add(UpdateScheduleEvent( + scheduleMode: state.scheduleMode ?? ScheduleModes.countdown, + hours: value, + minutes: state.minutes ?? 0, + )); + }), + const SizedBox(width: 10), + _buildPickerColumn(context, 'm', state.minutes ?? 0, 60, (value) { + context.read().add(UpdateScheduleEvent( + scheduleMode: state.scheduleMode ?? ScheduleModes.countdown, + hours: state.hours ?? 0, + minutes: value, + )); + }), + ], + ); + } + + Widget _buildPickerColumn(BuildContext context, String label, + int initialValue, int itemCount, ValueChanged onSelected) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + height: 50, + 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: const TextStyle(fontSize: 24), + ), + ); + }, + childCount: itemCount, + ), + ), + ), + const SizedBox(height: 8), + Text( + label, + style: const TextStyle( + color: ColorsManager.grayColor, + fontSize: 18, + ), + ), + ], + ); + } }