From 1e2e2bf4e664b13ed2cdd4c782c8a5f6b9051dfc Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 6 Oct 2024 15:09:36 +0300 Subject: [PATCH 1/8] push garage partial garage door --- .../helper/route_controls_based_code.dart | 5 + .../garage_door/bloc/garage_door_bloc.dart | 254 ++++++++++++++++++ .../garage_door/bloc/garage_door_event.dart | 165 ++++++++++++ .../garage_door/bloc/garage_door_state.dart | 129 +++++++++ .../helper/garage_door_helper.dart | 252 +++++++++++++++++ .../garage_door/models/garage_door_model.dart | 123 +++++++++ .../view/garage_door_batch_control_view.dart | 0 .../view/garage_door_control_view.dart | 177 ++++++++++++ .../widgets/schedule__garage_table.dart | 212 +++++++++++++++ .../widgets/schedule_garage_header.dart | 46 ++++ .../widgets/schedule_garage_managment_ui.dart | 50 ++++ .../widgets/schedule_garage_mode_buttons.dart | 45 ++++ .../schedule_garage_mode_selector.dart | 68 +++++ .../widgets/schedule_garage_view.dart | 84 ++++++ .../widgets/schedule_row_widget.dart | 76 ------ 15 files changed, 1610 insertions(+), 76 deletions(-) create mode 100644 lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart create mode 100644 lib/pages/device_managment/garage_door/bloc/garage_door_event.dart create mode 100644 lib/pages/device_managment/garage_door/bloc/garage_door_state.dart create mode 100644 lib/pages/device_managment/garage_door/helper/garage_door_helper.dart create mode 100644 lib/pages/device_managment/garage_door/models/garage_door_model.dart create mode 100644 lib/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart create mode 100644 lib/pages/device_managment/garage_door/view/garage_door_control_view.dart create mode 100644 lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart create mode 100644 lib/pages/device_managment/garage_door/widgets/schedule_garage_header.dart create mode 100644 lib/pages/device_managment/garage_door/widgets/schedule_garage_managment_ui.dart create mode 100644 lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart create mode 100644 lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_selector.dart create mode 100644 lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart delete mode 100644 lib/pages/device_managment/water_heater/widgets/schedule_row_widget.dart diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index eca980c6..3515ccf7 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_st import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_control_view.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart'; import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_control_view.dart'; @@ -82,6 +83,10 @@ mixin RouteControlsBasedCode { ); case 'DS': return MainDoorSensorControlView(device: device); + case 'GD': + return GarageDoorControlView( + deviceId: device.uuid!, + ); default: return const SizedBox(); } diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart new file mode 100644 index 00000000..da93a137 --- /dev/null +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart @@ -0,0 +1,254 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.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/models/garage_door_model.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/services/devices_mang_api.dart'; +import 'package:syncrow_web/utils/format_date_time.dart'; + +class GarageDoorBloc extends Bloc { + final String deviceId; + late GarageDoorStatusModel deviceStatus; + Timer? _timer; + + GarageDoorBloc({required this.deviceId}) : super(GarageDoorInitialState()) { + on(_fetchGarageDoorStatus); + on(_toggleGarageDoor); + on(_addSchedule); + on(_updateSchedule); + on(_deleteSchedule); + on(_fetchSchedules); + on(_increaseDelay); + on(_decreaseDelay); + on(_fetchRecords); + on(_handleUpdate); + on(_updateSelectedTime); + on(_updateSelectedDay); + on(_updateFunctionOn); + on(_initializeAddSchedule); + } + + void _fetchGarageDoorStatus(GarageDoorInitialEvent event, Emitter emit) async { + emit(GarageDoorLoadingState()); + try { + var response = await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = GarageDoorStatusModel.fromJson(deviceId, response.status); + emit(GarageDoorLoadedState(status: deviceStatus)); + // _listenToChanges(); + } catch (e) { + emit(GarageDoorErrorState(message: e.toString())); + } + } + + void _toggleGarageDoor(ToggleGarageDoorEvent event, Emitter emit) async { + emit(GarageDoorLoadingNewState(garageDoorModel: deviceStatus)); + deviceStatus = deviceStatus.copyWith(isOpen: event.isOpen); + emit(GarageDoorLoadedState(status: deviceStatus)); + await _runDeBouncer( + deviceId: event.deviceId, + code: event.code, + value: event.isOpen, + emit: emit, + isBatch: false, + ); + } + + Future _addSchedule(AddGarageDoorScheduleEvent event, Emitter emit) async { + emit(GarageDoorLoadingState()); + try { + ScheduleEntry newSchedule = ScheduleEntry( + category: event.category, + time: formatTimeOfDayToISO(event.time), + function: Status(code: 'switch_1', value: event.functionOn), + days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays), + ); + + bool success = await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId); + + if (success) { + add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: event.category)); + } else { + emit(GarageDoorErrorState(message: 'Failed to add schedule.')); + } + } catch (e) { + emit(GarageDoorErrorState(message: e.toString())); + } + } + + Future _updateSchedule(UpdateGarageDoorScheduleEvent event, Emitter emit) async { + emit(GarageDoorLoadingState()); + try { + final updatedSchedules = deviceStatus.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: deviceStatus.uuid, + scheduleId: event.scheduleId, + ); + + if (success) { + deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules); + emit(GarageDoorLoadedState(status: deviceStatus)); + } else { + emit(GarageDoorErrorState(message: 'Failed to update schedule.')); + } + } catch (e) { + emit(GarageDoorErrorState(message: e.toString())); + } + } + + Future _deleteSchedule(DeleteGarageDoorScheduleEvent event, Emitter emit) async { + emit(GarageDoorLoadingState()); + try { + bool success = await DevicesManagementApi().deleteScheduleRecord(deviceStatus.uuid, event.scheduleId); + + if (success) { + final updatedSchedules = + deviceStatus.schedules.where((schedule) => schedule.scheduleId != event.scheduleId).toList(); + deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules); + emit(GarageDoorLoadedState(status: deviceStatus)); + } else { + emit(GarageDoorErrorState(message: 'Failed to delete schedule.')); + } + } catch (e) { + emit(GarageDoorErrorState(message: e.toString())); + } + } + + Future _fetchSchedules(FetchGarageDoorSchedulesEvent event, Emitter emit) async { + emit(GarageDoorLoadingState()); + try { + List schedules = + await DevicesManagementApi().getDeviceSchedules(deviceStatus.uuid, event.category); + + deviceStatus = deviceStatus.copyWith(schedules: schedules); + emit(GarageDoorLoadedState(status: deviceStatus)); + } catch (e) { + emit(GarageDoorErrorState(message: 'Failed to fetch schedules.')); + } + } + + void _increaseDelay(IncreaseGarageDoorDelayEvent event, Emitter emit) async { + emit(GarageDoorLoadingNewState(garageDoorModel: deviceStatus)); + try { + deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay + Duration(minutes: 10)); + emit(GarageDoorLoadedState(status: deviceStatus)); + } catch (e) { + emit(GarageDoorErrorState(message: e.toString())); + } + } + + void _decreaseDelay(DecreaseGarageDoorDelayEvent event, Emitter emit) async { + emit(GarageDoorLoadingNewState(garageDoorModel: deviceStatus)); + try { + if (deviceStatus.delay.inMinutes > 10) { + deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay - Duration(minutes: 10)); + } + emit(GarageDoorLoadedState(status: deviceStatus)); + } catch (e) { + emit(GarageDoorErrorState(message: e.toString())); + } + } + + Future _updateSelectedTime(UpdateSelectedTimeEvent event, Emitter emit) async { + final currentState = state; + if (currentState is GarageDoorLoadedState) { + emit(currentState.copyWith(selectedTime: event.selectedTime)); + } + } + + Future _updateSelectedDay(UpdateSelectedDayEvent event, Emitter emit) async { + final currentState = state; + if (currentState is GarageDoorLoadedState) { + List updatedDays = List.from(currentState.selectedDays); + updatedDays[event.dayIndex] = event.isSelected; + emit(currentState.copyWith(selectedDays: updatedDays)); + } + } + + Future _updateFunctionOn(UpdateFunctionOnEvent event, Emitter emit) async { + final currentState = state; + if (currentState is GarageDoorLoadedState) { + emit(currentState.copyWith(functionOn: event.functionOn)); + } + } + + Future _initializeAddSchedule(InitializeAddScheduleEvent event, Emitter emit) async { + final currentState = state; + if (currentState is GarageDoorLoadedState) { + emit(currentState.copyWith( + selectedTime: event.selectedTime, + selectedDays: event.selectedDays, + functionOn: event.functionOn, + isEditing: event.isEditing, + )); + } + } + + Future _fetchRecords(FetchGarageDoorRecordsEvent event, Emitter emit) async { + emit(GarageDoorReportsLoadingState()); + try { + final DeviceReport records = await DevicesManagementApi.getDeviceReports(event.deviceId, 'code_for_records'); + emit(GarageDoorReportsState(deviceReport: records)); + } catch (e) { + emit(GarageDoorReportsFailedState(error: e.toString())); + } + } + + void _handleUpdate(GarageDoorUpdatedEvent event, Emitter emit) { + emit(GarageDoorLoadedState(status: deviceStatus)); + } + + Future _runDeBouncer({ + required dynamic deviceId, + required String code, + required dynamic value, + required Emitter emit, + required bool isBatch, + }) async { + late String id; + if (deviceId is List) { + id = deviceId.first; + } else { + id = deviceId; + } + + if (_timer != null) { + _timer!.cancel(); + } + _timer = Timer(const Duration(seconds: 1), () async { + try { + late bool response; + if (isBatch) { + response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); + } else { + response = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); + } + + if (!response) { + add(GarageDoorInitialEvent(id)); + } else if (response == true && code == 'scene') { + emit(GarageDoorLoadingState()); + await Future.delayed(const Duration(seconds: 1)); + add(GarageDoorInitialEvent(id)); + } + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(GarageDoorInitialEvent(id)); + } + }); + } +} diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart new file mode 100644 index 00000000..754ae140 --- /dev/null +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart @@ -0,0 +1,165 @@ +// lib/pages/device_managment/garage_door/bloc/event.dart + +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; + +abstract class GarageDoorEvent extends Equatable { + const GarageDoorEvent(); + + @override + List get props => []; +} + +class GarageDoorInitialEvent extends GarageDoorEvent { + final String deviceId; + + const GarageDoorInitialEvent(this.deviceId); + + @override + List get props => [deviceId]; +} + +class ToggleGarageDoorEvent extends GarageDoorEvent { + final String deviceId; + final bool isOpen; + final String code; + + const ToggleGarageDoorEvent({required this.deviceId, required this.isOpen, required this.code}); + + @override + List get props => [deviceId, isOpen]; +} + +class AddGarageDoorScheduleEvent extends GarageDoorEvent { + final String category; + final TimeOfDay time; + final bool functionOn; + final List selectedDays; + + const AddGarageDoorScheduleEvent({ + required this.category, + required this.time, + required this.functionOn, + required this.selectedDays, + }); +} + +class UpdateGarageDoorScheduleEvent extends GarageDoorEvent { + final String deviceId; + final String scheduleId; + final bool enable; + final bool functionOn; + final int index; + + const UpdateGarageDoorScheduleEvent({ + required this.deviceId, + required this.scheduleId, + required this.enable, + required this.functionOn, + required this.index, + }); +} + +class DeleteGarageDoorScheduleEvent extends GarageDoorEvent { + final String deviceId; + final String scheduleId; + final int index; + + const DeleteGarageDoorScheduleEvent({ + required this.deviceId, + required this.scheduleId, + required this.index, + }); +} + +class FetchGarageDoorSchedulesEvent extends GarageDoorEvent { + final String deviceId; + final String category; + + const FetchGarageDoorSchedulesEvent({ + required this.deviceId, + required this.category, + }); +} + +class IncreaseGarageDoorDelayEvent extends GarageDoorEvent { + final String deviceId; + + const IncreaseGarageDoorDelayEvent({required this.deviceId}); + + @override + List get props => [deviceId]; +} + +class DecreaseGarageDoorDelayEvent extends GarageDoorEvent { + final String deviceId; + + const DecreaseGarageDoorDelayEvent({required this.deviceId}); + + @override + List get props => [deviceId]; +} + +class FetchGarageDoorRecordsEvent extends GarageDoorEvent { + final String deviceId; + + const FetchGarageDoorRecordsEvent({required this.deviceId}); + + @override + List get props => [deviceId]; +} + +class GarageDoorUpdatedEvent extends GarageDoorEvent {} + +class UpdateSelectedTimeEvent extends GarageDoorEvent { + final TimeOfDay? selectedTime; + + const UpdateSelectedTimeEvent(this.selectedTime); + + @override + List get props => [selectedTime]; +} + +class UpdateSelectedDayEvent extends GarageDoorEvent { + final int dayIndex; + final bool isSelected; + + const UpdateSelectedDayEvent(this.dayIndex, this.isSelected); + + @override + List get props => [dayIndex, isSelected]; +} + +class UpdateFunctionOnEvent extends GarageDoorEvent { + final bool functionOn; + + const UpdateFunctionOnEvent({required this.functionOn}); + + @override + List get props => [functionOn]; +} + +class InitializeAddScheduleEvent extends GarageDoorEvent { + final TimeOfDay? selectedTime; + final List selectedDays; + final bool functionOn; + final bool isEditing; + final int? index; + + const InitializeAddScheduleEvent({ + required this.selectedTime, + required this.selectedDays, + required this.functionOn, + required this.isEditing, + this.index, + }); + + @override + List get props => [ + selectedTime, + selectedDays, + functionOn, + isEditing, + index, + ]; +} diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart new file mode 100644 index 00000000..24b07618 --- /dev/null +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart @@ -0,0 +1,129 @@ +// lib/pages/device_managment/garage_door/bloc/state.dart + +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.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'; + +abstract class GarageDoorState extends Equatable { + const GarageDoorState(); + + @override + List get props => []; +} + +class GarageDoorInitialState extends GarageDoorState {} + +class GarageDoorLoadingState extends GarageDoorState {} + +class GarageDoorLoadedState extends GarageDoorState { + final GarageDoorStatusModel status; + final List? schedules; + final Duration? delay; + final DeviceReport? records; + final List selectedDays; + final TimeOfDay? selectedTime; + final bool functionOn; + final bool isEditing; + final ScheduleModes? scheduleMode; + + const GarageDoorLoadedState({ + required this.status, + this.schedules, + this.delay, + this.records, + this.selectedDays = const [false, false, false, false, false, false, false], + this.selectedTime, + this.functionOn = false, + this.isEditing = false, + this.scheduleMode = ScheduleModes.schedule, + }); + + @override + List get props => [ + status, + schedules, + delay, + records, + selectedDays, + selectedTime, + functionOn, + isEditing, + scheduleMode, + ]; + + GarageDoorLoadedState copyWith({ + GarageDoorStatusModel? status, + List? schedules, + Duration? delay, + DeviceReport? records, + List? selectedDays, + TimeOfDay? selectedTime, + bool? functionOn, + bool? isEditing, + ScheduleModes? scheduleMode, + }) { + return GarageDoorLoadedState( + status: status ?? this.status, + schedules: schedules ?? this.schedules, + delay: delay ?? this.delay, + records: records ?? this.records, + selectedDays: selectedDays ?? this.selectedDays, + selectedTime: selectedTime, + functionOn: functionOn ?? this.functionOn, + isEditing: isEditing ?? this.isEditing, + scheduleMode: scheduleMode ?? this.scheduleMode, + ); + } +} + +class GarageDoorErrorState extends GarageDoorState { + final String message; + + const GarageDoorErrorState({required this.message}); + + @override + List get props => [message]; +} + +class GarageDoorLoadingNewState extends GarageDoorState { + final GarageDoorStatusModel garageDoorModel; + + const GarageDoorLoadingNewState({required this.garageDoorModel}); + + @override + List get props => [garageDoorModel]; +} + +class GarageDoorReportsLoadingState extends GarageDoorState {} + +class GarageDoorReportsFailedState extends GarageDoorState { + final String error; + + const GarageDoorReportsFailedState({required this.error}); + + @override + List get props => [error]; +} + +class GarageDoorReportsState extends GarageDoorState { + final DeviceReport deviceReport; + + const GarageDoorReportsState({required this.deviceReport}); + + @override + List get props => [deviceReport]; +} + +class ShowGarageDoorDescriptionState extends GarageDoorState { + final String description; + + const ShowGarageDoorDescriptionState({required this.description}); + + @override + List get props => [description]; +} + +class ScheduleGarageLoadingState extends GarageDoorState {} 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 new file mode 100644 index 00000000..e939ad9e --- /dev/null +++ b/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart @@ -0,0 +1,252 @@ +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/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/water_heater/models/schedule_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class ScheduleGarageDoorDialogHelper { + static void showAddGarageDoorScheduleDialog(BuildContext context, + {ScheduleModel? schedule, int? index, bool? isEdit}) { + final bloc = context.read(); + + 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); + + bloc.add(InitializeAddScheduleEvent( + selectedTime: time, + selectedDays: selectedDays, + functionOn: schedule.function.value, + isEditing: true, + index: index, + )); + } + + showDialog( + context: context, + builder: (ctx) { + return BlocProvider.value( + value: bloc, + child: BlocBuilder( + builder: (context, state) { + if (state is GarageDoorLoadedState) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox(), + Text( + 'Scheduling', + style: context.textTheme.titleLarge!.copyWith( + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(), + ], + ), + const SizedBox(height: 24), + SizedBox( + width: 150, + height: 40, + child: DefaultButton( + padding: 8, + backgroundColor: ColorsManager.boxColor, + borderRadius: 15, + onPressed: isEdit == true + ? null + : () 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), + ], + ), + actions: [ + SizedBox( + width: 200, + child: DefaultButton( + height: 40, + onPressed: () { + Navigator.pop(context); + }, + backgroundColor: ColorsManager.boxColor, + child: Text( + 'Cancel', + style: context.textTheme.bodyMedium, + ), + ), + ), + SizedBox( + width: 200, + child: DefaultButton( + height: 40, + onPressed: () { + if (state.selectedTime != null) { + if (state.isEditing && index != null) { + return; + } else { + bloc.add(AddGarageDoorScheduleEvent( + category: 'switch_1', + time: state.selectedTime!, + selectedDays: state.selectedDays, + functionOn: state.functionOn, + )); + } + Navigator.pop(context); + } + }, + backgroundColor: ColorsManager.primaryColor, + child: const Text('Save'), + ), + ), + ], + ); + } + return const SizedBox(); + }, + ), + ); + }, + ); + } + + 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 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; + } + + static Widget _buildDayCheckboxes(BuildContext context, List selectedDays, {bool? isEdit}) { + final dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + + return Row( + children: List.generate(7, (index) { + return Row( + children: [ + Checkbox( + value: selectedDays[index], + onChanged: isEdit == true + ? null + : (bool? value) { + context.read().add(UpdateSelectedDayEvent(index, value!)); + }, + ), + Text(dayLabels[index]), + ], + ); + }), + ); + } + + static Widget _buildFunctionSwitch(BuildContext context, bool isOn, bool? isEdit) { + return Row( + children: [ + Text( + 'Function:', + style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.grayColor), + ), + const SizedBox(width: 10), + Radio( + value: true, + groupValue: isOn, + onChanged: (bool? value) { + if (isEdit == true) { + return; + } else { + context.read().add(const UpdateFunctionOnEvent(functionOn: true)); + } + }, + ), + const Text('On'), + const SizedBox(width: 10), + Radio( + value: false, + groupValue: isOn, + onChanged: (bool? value) { + if (isEdit == true) { + return; + } else { + context.read().add(const UpdateFunctionOnEvent(functionOn: false)); + } + }, + ), + const Text('Off'), + ], + ); + } +} diff --git a/lib/pages/device_managment/garage_door/models/garage_door_model.dart b/lib/pages/device_managment/garage_door/models/garage_door_model.dart new file mode 100644 index 00000000..9e8d75fe --- /dev/null +++ b/lib/pages/device_managment/garage_door/models/garage_door_model.dart @@ -0,0 +1,123 @@ +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 GarageDoorStatusModel { + final String uuid; + final bool switch1; + final int countdown1; + final bool doorContactState; + final int trTimeCon; + final int countdownAlarm; + final String doorControl1; + final bool voiceControl1; + final String doorState1; + final bool isOpen; + final Duration delay; + final List schedules; // Add schedules field + + GarageDoorStatusModel({ + required this.uuid, + required this.switch1, + required this.countdown1, + required this.doorContactState, + required this.trTimeCon, + required this.countdownAlarm, + required this.doorControl1, + required this.voiceControl1, + required this.doorState1, + required this.isOpen, + required this.delay, + required this.schedules, // Initialize schedules + }); + + factory GarageDoorStatusModel.fromJson(String id, List jsonList) { + late bool switch1; + late int countdown1; + late bool doorContactState; + late int trTimeCon; + late int countdownAlarm; + late String doorControl1; + late bool voiceControl1; + late String doorState1; + List schedules = []; // Initialize schedules + + for (var status in jsonList) { + switch (status.code) { + case 'switch_1': + switch1 = status.value ?? false; + break; + case 'countdown_1': + countdown1 = status.value ?? 0; + break; + case 'doorcontact_state': + doorContactState = status.value ?? false; + break; + case 'tr_timecon': + trTimeCon = status.value ?? 0; + break; + case 'countdown_alarm': + countdownAlarm = status.value ?? 0; + break; + case 'door_control_1': + doorControl1 = status.value ?? 'closed'; + break; + case 'voice_control_1': + voiceControl1 = status.value ?? false; + break; + case 'door_state_1': + doorState1 = status.value ?? 'closed'; + break; + } + } + + return GarageDoorStatusModel( + uuid: id, + switch1: switch1, + countdown1: countdown1, + doorContactState: doorContactState, + trTimeCon: trTimeCon, + countdownAlarm: countdownAlarm, + doorControl1: doorControl1, + voiceControl1: voiceControl1, + doorState1: doorState1, + isOpen: doorState1 == 'open' ? true : false, + delay: Duration(seconds: countdown1), + schedules: schedules, // Assign schedules + ); + } + + GarageDoorStatusModel copyWith({ + String? uuid, + bool? switch1, + int? countdown1, + bool? doorContactState, + int? trTimeCon, + int? countdownAlarm, + String? doorControl1, + bool? voiceControl1, + String? doorState1, + bool? isOpen, + Duration? delay, + List? schedules, // Add schedules to copyWith + }) { + return GarageDoorStatusModel( + uuid: uuid ?? this.uuid, + switch1: switch1 ?? this.switch1, + countdown1: countdown1 ?? this.countdown1, + doorContactState: doorContactState ?? this.doorContactState, + trTimeCon: trTimeCon ?? this.trTimeCon, + countdownAlarm: countdownAlarm ?? this.countdownAlarm, + doorControl1: doorControl1 ?? this.doorControl1, + voiceControl1: voiceControl1 ?? this.voiceControl1, + doorState1: doorState1 ?? this.doorState1, + isOpen: isOpen ?? this.isOpen, + delay: delay ?? this.delay, + schedules: schedules ?? this.schedules, // Copy schedules + ); + } + + @override + String toString() { + return 'GarageDoorStatusModel(uuid: $uuid, switch1: $switch1, countdown1: $countdown1, doorContactState: $doorContactState, trTimeCon: $trTimeCon, countdownAlarm: $countdownAlarm, doorControl1: $doorControl1, voiceControl1: $voiceControl1, doorState1: $doorState1, isOpen: $isOpen, delay: $delay, schedules: $schedules)'; + } +} diff --git a/lib/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart b/lib/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart new file mode 100644 index 00000000..e69de29b 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 new file mode 100644 index 00000000..8e6a62f0 --- /dev/null +++ b/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart @@ -0,0 +1,177 @@ +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/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/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/shared/device_controls_container.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +import '../../main_door_sensor/view/main_door_control_view.dart'; + +class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout { + final String deviceId; + + const GarageDoorControlView({required this.deviceId, super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Garage Door Control')), + body: BlocProvider( + create: (context) => GarageDoorBloc(deviceId: deviceId)..add(GarageDoorInitialEvent(deviceId)), + child: BlocBuilder( + builder: (context, state) { + if (state is GarageDoorLoadingState || state is GarageDoorLoadingNewState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is GarageDoorLoadedState) { + return _buildControlView(context, state.status); + } else if (state is GarageDoorErrorState) { + return Center(child: Text('Error: ${state.message}')); + } + return const Center(child: Text('Unknown state')); + }, + ), + ), + ); + } + + Widget _buildControlView(BuildContext context, GarageDoorStatusModel status) { + final isExtraLarge = isExtraLargeScreenSize(context); + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + + return GridView( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isLarge || isExtraLarge + ? 3 + : isMedium + ? 2 + : 1, + childAspectRatio: 1.5, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + children: [ + IconNameStatusContainer( + isFullIcon: true, + name: status.doorContactState ? 'Open' : 'Close', + icon: Assets.openCloseDoor, + onTap: () { + context.read().add( + ToggleGarageDoorEvent(deviceId: status.uuid, isOpen: !status.isOpen, code: 'switch_1'), + ); + }, + status: status.doorContactState, + textColor: status.doorContactState ? ColorsManager.red : ColorsManager.blackColor, + paddingAmount: 8, + ), + GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: BlocProvider.of(context), + child: BuildGarageDoorScheduleView(status: status), + )); + }, + child: DeviceControlsContainer( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 60, + height: 60, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.whiteColors, + ), + padding: const EdgeInsets.all(12), + child: ClipOval( + child: SvgPicture.asset( + Assets.scheduling, + fit: BoxFit.fill, + ), + ), + ), + const SizedBox(height: 8), + Text( + 'Scheduling', + textAlign: TextAlign.center, + style: context.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor, + ), + ), + ], + ), + ), + ), + ToggleWidget( + label: '', + labelWidget: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + onPressed: () { + context.read().add(DecreaseGarageDoorDelayEvent(deviceId: deviceId)); + }, + icon: const Icon( + Icons.remove, + size: 28, + color: ColorsManager.greyColor, + ), + ), + Text( + '${status.delay.inHours.toString().padLeft(2, '0')}', + style: context.textTheme.titleLarge!.copyWith( + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'h', + style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor), + ), + Text( + '${(status.delay.inMinutes % 60).toString().padLeft(2, '0')}', + style: context.textTheme.titleLarge!.copyWith( + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'm', + style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor), + ), + IconButton( + onPressed: () { + context.read().add(IncreaseGarageDoorDelayEvent(deviceId: deviceId)); + }, + icon: const Icon( + Icons.add, + size: 28, + color: ColorsManager.greyColor, + ), + ), + ], + ), + value: false, + code: 'switch_1', + deviceId: status.uuid, + icon: Assets.acSchedule, + onChange: (value) {}, + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart b/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart new file mode 100644 index 00000000..4bd54aa8 --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart @@ -0,0 +1,212 @@ +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/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/helper/garage_door_helper.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'; + +class ScheduleGarageTableWidget extends StatelessWidget { + final GarageDoorLoadedState state; + + const ScheduleGarageTableWidget({ + 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 ScheduleGarageLoadingState) { + return const SizedBox(height: 200, child: Center(child: CircularProgressIndicator())); + } + if (state is GarageDoorLoadedState && state.schedules?.isEmpty == true) { + 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)), + ), + 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(GarageDoorLoadedState state, BuildContext context) { + return SizedBox( + height: 200, + child: SingleChildScrollView( + child: Table( + border: TableBorder.all(color: ColorsManager.graysColor), + defaultVerticalAlignment: TableCellVerticalAlignment.middle, + children: [ + if (state.schedules != null) + 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, GarageDoorLoadedState state) { + return TableRow( + children: [ + Center( + child: GestureDetector( + onTap: () { + context.read().add(UpdateGarageDoorScheduleEvent( + 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: () { + ScheduleGarageDoorDialogHelper.showAddGarageDoorScheduleDialog(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(DeleteGarageDoorScheduleEvent( + index: index, + scheduleId: schedule.scheduleId, + deviceId: state.status.uuid, + )); + }, + 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/pages/device_managment/garage_door/widgets/schedule_garage_header.dart b/lib/pages/device_managment/garage_door/widgets/schedule_garage_header.dart new file mode 100644 index 00000000..cf42e6a3 --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/schedule_garage_header.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class ScheduleGarageHeader extends StatelessWidget { + const ScheduleGarageHeader({super.key}); + + @override + Widget build(BuildContext context) { + return 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: const EdgeInsets.all(1), + icon: const Icon( + Icons.close, + color: Colors.grey, + size: 18, + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/garage_door/widgets/schedule_garage_managment_ui.dart b/lib/pages/device_managment/garage_door/widgets/schedule_garage_managment_ui.dart new file mode 100644 index 00000000..e5819e89 --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/schedule_garage_managment_ui.dart @@ -0,0 +1,50 @@ +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/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class ScheduleGarageManagementUI extends StatelessWidget { + final GarageDoorLoadedState state; + final Function onAddSchedule; + + const ScheduleGarageManagementUI({ + super.key, + required this.state, + required this.onAddSchedule, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 170, + height: 40, + child: DefaultButton( + borderColor: ColorsManager.boxColor, + padding: 2, + backgroundColor: ColorsManager.graysColor, + borderRadius: 15, + onPressed: () => onAddSchedule(), + child: Row( + children: [ + const Icon(Icons.add, color: ColorsManager.primaryColor), + Text( + ' Add new schedule', + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.blackColor, + ), + ), + ], + ), + ), + ), + const SizedBox(height: 20), + ScheduleGarageTableWidget(state: state), + ], + ); + } +} diff --git a/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart b/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart new file mode 100644 index 00000000..f1307d5f --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class ScheduleModeButtons extends StatelessWidget { + final VoidCallback onSave; + + const ScheduleModeButtons({ + super.key, + required this.onSave, + }); + + @override + Widget build(BuildContext context) { + return 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: DefaultButton( + height: 40, + onPressed: onSave, + backgroundColor: ColorsManager.primaryColor, + child: const Text('Save'), + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_selector.dart b/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_selector.dart new file mode 100644 index 00000000..7b6e4690 --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_selector.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.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/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 ScheduleGarageDoorModeSelector extends StatelessWidget { + final GarageDoorLoadedState state; + + const ScheduleGarageDoorModeSelector({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, 'Schedule', ScheduleModes.schedule, state), + ], + ), + ], + ); + } + + Widget _buildRadioTile(BuildContext context, String label, ScheduleModes mode, GarageDoorLoadedState 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.schedule) { + context.read().add( + FetchGarageDoorSchedulesEvent( + category: 'switch_1', + deviceId: state.status.uuid, + ), + ); + } + } + }, + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart b/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart new file mode 100644 index 00000000..be11203f --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.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/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_selector.dart'; + +class BuildGarageDoorScheduleView extends StatefulWidget { + const BuildGarageDoorScheduleView({super.key, required this.status}); + + final GarageDoorStatusModel 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 GarageDoorLoadedState) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const ScheduleGarageHeader(), + const SizedBox(height: 20), + ScheduleGarageDoorModeSelector(state: state), + const SizedBox(height: 20), + ScheduleGarageManagementUI( + state: state, + onAddSchedule: () { + ScheduleGarageDoorDialogHelper.showAddGarageDoorScheduleDialog(context, + schedule: null, index: null, isEdit: false); + }, + ), + ], + ); + } + if (state is GarageDoorLoadingState) { + return const SizedBox( + height: 200, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ScheduleGarageHeader(), + SizedBox( + height: 20, + ), + Center(child: CircularProgressIndicator()), + ], + )); + } + return const SizedBox( + height: 200, + child: ScheduleGarageHeader(), + ); + }, + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/water_heater/widgets/schedule_row_widget.dart b/lib/pages/device_managment/water_heater/widgets/schedule_row_widget.dart deleted file mode 100644 index ee0805d6..00000000 --- a/lib/pages/device_managment/water_heater/widgets/schedule_row_widget.dart +++ /dev/null @@ -1,76 +0,0 @@ -// import 'package:flutter/material.dart'; -// import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart'; -// import 'package:syncrow_web/utils/format_date_time.dart'; -// import 'package:syncrow_web/utils/color_manager.dart'; - -// class ScheduleRowWidget extends StatelessWidget { -// final ScheduleModel schedule; -// final int index; -// final Function onEdit; -// final Function onDelete; - -// const ScheduleRowWidget({ -// super.key, -// required this.schedule, -// required this.index, -// required this.onEdit, -// required this.onDelete, -// }); - -// @override -// Widget build(BuildContext context) { -// return Table( -// border: TableBorder.all(color: ColorsManager.graysColor), -// defaultVerticalAlignment: TableCellVerticalAlignment.middle, -// children: [ -// TableRow( -// children: [ -// Center( -// child: schedule.enable -// ? const Icon(Icons.radio_button_checked, -// color: ColorsManager.blueColor) -// : const Icon(Icons.radio_button_unchecked), -// ), -// Center(child: Text(_getSelectedDays(schedule.selectedDays ?? []))), -// Center(child: Text(formatIsoStringToTime(schedule.time, context))), -// Center(child: Text(schedule.enable ? 'On' : 'Off')), -// Center( -// child: Wrap( -// runAlignment: WrapAlignment.center, -// children: [ -// TextButton( -// style: TextButton.styleFrom(padding: EdgeInsets.zero), -// onPressed: () => onEdit(), -// child: const Text( -// 'Edit', -// style: TextStyle(color: ColorsManager.blueColor), -// ), -// ), -// TextButton( -// style: TextButton.styleFrom(padding: EdgeInsets.zero), -// onPressed: () => onDelete(), -// child: const Text( -// 'Delete', -// style: TextStyle(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(', '); -// } -// } From 4f833e86fb24a114f746b1cfca2ad60e2c4f2c6a Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 6 Oct 2024 23:31:36 +0300 Subject: [PATCH 2/8] push garage door schedule --- assets/icons/closed_door.svg | 135 ++++++++++++++++ assets/icons/door_delay.svg | 29 ++++ assets/icons/opened_door.svg | 104 ++++++++++++ assets/icons/records.svg | 18 +++ .../garage_door/bloc/garage_door_bloc.dart | 152 +++++++++++------- .../garage_door/bloc/garage_door_state.dart | 6 - .../garage_door/models/garage_door_model.dart | 14 +- .../view/garage_door_control_view.dart | 119 +++++++------- .../widgets/schedule__garage_table.dart | 8 +- .../widgets/schedule_garage_mode_buttons.dart | 4 +- .../widgets/schedule_garage_view.dart | 26 ++- .../view/main_door_control_view.dart | 52 +++--- .../shared/device_control_dialog.dart | 13 +- lib/utils/constants/assets.dart | 5 + 14 files changed, 494 insertions(+), 191 deletions(-) create mode 100644 assets/icons/closed_door.svg create mode 100644 assets/icons/door_delay.svg create mode 100644 assets/icons/opened_door.svg create mode 100644 assets/icons/records.svg diff --git a/assets/icons/closed_door.svg b/assets/icons/closed_door.svg new file mode 100644 index 00000000..9cbf40dc --- /dev/null +++ b/assets/icons/closed_door.svg @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/door_delay.svg b/assets/icons/door_delay.svg new file mode 100644 index 00000000..49dbbaef --- /dev/null +++ b/assets/icons/door_delay.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/opened_door.svg b/assets/icons/opened_door.svg new file mode 100644 index 00000000..386a66f1 --- /dev/null +++ b/assets/icons/opened_door.svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/records.svg b/assets/icons/records.svg new file mode 100644 index 00000000..9e316afd --- /dev/null +++ b/assets/icons/records.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart index da93a137..10ccbca4 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart @@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_ import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.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/devices_mang_api.dart'; import 'package:syncrow_web/utils/format_date_time.dart'; @@ -39,27 +40,29 @@ class GarageDoorBloc extends Bloc { var response = await DevicesManagementApi().getDeviceStatus(event.deviceId); deviceStatus = GarageDoorStatusModel.fromJson(deviceId, response.status); emit(GarageDoorLoadedState(status: deviceStatus)); - // _listenToChanges(); } catch (e) { emit(GarageDoorErrorState(message: e.toString())); } } void _toggleGarageDoor(ToggleGarageDoorEvent event, Emitter emit) async { - emit(GarageDoorLoadingNewState(garageDoorModel: deviceStatus)); - deviceStatus = deviceStatus.copyWith(isOpen: event.isOpen); + final oldValue = deviceStatus.switch1; + _updateLocalValue('switch_1', event.isOpen); emit(GarageDoorLoadedState(status: deviceStatus)); - await _runDeBouncer( + final success = await _runDeBouncer( deviceId: event.deviceId, - code: event.code, + code: 'switch_1', value: event.isOpen, + oldValue: oldValue, emit: emit, isBatch: false, ); + if (!success) { + _revertValue('switch_1', oldValue, emit); + } } Future _addSchedule(AddGarageDoorScheduleEvent event, Emitter emit) async { - emit(GarageDoorLoadingState()); try { ScheduleEntry newSchedule = ScheduleEntry( category: event.category, @@ -67,23 +70,20 @@ class GarageDoorBloc extends Bloc { function: Status(code: 'switch_1', value: event.functionOn), days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays), ); - bool success = await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId); - if (success) { - add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: event.category)); + add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1')); } else { - emit(GarageDoorErrorState(message: 'Failed to add schedule.')); + emit(GarageDoorLoadedState(status: deviceStatus)); } } catch (e) { - emit(GarageDoorErrorState(message: e.toString())); + emit(GarageDoorLoadedState(status: deviceStatus)); } } Future _updateSchedule(UpdateGarageDoorScheduleEvent event, Emitter emit) async { - emit(GarageDoorLoadingState()); try { - final updatedSchedules = deviceStatus.schedules.map((schedule) { + final updatedSchedules = deviceStatus.schedules?.map((schedule) { if (schedule.scheduleId == event.scheduleId) { return schedule.copyWith( function: Status(code: 'switch_1', value: event.functionOn), @@ -92,57 +92,61 @@ class GarageDoorBloc extends Bloc { } return schedule; }).toList(); - bool success = await DevicesManagementApi().updateScheduleRecord( enable: event.enable, uuid: deviceStatus.uuid, scheduleId: event.scheduleId, ); - if (success) { deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules); emit(GarageDoorLoadedState(status: deviceStatus)); } else { - emit(GarageDoorErrorState(message: 'Failed to update schedule.')); + emit(GarageDoorLoadedState(status: deviceStatus)); } } catch (e) { - emit(GarageDoorErrorState(message: e.toString())); + emit(GarageDoorLoadedState(status: deviceStatus)); } } Future _deleteSchedule(DeleteGarageDoorScheduleEvent event, Emitter emit) async { - emit(GarageDoorLoadingState()); try { bool success = await DevicesManagementApi().deleteScheduleRecord(deviceStatus.uuid, event.scheduleId); - if (success) { final updatedSchedules = - deviceStatus.schedules.where((schedule) => schedule.scheduleId != event.scheduleId).toList(); + deviceStatus.schedules?.where((schedule) => schedule.scheduleId != event.scheduleId).toList(); deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules); emit(GarageDoorLoadedState(status: deviceStatus)); } else { - emit(GarageDoorErrorState(message: 'Failed to delete schedule.')); + emit(GarageDoorLoadedState(status: deviceStatus)); } } catch (e) { - emit(GarageDoorErrorState(message: e.toString())); + emit(GarageDoorLoadedState(status: deviceStatus)); } } Future _fetchSchedules(FetchGarageDoorSchedulesEvent event, Emitter emit) async { - emit(GarageDoorLoadingState()); + emit(ScheduleGarageLoadingState()); try { List schedules = await DevicesManagementApi().getDeviceSchedules(deviceStatus.uuid, event.category); - deviceStatus = deviceStatus.copyWith(schedules: schedules); - emit(GarageDoorLoadedState(status: deviceStatus)); + emit( + GarageDoorLoadedState( + status: deviceStatus, + scheduleMode: ScheduleModes.schedule, + ), + ); } catch (e) { - emit(GarageDoorErrorState(message: 'Failed to fetch schedules.')); + emit( + GarageDoorLoadedState( + status: deviceStatus, + scheduleMode: ScheduleModes.schedule, + ), + ); } } void _increaseDelay(IncreaseGarageDoorDelayEvent event, Emitter emit) async { - emit(GarageDoorLoadingNewState(garageDoorModel: deviceStatus)); try { deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay + Duration(minutes: 10)); emit(GarageDoorLoadedState(status: deviceStatus)); @@ -152,7 +156,6 @@ class GarageDoorBloc extends Bloc { } void _decreaseDelay(DecreaseGarageDoorDelayEvent event, Emitter emit) async { - emit(GarageDoorLoadingNewState(garageDoorModel: deviceStatus)); try { if (deviceStatus.delay.inMinutes > 10) { deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay - Duration(minutes: 10)); @@ -175,14 +178,14 @@ class GarageDoorBloc extends Bloc { if (currentState is GarageDoorLoadedState) { List updatedDays = List.from(currentState.selectedDays); updatedDays[event.dayIndex] = event.isSelected; - emit(currentState.copyWith(selectedDays: updatedDays)); + emit(currentState.copyWith(selectedDays: updatedDays, selectedTime: currentState.selectedTime)); } } Future _updateFunctionOn(UpdateFunctionOnEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { - emit(currentState.copyWith(functionOn: event.functionOn)); + emit(currentState.copyWith(functionOn: event.functionOn, selectedTime: currentState.selectedTime)); } } @@ -201,7 +204,7 @@ class GarageDoorBloc extends Bloc { Future _fetchRecords(FetchGarageDoorRecordsEvent event, Emitter emit) async { emit(GarageDoorReportsLoadingState()); try { - final DeviceReport records = await DevicesManagementApi.getDeviceReports(event.deviceId, 'code_for_records'); + final DeviceReport records = await DevicesManagementApi.getDeviceReports(event.deviceId, 'switch_1'); emit(GarageDoorReportsState(deviceReport: records)); } catch (e) { emit(GarageDoorReportsFailedState(error: e.toString())); @@ -212,43 +215,68 @@ class GarageDoorBloc extends Bloc { emit(GarageDoorLoadedState(status: deviceStatus)); } - Future _runDeBouncer({ + Future _runDeBouncer({ required dynamic deviceId, required String code, required dynamic value, + required dynamic oldValue, required Emitter emit, required bool isBatch, }) async { - late String id; - if (deviceId is List) { - id = deviceId.first; - } else { - id = deviceId; - } - - if (_timer != null) { - _timer!.cancel(); - } - _timer = Timer(const Duration(seconds: 1), () async { - try { - late bool response; - if (isBatch) { - response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); - } else { - response = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); - } - - if (!response) { - add(GarageDoorInitialEvent(id)); - } else if (response == true && code == 'scene') { - emit(GarageDoorLoadingState()); - await Future.delayed(const Duration(seconds: 1)); - add(GarageDoorInitialEvent(id)); - } - } catch (_) { - await Future.delayed(const Duration(milliseconds: 500)); - add(GarageDoorInitialEvent(id)); + try { + late bool status; + await Future.delayed(const Duration(milliseconds: 500)); + if (isBatch) { + status = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); + } else { + status = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); } - }); + + if (!status) { + _revertValue(code, oldValue, emit); + return false; + } else { + return true; + } + } catch (e) { + _revertValue(code, oldValue, emit); + return false; + } + } + + void _revertValue(String code, dynamic oldValue, Emitter emit) { + switch (code) { + case 'switch_1': + if (oldValue is bool) { + deviceStatus = deviceStatus.copyWith(switch1: oldValue); + } + break; + // Add other cases if needed + default: + break; + } + if (state is GarageDoorLoadedState) { + final currentState = state as GarageDoorLoadedState; + emit(currentState.copyWith(status: deviceStatus)); + } + } + + void _updateLocalValue(String code, dynamic value) { + switch (code) { + case 'switch_1': + if (value is bool) { + deviceStatus = deviceStatus.copyWith(switch1: value); + } + break; + // Add other cases if needed + default: + break; + } + } + + @override + Future close() { + _timer?.cancel(); + return super.close(); } } diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart index 24b07618..6f3a2320 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart @@ -4,7 +4,6 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.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'; abstract class GarageDoorState extends Equatable { @@ -20,7 +19,6 @@ class GarageDoorLoadingState extends GarageDoorState {} class GarageDoorLoadedState extends GarageDoorState { final GarageDoorStatusModel status; - final List? schedules; final Duration? delay; final DeviceReport? records; final List selectedDays; @@ -31,7 +29,6 @@ class GarageDoorLoadedState extends GarageDoorState { const GarageDoorLoadedState({ required this.status, - this.schedules, this.delay, this.records, this.selectedDays = const [false, false, false, false, false, false, false], @@ -44,7 +41,6 @@ class GarageDoorLoadedState extends GarageDoorState { @override List get props => [ status, - schedules, delay, records, selectedDays, @@ -56,7 +52,6 @@ class GarageDoorLoadedState extends GarageDoorState { GarageDoorLoadedState copyWith({ GarageDoorStatusModel? status, - List? schedules, Duration? delay, DeviceReport? records, List? selectedDays, @@ -67,7 +62,6 @@ class GarageDoorLoadedState extends GarageDoorState { }) { return GarageDoorLoadedState( status: status ?? this.status, - schedules: schedules ?? this.schedules, delay: delay ?? this.delay, records: records ?? this.records, selectedDays: selectedDays ?? this.selectedDays, diff --git a/lib/pages/device_managment/garage_door/models/garage_door_model.dart b/lib/pages/device_managment/garage_door/models/garage_door_model.dart index 9e8d75fe..dcb4718c 100644 --- a/lib/pages/device_managment/garage_door/models/garage_door_model.dart +++ b/lib/pages/device_managment/garage_door/models/garage_door_model.dart @@ -11,9 +11,9 @@ class GarageDoorStatusModel { final String doorControl1; final bool voiceControl1; final String doorState1; - final bool isOpen; + // final bool isOpen; final Duration delay; - final List schedules; // Add schedules field + final List? schedules; // Add schedules field GarageDoorStatusModel({ required this.uuid, @@ -25,7 +25,7 @@ class GarageDoorStatusModel { required this.doorControl1, required this.voiceControl1, required this.doorState1, - required this.isOpen, + // required this.isOpen, required this.delay, required this.schedules, // Initialize schedules }); @@ -80,7 +80,7 @@ class GarageDoorStatusModel { doorControl1: doorControl1, voiceControl1: voiceControl1, doorState1: doorState1, - isOpen: doorState1 == 'open' ? true : false, + // isOpen: doorState1 == 'open' ? true : false, delay: Duration(seconds: countdown1), schedules: schedules, // Assign schedules ); @@ -96,7 +96,7 @@ class GarageDoorStatusModel { String? doorControl1, bool? voiceControl1, String? doorState1, - bool? isOpen, + // bool? isOpen, Duration? delay, List? schedules, // Add schedules to copyWith }) { @@ -110,7 +110,7 @@ class GarageDoorStatusModel { doorControl1: doorControl1 ?? this.doorControl1, voiceControl1: voiceControl1 ?? this.voiceControl1, doorState1: doorState1 ?? this.doorState1, - isOpen: isOpen ?? this.isOpen, + // isOpen: isOpen ?? this.isOpen, delay: delay ?? this.delay, schedules: schedules ?? this.schedules, // Copy schedules ); @@ -118,6 +118,6 @@ class GarageDoorStatusModel { @override String toString() { - return 'GarageDoorStatusModel(uuid: $uuid, switch1: $switch1, countdown1: $countdown1, doorContactState: $doorContactState, trTimeCon: $trTimeCon, countdownAlarm: $countdownAlarm, doorControl1: $doorControl1, voiceControl1: $voiceControl1, doorState1: $doorState1, isOpen: $isOpen, delay: $delay, schedules: $schedules)'; + return 'GarageDoorStatusModel(uuid: $uuid, switch1: $switch1, countdown1: $countdown1, doorContactState: $doorContactState, trTimeCon: $trTimeCon, countdownAlarm: $countdownAlarm, doorControl1: $doorControl1, voiceControl1: $voiceControl1, doorState1: $doorState1, delay: $delay, schedules: $schedules)'; } } 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 8e6a62f0..8e6201f7 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 @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/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/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/shared/device_controls_container.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -22,22 +20,19 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Garage Door Control')), - body: BlocProvider( - create: (context) => GarageDoorBloc(deviceId: deviceId)..add(GarageDoorInitialEvent(deviceId)), - child: BlocBuilder( - builder: (context, state) { - if (state is GarageDoorLoadingState || state is GarageDoorLoadingNewState) { - return const Center(child: CircularProgressIndicator()); - } else if (state is GarageDoorLoadedState) { - return _buildControlView(context, state.status); - } else if (state is GarageDoorErrorState) { - return Center(child: Text('Error: ${state.message}')); - } - return const Center(child: Text('Unknown state')); - }, - ), + return BlocProvider( + create: (context) => GarageDoorBloc(deviceId: deviceId)..add(GarageDoorInitialEvent(deviceId)), + child: BlocBuilder( + builder: (context, state) { + if (state is GarageDoorLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is GarageDoorLoadedState) { + return _buildControlView(context, state.status); + } else if (state is GarageDoorErrorState) { + return Center(child: Text('Error: ${state.message}')); + } + return const Center(child: Text('Unknown state')); + }, ), ); } @@ -50,6 +45,7 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout return GridView( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.symmetric(horizontal: 50), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: isLarge || isExtraLarge ? 3 @@ -57,25 +53,29 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout ? 2 : 1, childAspectRatio: 1.5, + mainAxisExtent: 170, crossAxisSpacing: 12, mainAxisSpacing: 12, ), children: [ IconNameStatusContainer( - isFullIcon: true, - name: status.doorContactState ? 'Open' : 'Close', - icon: Assets.openCloseDoor, + isFullIcon: false, + name: status.switch1 ? 'Opened' : 'Closed', + icon: status.switch1 ? Assets.openedDoor : Assets.closedDoor, onTap: () { context.read().add( - ToggleGarageDoorEvent(deviceId: status.uuid, isOpen: !status.isOpen, code: 'switch_1'), + ToggleGarageDoorEvent(deviceId: status.uuid, isOpen: !status.switch1, code: 'switch_1'), ); }, - status: status.doorContactState, - textColor: status.doorContactState ? ColorsManager.red : ColorsManager.blackColor, - paddingAmount: 8, + status: status.switch1, + textColor: ColorsManager.blackColor, + //paddingAmount: 6, ), - GestureDetector( + IconNameStatusContainer( onTap: () { + context.read().add( + FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1'), + ); showDialog( context: context, builder: (ctx) => BlocProvider.value( @@ -83,43 +83,18 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout child: BuildGarageDoorScheduleView(status: status), )); }, - child: DeviceControlsContainer( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 60, - height: 60, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: ColorsManager.whiteColors, - ), - padding: const EdgeInsets.all(12), - child: ClipOval( - child: SvgPicture.asset( - Assets.scheduling, - fit: BoxFit.fill, - ), - ), - ), - const SizedBox(height: 8), - Text( - 'Scheduling', - textAlign: TextAlign.center, - style: context.textTheme.titleMedium!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.blackColor, - ), - ), - ], - ), - ), + name: 'Scheduling', + icon: Assets.acSchedule, + status: false, + textColor: ColorsManager.blackColor, + isFullIcon: false, + //paddingAmount: 15, ), ToggleWidget( label: '', labelWidget: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.end, children: [ IconButton( onPressed: () { @@ -168,9 +143,35 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout value: false, code: 'switch_1', deviceId: status.uuid, - icon: Assets.acSchedule, + icon: Assets.doorDelay, onChange: (value) {}, ), + IconNameStatusContainer( + isFullIcon: false, + name: 'Records', + icon: Assets.records, + onTap: () { + // context.read().add( + // (deviceId: status.uuid, isOpen: !status.isOpen, code: 'switch_1'), + // ); + }, + status: false, + textColor: ColorsManager.blackColor, + //paddingAmount: 6, + ), + IconNameStatusContainer( + isFullIcon: false, + name: 'Preferences', + icon: Assets.preferences, + onTap: () { + // context.read().add( + // (deviceId: status.uuid, isOpen: !status.isOpen, code: 'switch_1'), + // ); + }, + status: false, + textColor: ColorsManager.blackColor, + // paddingAmount: 6, + ), ], ); } diff --git a/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart b/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart index 4bd54aa8..9bd75094 100644 --- a/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart +++ b/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart @@ -52,7 +52,7 @@ class ScheduleGarageTableWidget extends StatelessWidget { if (state is ScheduleGarageLoadingState) { return const SizedBox(height: 200, child: Center(child: CircularProgressIndicator())); } - if (state is GarageDoorLoadedState && state.schedules?.isEmpty == true) { + if (state is GarageDoorLoadedState && state.status.schedules == null) { return _buildEmptyState(context); } else if (state is GarageDoorLoadedState) { return Container( @@ -110,9 +110,9 @@ class ScheduleGarageTableWidget extends StatelessWidget { border: TableBorder.all(color: ColorsManager.graysColor), defaultVerticalAlignment: TableCellVerticalAlignment.middle, children: [ - if (state.schedules != null) - for (int i = 0; i < state.schedules!.length; i++) - _buildScheduleRow(state.schedules![i], i, context, state), + if (state.status.schedules != null) + for (int i = 0; i < state.status.schedules!.length; i++) + _buildScheduleRow(state.status.schedules![i], i, context, state), ], ), ), diff --git a/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart b/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart index f1307d5f..b30c3596 100644 --- a/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart +++ b/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart @@ -3,10 +3,10 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; -class ScheduleModeButtons extends StatelessWidget { +class ScheduleGarageModeButtons extends StatelessWidget { final VoidCallback onSave; - const ScheduleModeButtons({ + const ScheduleGarageModeButtons({ super.key, required this.onSave, }); diff --git a/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart b/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart index be11203f..8303f4b3 100644 --- a/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart +++ b/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart @@ -6,7 +6,7 @@ import 'package:syncrow_web/pages/device_managment/garage_door/helper/garage_doo 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_selector.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart'; class BuildGarageDoorScheduleView extends StatefulWidget { const BuildGarageDoorScheduleView({super.key, required this.status}); @@ -43,8 +43,6 @@ class _BuildScheduleViewState extends State { children: [ const ScheduleGarageHeader(), const SizedBox(height: 20), - ScheduleGarageDoorModeSelector(state: state), - const SizedBox(height: 20), ScheduleGarageManagementUI( state: state, onAddSchedule: () { @@ -52,20 +50,32 @@ class _BuildScheduleViewState extends State { schedule: null, index: null, isEdit: false); }, ), + const SizedBox(height: 20), + ScheduleGarageModeButtons( + onSave: () { + Navigator.pop(context); + }, + ), ], ); } - if (state is GarageDoorLoadingState) { - return const SizedBox( + if (state is ScheduleGarageLoadingState) { + return SizedBox( height: 200, child: Column( mainAxisSize: MainAxisSize.min, children: [ - ScheduleGarageHeader(), - SizedBox( + const ScheduleGarageHeader(), + const SizedBox( + height: 50, + ), + const Center(child: CircularProgressIndicator()), + const SizedBox( height: 20, ), - Center(child: CircularProgressIndicator()), + ScheduleGarageModeButtons( + onSave: () {}, + ), ], )); } diff --git a/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart b/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart index 5785a799..fe03c74a 100644 --- a/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart +++ b/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart @@ -14,8 +14,7 @@ import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class MainDoorSensorControlView extends StatelessWidget - with HelperResponsiveLayout { +class MainDoorSensorControlView extends StatelessWidget with HelperResponsiveLayout { const MainDoorSensorControlView({super.key, required this.device}); final AllDevicesModel device; @@ -23,12 +22,10 @@ class MainDoorSensorControlView extends StatelessWidget @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => MainDoorSensorBloc() - ..add(MainDoorSensorFetchDeviceEvent(device.uuid!)), + create: (context) => MainDoorSensorBloc()..add(MainDoorSensorFetchDeviceEvent(device.uuid!)), child: BlocBuilder( builder: (context, state) { - if (state is MainDoorSensorLoadingState || - state is MainDoorSensorReportsLoadingState) { + if (state is MainDoorSensorLoadingState || state is MainDoorSensorReportsLoadingState) { return const Center(child: CircularProgressIndicator()); } else if (state is MainDoorSensorDeviceStatusLoaded) { return _buildStatusControls(context, state.status); @@ -37,15 +34,12 @@ class MainDoorSensorControlView extends StatelessWidget report: state.deviceReport, onRowTap: (index) {}, onClose: () { - context - .read() - .add(MainDoorSensorFetchDeviceEvent(device.uuid!)); + context.read().add(MainDoorSensorFetchDeviceEvent(device.uuid!)); }, hideValueShowDescription: true, mainDoorSensor: true, ); - } else if (state is MainDoorSensorFailedState || - state is MainDoorSensorBatchFailedState) { + } else if (state is MainDoorSensorFailedState || state is MainDoorSensorBatchFailedState) { return const Center(child: Text('Error fetching status')); } else { return const Center(child: CircularProgressIndicator()); @@ -54,8 +48,7 @@ class MainDoorSensorControlView extends StatelessWidget )); } - Widget _buildStatusControls( - BuildContext context, MainDoorSensorStatusModel status) { + Widget _buildStatusControls(BuildContext context, MainDoorSensorStatusModel status) { final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); @@ -80,9 +73,7 @@ class MainDoorSensorControlView extends StatelessWidget icon: Assets.openCloseDoor, onTap: () {}, status: status.doorContactState, - textColor: status.doorContactState - ? ColorsManager.red - : ColorsManager.blackColor, + textColor: status.doorContactState ? ColorsManager.red : ColorsManager.blackColor, paddingAmount: 8, ), IconNameStatusContainer( @@ -90,9 +81,7 @@ class MainDoorSensorControlView extends StatelessWidget name: 'Open/Close\nRecord', icon: Assets.openCloseRecords, onTap: () { - final from = DateTime.now() - .subtract(const Duration(days: 30)) - .millisecondsSinceEpoch; + final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch; final to = DateTime.now().millisecondsSinceEpoch; context.read().add( MainDoorSensorReportsEvent( @@ -161,22 +150,19 @@ class IconNameStatusContainer extends StatelessWidget { ), ) else - Container( - width: 60, + ClipOval( + child: Container( height: 60, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: ColorsManager.whiteColors, + width: 60, + padding: EdgeInsets.all(paddingAmount ?? 8), + color: ColorsManager.whiteColors, + child: SvgPicture.asset( + icon, + width: 35, + height: 35, + fit: BoxFit.contain, ), - //margin: const EdgeInsets.symmetric(horizontal: 4), - padding: EdgeInsets.all(paddingAmount ?? 12), - child: ClipOval( - child: SvgPicture.asset( - icon, - fit: BoxFit.contain, - ), - ), - ), + )), const Spacer(), Padding( padding: const EdgeInsets.symmetric(horizontal: 6), diff --git a/lib/pages/device_managment/shared/device_control_dialog.dart b/lib/pages/device_managment/shared/device_control_dialog.dart index ba37203e..6a45ce18 100644 --- a/lib/pages/device_managment/shared/device_control_dialog.dart +++ b/lib/pages/device_managment/shared/device_control_dialog.dart @@ -1,8 +1,5 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; - import 'package:syncrow_web/pages/device_managment/all_devices/helper/route_controls_based_code.dart'; - import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/format_date_time.dart'; @@ -22,7 +19,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode { ), child: SizedBox( width: 798, - // height: context.screenHeight * 0.7, + //height: context.screenHeight * 0.7, child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(20.0), @@ -113,13 +110,9 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode { ), _buildInfoRow( 'Battery Level:', - device.batteryLevel != null - ? '${device.batteryLevel ?? 0}%' - : "-", + device.batteryLevel != null ? '${device.batteryLevel ?? 0}%' : "-", statusColor: device.batteryLevel != null - ? (device.batteryLevel! < 20 - ? ColorsManager.red - : ColorsManager.green) + ? (device.batteryLevel! < 20 ? ColorsManager.red : ColorsManager.green) : null, ), ], diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index ff1b3c15..b94c48c0 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -150,4 +150,9 @@ class Assets { //assets/icons/preferences.svg static const String preferences = 'assets/icons/preferences.svg'; + + static const String openedDoor = 'assets/icons/opened_door.svg'; + static const String closedDoor = 'assets/icons/closed_door.svg'; + static const String doorDelay = 'assets/icons/door_delay.svg'; + static const String records = 'assets/icons/records.svg'; } From 9733295dcac81d1e3aad9fbe6c53e48858f66e74 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 7 Oct 2024 00:45:34 +0300 Subject: [PATCH 3/8] push garage door delay --- .../garage_door/bloc/garage_door_bloc.dart | 92 +++++++++++-------- .../garage_door/bloc/garage_door_event.dart | 8 +- .../view/garage_door_control_view.dart | 29 ++++-- 3 files changed, 78 insertions(+), 51 deletions(-) diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart index 10ccbca4..bc67b081 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart @@ -19,7 +19,7 @@ class GarageDoorBloc extends Bloc { GarageDoorBloc({required this.deviceId}) : super(GarageDoorInitialState()) { on(_fetchGarageDoorStatus); - on(_toggleGarageDoor); + on(_garageDoorControlEvent); on(_addSchedule); on(_updateSchedule); on(_deleteSchedule); @@ -45,23 +45,6 @@ class GarageDoorBloc extends Bloc { } } - void _toggleGarageDoor(ToggleGarageDoorEvent event, Emitter emit) async { - final oldValue = deviceStatus.switch1; - _updateLocalValue('switch_1', event.isOpen); - emit(GarageDoorLoadedState(status: deviceStatus)); - final success = await _runDeBouncer( - deviceId: event.deviceId, - code: 'switch_1', - value: event.isOpen, - oldValue: oldValue, - emit: emit, - isBatch: false, - ); - if (!success) { - _revertValue('switch_1', oldValue, emit); - } - } - Future _addSchedule(AddGarageDoorScheduleEvent event, Emitter emit) async { try { ScheduleEntry newSchedule = ScheduleEntry( @@ -146,26 +129,6 @@ class GarageDoorBloc extends Bloc { } } - void _increaseDelay(IncreaseGarageDoorDelayEvent event, Emitter emit) async { - try { - deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay + Duration(minutes: 10)); - emit(GarageDoorLoadedState(status: deviceStatus)); - } catch (e) { - emit(GarageDoorErrorState(message: e.toString())); - } - } - - void _decreaseDelay(DecreaseGarageDoorDelayEvent event, Emitter emit) async { - try { - if (deviceStatus.delay.inMinutes > 10) { - deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay - Duration(minutes: 10)); - } - emit(GarageDoorLoadedState(status: deviceStatus)); - } catch (e) { - emit(GarageDoorErrorState(message: e.toString())); - } - } - Future _updateSelectedTime(UpdateSelectedTimeEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { @@ -244,6 +207,49 @@ class GarageDoorBloc extends Bloc { } } + void _increaseDelay(IncreaseGarageDoorDelayEvent event, Emitter emit) async { + // if (deviceStatus.countdown1 != 0) { + try { + deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay + Duration(minutes: 10)); + emit(GarageDoorLoadedState(status: deviceStatus)); + add(GarageDoorControlEvent(deviceId: event.deviceId, value: deviceStatus.delay.inSeconds, code: 'countdown_1')); + } catch (e) { + emit(GarageDoorErrorState(message: e.toString())); + } + // } + } + + void _decreaseDelay(DecreaseGarageDoorDelayEvent event, Emitter emit) async { + // if (deviceStatus.countdown1 != 0) { + try { + if (deviceStatus.delay.inMinutes > 10) { + deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay - Duration(minutes: 10)); + } + emit(GarageDoorLoadedState(status: deviceStatus)); + add(GarageDoorControlEvent(deviceId: event.deviceId, value: deviceStatus.delay.inSeconds, code: 'countdown_1')); + } catch (e) { + emit(GarageDoorErrorState(message: e.toString())); + } + //} + } + + void _garageDoorControlEvent(GarageDoorControlEvent event, Emitter emit) async { + final oldValue = event.code == 'countdown_1' ? deviceStatus.countdown1 : deviceStatus.switch1; + _updateLocalValue(event.code, event.value); + emit(GarageDoorLoadedState(status: deviceStatus)); + final success = await _runDeBouncer( + deviceId: event.deviceId, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + isBatch: false, + ); + if (!success) { + _revertValue(event.code, oldValue, emit); + } + } + void _revertValue(String code, dynamic oldValue, Emitter emit) { switch (code) { case 'switch_1': @@ -251,6 +257,11 @@ class GarageDoorBloc extends Bloc { deviceStatus = deviceStatus.copyWith(switch1: oldValue); } break; + case 'countdown_1': + if (oldValue is int) { + deviceStatus = deviceStatus.copyWith(countdown1: oldValue, delay: Duration(seconds: oldValue)); + } + break; // Add other cases if needed default: break; @@ -268,6 +279,11 @@ class GarageDoorBloc extends Bloc { deviceStatus = deviceStatus.copyWith(switch1: value); } break; + case 'countdown_1': + if (value is int) { + deviceStatus = deviceStatus.copyWith(countdown1: value, delay: Duration(seconds: value)); + } + break; // Add other cases if needed default: break; diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart index 754ae140..ee07713a 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart @@ -19,15 +19,15 @@ class GarageDoorInitialEvent extends GarageDoorEvent { List get props => [deviceId]; } -class ToggleGarageDoorEvent extends GarageDoorEvent { +class GarageDoorControlEvent extends GarageDoorEvent { final String deviceId; - final bool isOpen; + final dynamic value; final String code; - const ToggleGarageDoorEvent({required this.deviceId, required this.isOpen, required this.code}); + const GarageDoorControlEvent({required this.deviceId, required this.value, required this.code}); @override - List get props => [deviceId, isOpen]; + List get props => [deviceId, value]; } class AddGarageDoorScheduleEvent extends GarageDoorEvent { 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 8e6201f7..41e6baee 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 @@ -64,7 +64,7 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout icon: status.switch1 ? Assets.openedDoor : Assets.closedDoor, onTap: () { context.read().add( - ToggleGarageDoorEvent(deviceId: status.uuid, isOpen: !status.switch1, code: 'switch_1'), + GarageDoorControlEvent(deviceId: status.uuid, value: !status.switch1, code: 'switch_1'), ); }, status: status.switch1, @@ -94,20 +94,23 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout label: '', labelWidget: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, children: [ IconButton( onPressed: () { - context.read().add(DecreaseGarageDoorDelayEvent(deviceId: deviceId)); + // if (status.countdown1 != 0) { + context.read().add(DecreaseGarageDoorDelayEvent(deviceId: status.uuid)); + // } }, icon: const Icon( Icons.remove, size: 28, color: ColorsManager.greyColor, ), + padding: EdgeInsets.zero, ), Text( - '${status.delay.inHours.toString().padLeft(2, '0')}', + status.delay.inHours.toString().padLeft(2, '0'), style: context.textTheme.titleLarge!.copyWith( color: ColorsManager.dialogBlueTitle, fontWeight: FontWeight.bold, @@ -118,7 +121,7 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor), ), Text( - '${(status.delay.inMinutes % 60).toString().padLeft(2, '0')}', + (status.delay.inMinutes % 60).toString().padLeft(2, '0'), style: context.textTheme.titleLarge!.copyWith( color: ColorsManager.dialogBlueTitle, fontWeight: FontWeight.bold, @@ -130,21 +133,29 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout ), IconButton( onPressed: () { - context.read().add(IncreaseGarageDoorDelayEvent(deviceId: deviceId)); + // if (status.countdown1 != 0) { + context.read().add(IncreaseGarageDoorDelayEvent(deviceId: status.uuid)); + // } }, icon: const Icon( Icons.add, size: 28, color: ColorsManager.greyColor, ), + padding: EdgeInsets.zero, ), ], ), - value: false, - code: 'switch_1', + value: status.countdown1 != 0 ? true : false, + code: 'countdown_1', deviceId: status.uuid, icon: Assets.doorDelay, - onChange: (value) {}, + onChange: (value) { + context.read().add( + GarageDoorControlEvent( + deviceId: status.uuid, value: value ? status.delay.inSeconds : 0, code: 'countdown_1'), + ); + }, ), IconNameStatusContainer( isFullIcon: false, From 00277742d0750444f9c8bfab65eb0965ccb8d1af Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 7 Oct 2024 01:12:35 +0300 Subject: [PATCH 4/8] push garage door records --- .../garage_door/bloc/garage_door_bloc.dart | 10 ++++- .../garage_door/bloc/garage_door_event.dart | 7 +++- .../view/garage_door_control_view.dart | 21 +++++++---- .../shared/table/report_table.dart | 37 ++++++++++--------- 4 files changed, 47 insertions(+), 28 deletions(-) diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart index bc67b081..f640a50d 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart @@ -32,6 +32,7 @@ class GarageDoorBloc extends Bloc { on(_updateSelectedDay); on(_updateFunctionOn); on(_initializeAddSchedule); + on(_backToGridView); } void _fetchGarageDoorStatus(GarageDoorInitialEvent event, Emitter emit) async { @@ -167,13 +168,20 @@ class GarageDoorBloc extends Bloc { Future _fetchRecords(FetchGarageDoorRecordsEvent event, Emitter emit) async { emit(GarageDoorReportsLoadingState()); try { - final DeviceReport records = await DevicesManagementApi.getDeviceReports(event.deviceId, 'switch_1'); + final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch; + final to = DateTime.now().millisecondsSinceEpoch; + final DeviceReport records = + await DevicesManagementApi.getDeviceReportsByDate(event.deviceId, 'switch_1', from.toString(), to.toString()); emit(GarageDoorReportsState(deviceReport: records)); } catch (e) { emit(GarageDoorReportsFailedState(error: e.toString())); } } + void _backToGridView(BackToGarageDoorGridViewEvent event, Emitter emit) { + emit(GarageDoorLoadedState(status: deviceStatus)); + } + void _handleUpdate(GarageDoorUpdatedEvent event, Emitter emit) { emit(GarageDoorLoadedState(status: deviceStatus)); } diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart index ee07713a..20758077 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart @@ -102,13 +102,16 @@ class DecreaseGarageDoorDelayEvent extends GarageDoorEvent { class FetchGarageDoorRecordsEvent extends GarageDoorEvent { final String deviceId; + final String code; - const FetchGarageDoorRecordsEvent({required this.deviceId}); + const FetchGarageDoorRecordsEvent({required this.deviceId, required this.code}); @override - List get props => [deviceId]; + List get props => [deviceId, code]; } +class BackToGarageDoorGridViewEvent extends GarageDoorEvent {} + class GarageDoorUpdatedEvent extends GarageDoorEvent {} class UpdateSelectedTimeEvent extends GarageDoorEvent { 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 41e6baee..1ad3c756 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,6 +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/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/shared/table/report_table.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -26,6 +27,16 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout builder: (context, state) { if (state is GarageDoorLoadingState) { return const Center(child: CircularProgressIndicator()); + } else if (state is GarageDoorReportsState) { + return ReportsTable( + report: state.deviceReport, + hideValueShowDescription: true, + garageDoorSensor: true, + onRowTap: (index) {}, + onClose: () { + context.read().add(BackToGarageDoorGridViewEvent()); + }, + ); } else if (state is GarageDoorLoadedState) { return _buildControlView(context, state.status); } else if (state is GarageDoorErrorState) { @@ -162,9 +173,7 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout name: 'Records', icon: Assets.records, onTap: () { - // context.read().add( - // (deviceId: status.uuid, isOpen: !status.isOpen, code: 'switch_1'), - // ); + context.read().add(FetchGarageDoorRecordsEvent(code: 'switch_1', deviceId: status.uuid)); }, status: false, textColor: ColorsManager.blackColor, @@ -174,11 +183,7 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout isFullIcon: false, name: 'Preferences', icon: Assets.preferences, - onTap: () { - // context.read().add( - // (deviceId: status.uuid, isOpen: !status.isOpen, code: 'switch_1'), - // ); - }, + onTap: () {}, status: false, textColor: ColorsManager.blackColor, // paddingAmount: 6, diff --git a/lib/pages/device_managment/shared/table/report_table.dart b/lib/pages/device_managment/shared/table/report_table.dart index 7dda10e1..527ae783 100644 --- a/lib/pages/device_managment/shared/table/report_table.dart +++ b/lib/pages/device_managment/shared/table/report_table.dart @@ -13,6 +13,7 @@ class ReportsTable extends StatelessWidget { final VoidCallback onClose; bool? hideValueShowDescription; bool? mainDoorSensor; + bool? garageDoorSensor; ReportsTable({ super.key, @@ -23,6 +24,7 @@ class ReportsTable extends StatelessWidget { this.thirdColumnDescription, this.hideValueShowDescription, this.mainDoorSensor, + this.garageDoorSensor, }); @override @@ -53,30 +55,31 @@ class ReportsTable extends StatelessWidget { DeviceEvent data = entry.value; // Parse eventTime into Date and Time - DateTime eventDateTime = - DateTime.fromMillisecondsSinceEpoch(data.eventTime!); + DateTime eventDateTime = DateTime.fromMillisecondsSinceEpoch(data.eventTime!); String date = DateFormat('dd/MM/yyyy').format(eventDateTime); String time = DateFormat('HH:mm').format(eventDateTime); + String value; + if (hideValueShowDescription == true) { + if (mainDoorSensor != null && mainDoorSensor == true) { + value = data.value == 'true' ? 'Open' : 'Close'; + } else if (garageDoorSensor != null && garageDoorSensor == true) { + value = data.value == 'true' ? 'Opened' : 'Closed'; + } else { + value = '${data.value!} ${thirdColumnDescription ?? ''}'; + } + } else { + value = '${data.value!} ${thirdColumnDescription ?? ''}'; + } + return TableRow( children: [ TableCellWidget(value: date), TableCellWidget(value: time), - hideValueShowDescription == true - ? TableCellWidget( - value: (mainDoorSensor != null && - mainDoorSensor == true) - ? data.value == 'true' - ? 'Open' - : 'Close' - : thirdColumnDescription ?? '', - onTap: () => onRowTap(index), - ) - : TableCellWidget( - value: - '${data.value!} ${thirdColumnDescription ?? ''}', - onTap: () => onRowTap(index), - ), + TableCellWidget( + value: value, + onTap: () => onRowTap(index), + ), ], ); }), From ebde81b64de6067b1b49581088ebdf431bb4fcb6 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 7 Oct 2024 07:43:07 +0300 Subject: [PATCH 5/8] working on preferences --- .../helper/garage_door_helper.dart | 129 ++++++++++++++++-- .../view/garage_door_control_view.dart | 54 +++++--- .../opening_clsoing_time_dialog_body.dart | 13 ++ .../widgets/schedule__garage_table.dart | 48 +++++-- .../widgets/schedule_garage_view.dart | 8 +- .../widgets/time_out_alarm_dialog_body.dart | 13 ++ .../shared/toggle_widget.dart | 2 + lib/utils/extension/build_context_x.dart | 112 +++++++++++++++ 8 files changed, 330 insertions(+), 49 deletions(-) create mode 100644 lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart create mode 100644 lib/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart 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 e939ad9e..55957b53 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,11 +4,15 @@ 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/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'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; -class ScheduleGarageDoorDialogHelper { +class GarageDoorDialogHelper { static void showAddGarageDoorScheduleDialog(BuildContext context, {ScheduleModel? schedule, int? index, bool? isEdit}) { final bloc = context.read(); @@ -77,7 +81,8 @@ class ScheduleGarageDoorDialogHelper { : () async { TimeOfDay? time = await showTimePicker( context: context, - initialTime: state.selectedTime ?? TimeOfDay.now(), + initialTime: + state.selectedTime ?? TimeOfDay.now(), builder: (context, child) { return Theme( data: Theme.of(context).copyWith( @@ -97,7 +102,9 @@ class ScheduleGarageDoorDialogHelper { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - state.selectedTime == null ? 'Time' : state.selectedTime!.format(context), + state.selectedTime == null + ? 'Time' + : state.selectedTime!.format(context), style: context.textTheme.bodySmall!.copyWith( color: ColorsManager.grayColor, ), @@ -112,7 +119,8 @@ class ScheduleGarageDoorDialogHelper { ), ), const SizedBox(height: 16), - _buildDayCheckboxes(context, state.selectedDays, isEdit: isEdit), + _buildDayCheckboxes(context, state.selectedDays, + isEdit: isEdit), const SizedBox(height: 16), _buildFunctionSwitch(context, state.functionOn, isEdit), ], @@ -191,7 +199,9 @@ class ScheduleGarageDoorDialogHelper { return daysBoolean; } - static Widget _buildDayCheckboxes(BuildContext context, List selectedDays, {bool? isEdit}) { + static Widget _buildDayCheckboxes( + BuildContext context, List selectedDays, + {bool? isEdit}) { final dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; return Row( @@ -203,7 +213,9 @@ class ScheduleGarageDoorDialogHelper { onChanged: isEdit == true ? null : (bool? value) { - context.read().add(UpdateSelectedDayEvent(index, value!)); + context + .read() + .add(UpdateSelectedDayEvent(index, value!)); }, ), Text(dayLabels[index]), @@ -213,12 +225,14 @@ class ScheduleGarageDoorDialogHelper { ); } - static Widget _buildFunctionSwitch(BuildContext context, bool isOn, bool? isEdit) { + static Widget _buildFunctionSwitch( + BuildContext context, bool isOn, bool? isEdit) { return Row( children: [ Text( 'Function:', - style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.grayColor), + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.grayColor), ), const SizedBox(width: 10), Radio( @@ -228,7 +242,9 @@ class ScheduleGarageDoorDialogHelper { if (isEdit == true) { return; } else { - context.read().add(const UpdateFunctionOnEvent(functionOn: true)); + context + .read() + .add(const UpdateFunctionOnEvent(functionOn: true)); } }, ), @@ -241,7 +257,9 @@ class ScheduleGarageDoorDialogHelper { if (isEdit == true) { return; } else { - context.read().add(const UpdateFunctionOnEvent(functionOn: false)); + context + .read() + .add(const UpdateFunctionOnEvent(functionOn: false)); } }, ), @@ -249,4 +267,95 @@ class ScheduleGarageDoorDialogHelper { ], ); } + + static void showPreferencesDialog(BuildContext context) { + final bloc = context.read(); + + showDialog( + context: context, + builder: (ctx) { + return BlocProvider.value( + value: bloc, + child: BlocBuilder( + builder: (context, state) { + if (state is GarageDoorLoadedState) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox(), + Text( + 'Preferences', + style: context.textTheme.titleLarge!.copyWith( + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(), + ], + ), + const SizedBox(height: 24), + Row( + children: [ + const SizedBox(width: 24), + SizedBox( + width: 190, + height: 150, + child: GestureDetector( + onTap: () { + context.customAlertDialog( + alertBody: TimeOutAlarmDialogBody(), + title: 'Time Out Alarm', + onConfirm: () {}); + }, + child: ToggleWidget( + icon: "-1", + value: bloc.deviceStatus.countdownAlarm > 0, + code: 'countdown_alarm', + deviceId: bloc.deviceId, + label: 'Alarm when door is open', + onChange: (value) {}), + ), + ), + const SizedBox( + width: 20, + ), + SizedBox( + width: 190, + height: 150, + child: GestureDetector( + onTap: () { + context.customAlertDialog( + alertBody: OpeningClosingTimeDialogBody(), + title: 'Opening and Closing Time', + onConfirm: () {}); + }, + child: PresenceDisplayValue( + value: bloc.deviceStatus.trTimeCon.toString(), + postfix: 'sec', + description: 'Opening & Closing Time', + ), + ), + ), + const SizedBox(width: 24), + ], + ) + ], + ), + ); + } + return const SizedBox(); + }, + ), + ); + }, + ); + } } 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 1ad3c756..e3c44405 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 @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.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/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/shared/table/report_table.dart'; @@ -14,7 +15,8 @@ import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_la import '../../main_door_sensor/view/main_door_control_view.dart'; -class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout { +class GarageDoorControlView extends StatelessWidget + with HelperResponsiveLayout { final String deviceId; const GarageDoorControlView({required this.deviceId, super.key}); @@ -22,7 +24,8 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => GarageDoorBloc(deviceId: deviceId)..add(GarageDoorInitialEvent(deviceId)), + create: (context) => GarageDoorBloc(deviceId: deviceId) + ..add(GarageDoorInitialEvent(deviceId)), child: BlocBuilder( builder: (context, state) { if (state is GarageDoorLoadingState) { @@ -34,7 +37,9 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout garageDoorSensor: true, onRowTap: (index) {}, onClose: () { - context.read().add(BackToGarageDoorGridViewEvent()); + context + .read() + .add(BackToGarageDoorGridViewEvent()); }, ); } else if (state is GarageDoorLoadedState) { @@ -64,7 +69,7 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout ? 2 : 1, childAspectRatio: 1.5, - mainAxisExtent: 170, + mainAxisExtent: 140, crossAxisSpacing: 12, mainAxisSpacing: 12, ), @@ -75,17 +80,20 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout icon: status.switch1 ? Assets.openedDoor : Assets.closedDoor, onTap: () { context.read().add( - GarageDoorControlEvent(deviceId: status.uuid, value: !status.switch1, code: 'switch_1'), + GarageDoorControlEvent( + deviceId: status.uuid, + value: !status.switch1, + code: 'switch_1'), ); }, status: status.switch1, textColor: ColorsManager.blackColor, - //paddingAmount: 6, ), IconNameStatusContainer( onTap: () { context.read().add( - FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1'), + FetchGarageDoorSchedulesEvent( + deviceId: deviceId, category: 'switch_1'), ); showDialog( context: context, @@ -99,7 +107,6 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout status: false, textColor: ColorsManager.blackColor, isFullIcon: false, - //paddingAmount: 15, ), ToggleWidget( label: '', @@ -109,9 +116,9 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout children: [ IconButton( onPressed: () { - // if (status.countdown1 != 0) { - context.read().add(DecreaseGarageDoorDelayEvent(deviceId: status.uuid)); - // } + context + .read() + .add(DecreaseGarageDoorDelayEvent(deviceId: status.uuid)); }, icon: const Icon( Icons.remove, @@ -129,7 +136,8 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout ), Text( 'h', - style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor), + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blackColor), ), Text( (status.delay.inMinutes % 60).toString().padLeft(2, '0'), @@ -140,13 +148,14 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout ), Text( 'm', - style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor), + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blackColor), ), IconButton( onPressed: () { - // if (status.countdown1 != 0) { - context.read().add(IncreaseGarageDoorDelayEvent(deviceId: status.uuid)); - // } + context + .read() + .add(IncreaseGarageDoorDelayEvent(deviceId: status.uuid)); }, icon: const Icon( Icons.add, @@ -164,7 +173,9 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout onChange: (value) { context.read().add( GarageDoorControlEvent( - deviceId: status.uuid, value: value ? status.delay.inSeconds : 0, code: 'countdown_1'), + deviceId: status.uuid, + value: value ? status.delay.inSeconds : 0, + code: 'countdown_1'), ); }, ), @@ -173,20 +184,21 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout name: 'Records', icon: Assets.records, onTap: () { - context.read().add(FetchGarageDoorRecordsEvent(code: 'switch_1', deviceId: status.uuid)); + context.read().add(FetchGarageDoorRecordsEvent( + code: 'switch_1', deviceId: status.uuid)); }, status: false, textColor: ColorsManager.blackColor, - //paddingAmount: 6, ), IconNameStatusContainer( isFullIcon: false, name: 'Preferences', icon: Assets.preferences, - onTap: () {}, + onTap: () { + GarageDoorDialogHelper.showPreferencesDialog(context); + }, status: false, textColor: ColorsManager.blackColor, - // paddingAmount: 6, ), ], ); diff --git a/lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart b/lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart new file mode 100644 index 00000000..ea76d901 --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class OpeningClosingTimeDialogBody extends StatelessWidget { + const OpeningClosingTimeDialogBody({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + width: 350, + child: Text('asdasd'), + ); + } +} diff --git a/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart b/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart index 9bd75094..48ebbcad 100644 --- a/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart +++ b/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart @@ -26,7 +26,8 @@ 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.only( + topLeft: Radius.circular(20), topRight: Radius.circular(20)), ), children: [ TableRow( @@ -50,17 +51,21 @@ 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 == null) { + if (state is GarageDoorLoadedState && + state.status.schedules == null) { 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.only( + bottomLeft: Radius.circular(20), + bottomRight: Radius.circular(20)), ), child: _buildTableBody(state, context)); } @@ -78,7 +83,8 @@ 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.only( + bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)), ), child: Center( child: Column( @@ -112,7 +118,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 +141,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 +160,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 +169,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 +181,24 @@ class ScheduleGarageTableWidget extends StatelessWidget { TextButton( style: TextButton.styleFrom(padding: EdgeInsets.zero), onPressed: () { - ScheduleGarageDoorDialogHelper.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 +206,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_view.dart b/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart index 8303f4b3..107c8e0a 100644 --- a/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart +++ b/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart @@ -34,7 +34,8 @@ class _BuildScheduleViewState extends State { width: 700, child: SingleChildScrollView( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 40.0, vertical: 20), + padding: + const EdgeInsets.symmetric(horizontal: 40.0, vertical: 20), child: BlocBuilder( builder: (context, state) { if (state is GarageDoorLoadedState) { @@ -46,8 +47,9 @@ class _BuildScheduleViewState extends State { ScheduleGarageManagementUI( state: state, onAddSchedule: () { - ScheduleGarageDoorDialogHelper.showAddGarageDoorScheduleDialog(context, - schedule: null, index: null, isEdit: false); + GarageDoorDialogHelper + .showAddGarageDoorScheduleDialog(context, + schedule: null, index: null, isEdit: false); }, ), const SizedBox(height: 20), diff --git a/lib/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart b/lib/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart new file mode 100644 index 00000000..948f8190 --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class TimeOutAlarmDialogBody extends StatelessWidget { + const TimeOutAlarmDialogBody({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + width: 350, + child: Text('asdasd'), + ); + } +} diff --git a/lib/pages/device_managment/shared/toggle_widget.dart b/lib/pages/device_managment/shared/toggle_widget.dart index ee76d442..942c1b32 100644 --- a/lib/pages/device_managment/shared/toggle_widget.dart +++ b/lib/pages/device_managment/shared/toggle_widget.dart @@ -13,6 +13,7 @@ class ToggleWidget extends StatelessWidget { final Widget? labelWidget; final Function(dynamic value) onChange; final bool showToggle; + final bool showIcon; const ToggleWidget({ super.key, @@ -24,6 +25,7 @@ class ToggleWidget extends StatelessWidget { this.icon, this.labelWidget, this.showToggle = true, + this.showIcon = true, }); @override diff --git a/lib/utils/extension/build_context_x.dart b/lib/utils/extension/build_context_x.dart index 50bc5972..dbdbb347 100644 --- a/lib/utils/extension/build_context_x.dart +++ b/lib/utils/extension/build_context_x.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; extension BuildContextExt on BuildContext { ThemeData get theme => Theme.of(this); @@ -14,4 +15,115 @@ extension BuildContextExt on BuildContext { double get screenHeight => MediaQuery.of(this).size.height; double get textScale => MediaQuery.textScalerOf(this).scale(1); + + void customAlertDialog({ + required Widget alertBody, + required String title, + required VoidCallback onConfirm, + VoidCallback? onDismiss, + bool? hideConfirmButton, + final double? dialogWidth, + }) { + showDialog( + context: this, + builder: (BuildContext context) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: Container( + width: dialogWidth ?? 360, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + /// header widget + Text( + title, + style: context.textTheme.bodyMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontWeight.bold, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 15, + horizontal: 50, + ), + child: Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + ), + + /// custom body content + Flexible(child: SingleChildScrollView(child: alertBody)), + + /// Footer buttons + Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + hideConfirmButton != true + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + GestureDetector( + onTap: onDismiss ?? + () { + Navigator.pop(context); + }, + child: Center( + child: Text( + 'Cancel', + style: context.textTheme.bodyMedium! + .copyWith(color: ColorsManager.greyColor), + ), + ), + ), + Container( + height: 50, + width: 1, + color: ColorsManager.greyColor, + ), + GestureDetector( + onTap: onConfirm, + child: Center( + child: Text( + 'Confirm', + style: context.textTheme.bodyMedium!.copyWith( + color: + ColorsManager.primaryColorWithOpacity), + ), + ), + ), + ], + ) + : Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: GestureDetector( + onTap: onDismiss ?? + () { + Navigator.pop(context); + }, + child: Center( + child: Text( + 'Cancel', + style: context.textTheme.bodyMedium! + .copyWith(color: ColorsManager.greyColor), + ), + ), + ), + ), + ], + ), + ), + ); + }, + ); + } } From 3748fc1419e7d967cc4f03efcefce08cc9d7e530 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 7 Oct 2024 09:31:33 +0300 Subject: [PATCH 6/8] push garage door preferences --- .../garage_door/bloc/garage_door_bloc.dart | 149 +++++++++++++----- .../garage_door/bloc/garage_door_event.dart | 18 ++- .../helper/garage_door_helper.dart | 60 ++++++- .../opening_clsoing_time_dialog_body.dart | 48 +++++- .../garage_door/widgets/seconds_picker.dart | 52 ++++++ .../widgets/time_out_alarm_dialog_body.dart | 44 +++++- 6 files changed, 317 insertions(+), 54 deletions(-) create mode 100644 lib/pages/device_managment/garage_door/widgets/seconds_picker.dart diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart index f640a50d..04af1e39 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart @@ -33,12 +33,16 @@ class GarageDoorBloc extends Bloc { on(_updateFunctionOn); on(_initializeAddSchedule); on(_backToGridView); + on(_onUpdateCountdownAlarm); + on(_onUpdateTrTimeCon); } - void _fetchGarageDoorStatus(GarageDoorInitialEvent event, Emitter emit) async { + void _fetchGarageDoorStatus( + GarageDoorInitialEvent event, Emitter emit) async { emit(GarageDoorLoadingState()); try { - var response = await DevicesManagementApi().getDeviceStatus(event.deviceId); + var response = + await DevicesManagementApi().getDeviceStatus(event.deviceId); deviceStatus = GarageDoorStatusModel.fromJson(deviceId, response.status); emit(GarageDoorLoadedState(status: deviceStatus)); } catch (e) { @@ -46,7 +50,8 @@ class GarageDoorBloc extends Bloc { } } - Future _addSchedule(AddGarageDoorScheduleEvent event, Emitter emit) async { + Future _addSchedule( + AddGarageDoorScheduleEvent event, Emitter emit) async { try { ScheduleEntry newSchedule = ScheduleEntry( category: event.category, @@ -54,9 +59,11 @@ class GarageDoorBloc extends Bloc { function: Status(code: 'switch_1', value: event.functionOn), days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays), ); - bool success = await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId); + bool success = + await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId); if (success) { - add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1')); + add(FetchGarageDoorSchedulesEvent( + deviceId: deviceId, category: 'switch_1')); } else { emit(GarageDoorLoadedState(status: deviceStatus)); } @@ -65,7 +72,29 @@ class GarageDoorBloc extends Bloc { } } - Future _updateSchedule(UpdateGarageDoorScheduleEvent event, Emitter emit) async { + void _onUpdateCountdownAlarm( + UpdateCountdownAlarmEvent event, Emitter emit) { + final currentState = state; + if (currentState is GarageDoorLoadedState) { + emit(currentState.copyWith( + status: + currentState.status.copyWith(countdownAlarm: event.countdownAlarm), + )); + } + } + + void _onUpdateTrTimeCon( + UpdateTrTimeConEvent event, Emitter emit) { + final currentState = state; + if (currentState is GarageDoorLoadedState) { + emit(currentState.copyWith( + status: currentState.status.copyWith(trTimeCon: event.trTimeCon), + )); + } + } + + Future _updateSchedule(UpdateGarageDoorScheduleEvent event, + Emitter emit) async { try { final updatedSchedules = deviceStatus.schedules?.map((schedule) { if (schedule.scheduleId == event.scheduleId) { @@ -92,12 +121,15 @@ class GarageDoorBloc extends Bloc { } } - Future _deleteSchedule(DeleteGarageDoorScheduleEvent event, Emitter emit) async { + Future _deleteSchedule(DeleteGarageDoorScheduleEvent event, + Emitter emit) async { try { - bool success = await DevicesManagementApi().deleteScheduleRecord(deviceStatus.uuid, event.scheduleId); + bool success = await DevicesManagementApi() + .deleteScheduleRecord(deviceStatus.uuid, event.scheduleId); if (success) { - final updatedSchedules = - deviceStatus.schedules?.where((schedule) => schedule.scheduleId != event.scheduleId).toList(); + final updatedSchedules = deviceStatus.schedules + ?.where((schedule) => schedule.scheduleId != event.scheduleId) + .toList(); deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules); emit(GarageDoorLoadedState(status: deviceStatus)); } else { @@ -108,11 +140,12 @@ class GarageDoorBloc extends Bloc { } } - Future _fetchSchedules(FetchGarageDoorSchedulesEvent event, Emitter emit) async { + Future _fetchSchedules(FetchGarageDoorSchedulesEvent event, + Emitter emit) async { emit(ScheduleGarageLoadingState()); try { - List schedules = - await DevicesManagementApi().getDeviceSchedules(deviceStatus.uuid, event.category); + List schedules = await DevicesManagementApi() + .getDeviceSchedules(deviceStatus.uuid, event.category); deviceStatus = deviceStatus.copyWith(schedules: schedules); emit( GarageDoorLoadedState( @@ -130,30 +163,37 @@ class GarageDoorBloc extends Bloc { } } - Future _updateSelectedTime(UpdateSelectedTimeEvent event, Emitter emit) async { + Future _updateSelectedTime( + UpdateSelectedTimeEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { emit(currentState.copyWith(selectedTime: event.selectedTime)); } } - Future _updateSelectedDay(UpdateSelectedDayEvent event, Emitter emit) async { + Future _updateSelectedDay( + UpdateSelectedDayEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { List updatedDays = List.from(currentState.selectedDays); updatedDays[event.dayIndex] = event.isSelected; - emit(currentState.copyWith(selectedDays: updatedDays, selectedTime: currentState.selectedTime)); + emit(currentState.copyWith( + selectedDays: updatedDays, selectedTime: currentState.selectedTime)); } } - Future _updateFunctionOn(UpdateFunctionOnEvent event, Emitter emit) async { + Future _updateFunctionOn( + UpdateFunctionOnEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { - emit(currentState.copyWith(functionOn: event.functionOn, selectedTime: currentState.selectedTime)); + emit(currentState.copyWith( + functionOn: event.functionOn, + selectedTime: currentState.selectedTime)); } } - Future _initializeAddSchedule(InitializeAddScheduleEvent event, Emitter emit) async { + Future _initializeAddSchedule( + InitializeAddScheduleEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { emit(currentState.copyWith( @@ -165,24 +205,30 @@ class GarageDoorBloc extends Bloc { } } - Future _fetchRecords(FetchGarageDoorRecordsEvent event, Emitter emit) async { + Future _fetchRecords( + FetchGarageDoorRecordsEvent event, Emitter emit) async { emit(GarageDoorReportsLoadingState()); try { - final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch; + final from = DateTime.now() + .subtract(const Duration(days: 30)) + .millisecondsSinceEpoch; final to = DateTime.now().millisecondsSinceEpoch; final DeviceReport records = - await DevicesManagementApi.getDeviceReportsByDate(event.deviceId, 'switch_1', from.toString(), to.toString()); + await DevicesManagementApi.getDeviceReportsByDate( + event.deviceId, 'switch_1', from.toString(), to.toString()); emit(GarageDoorReportsState(deviceReport: records)); } catch (e) { emit(GarageDoorReportsFailedState(error: e.toString())); } } - void _backToGridView(BackToGarageDoorGridViewEvent event, Emitter emit) { + void _backToGridView( + BackToGarageDoorGridViewEvent event, Emitter emit) { emit(GarageDoorLoadedState(status: deviceStatus)); } - void _handleUpdate(GarageDoorUpdatedEvent event, Emitter emit) { + void _handleUpdate( + GarageDoorUpdatedEvent event, Emitter emit) { emit(GarageDoorLoadedState(status: deviceStatus)); } @@ -198,9 +244,11 @@ class GarageDoorBloc extends Bloc { late bool status; await Future.delayed(const Duration(milliseconds: 500)); if (isBatch) { - status = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); + status = await DevicesManagementApi() + .deviceBatchControl(deviceId, code, value); } else { - status = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); + status = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); } if (!status) { @@ -215,34 +263,47 @@ class GarageDoorBloc extends Bloc { } } - void _increaseDelay(IncreaseGarageDoorDelayEvent event, Emitter emit) async { + void _increaseDelay( + IncreaseGarageDoorDelayEvent event, Emitter emit) async { // if (deviceStatus.countdown1 != 0) { try { - deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay + Duration(minutes: 10)); + deviceStatus = deviceStatus.copyWith( + delay: deviceStatus.delay + Duration(minutes: 10)); emit(GarageDoorLoadedState(status: deviceStatus)); - add(GarageDoorControlEvent(deviceId: event.deviceId, value: deviceStatus.delay.inSeconds, code: 'countdown_1')); + add(GarageDoorControlEvent( + deviceId: event.deviceId, + value: deviceStatus.delay.inSeconds, + code: 'countdown_1')); } catch (e) { emit(GarageDoorErrorState(message: e.toString())); } // } } - void _decreaseDelay(DecreaseGarageDoorDelayEvent event, Emitter emit) async { + void _decreaseDelay( + DecreaseGarageDoorDelayEvent event, Emitter emit) async { // if (deviceStatus.countdown1 != 0) { try { if (deviceStatus.delay.inMinutes > 10) { - deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay - Duration(minutes: 10)); + deviceStatus = deviceStatus.copyWith( + delay: deviceStatus.delay - Duration(minutes: 10)); } emit(GarageDoorLoadedState(status: deviceStatus)); - add(GarageDoorControlEvent(deviceId: event.deviceId, value: deviceStatus.delay.inSeconds, code: 'countdown_1')); + add(GarageDoorControlEvent( + deviceId: event.deviceId, + value: deviceStatus.delay.inSeconds, + code: 'countdown_1')); } catch (e) { emit(GarageDoorErrorState(message: e.toString())); } //} } - void _garageDoorControlEvent(GarageDoorControlEvent event, Emitter emit) async { - final oldValue = event.code == 'countdown_1' ? deviceStatus.countdown1 : deviceStatus.switch1; + void _garageDoorControlEvent( + GarageDoorControlEvent event, Emitter emit) async { + final oldValue = event.code == 'countdown_1' + ? deviceStatus.countdown1 + : deviceStatus.switch1; _updateLocalValue(event.code, event.value); emit(GarageDoorLoadedState(status: deviceStatus)); final success = await _runDeBouncer( @@ -258,7 +319,8 @@ class GarageDoorBloc extends Bloc { } } - void _revertValue(String code, dynamic oldValue, Emitter emit) { + void _revertValue( + String code, dynamic oldValue, Emitter emit) { switch (code) { case 'switch_1': if (oldValue is bool) { @@ -267,7 +329,8 @@ class GarageDoorBloc extends Bloc { break; case 'countdown_1': if (oldValue is int) { - deviceStatus = deviceStatus.copyWith(countdown1: oldValue, delay: Duration(seconds: oldValue)); + deviceStatus = deviceStatus.copyWith( + countdown1: oldValue, delay: Duration(seconds: oldValue)); } break; // Add other cases if needed @@ -289,10 +352,20 @@ class GarageDoorBloc extends Bloc { break; case 'countdown_1': if (value is int) { - deviceStatus = deviceStatus.copyWith(countdown1: value, delay: Duration(seconds: value)); + deviceStatus = deviceStatus.copyWith( + countdown1: value, delay: Duration(seconds: value)); + } + break; + case 'countdown_alarm': + if (value is int) { + deviceStatus = deviceStatus.copyWith(countdownAlarm: value); + } + break; + case 'tr_timecon': + if (value is int) { + deviceStatus = deviceStatus.copyWith(trTimeCon: value); } break; - // Add other cases if needed default: break; } diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart index 20758077..515b2a84 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart @@ -24,7 +24,8 @@ class GarageDoorControlEvent extends GarageDoorEvent { final dynamic value; final String code; - const GarageDoorControlEvent({required this.deviceId, required this.value, required this.code}); + const GarageDoorControlEvent( + {required this.deviceId, required this.value, required this.code}); @override List get props => [deviceId, value]; @@ -104,7 +105,8 @@ class FetchGarageDoorRecordsEvent extends GarageDoorEvent { final String deviceId; final String code; - const FetchGarageDoorRecordsEvent({required this.deviceId, required this.code}); + const FetchGarageDoorRecordsEvent( + {required this.deviceId, required this.code}); @override List get props => [deviceId, code]; @@ -166,3 +168,15 @@ class InitializeAddScheduleEvent extends GarageDoorEvent { index, ]; } + +class UpdateCountdownAlarmEvent extends GarageDoorEvent { + final int countdownAlarm; + + const UpdateCountdownAlarmEvent(this.countdownAlarm); +} + +class UpdateTrTimeConEvent extends GarageDoorEvent { + final int trTimeCon; + + const UpdateTrTimeConEvent(this.trTimeCon); +} 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 55957b53..c30f391d 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 @@ -291,6 +291,9 @@ class GarageDoorDialogHelper { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const SizedBox(), + + /// The dialog is closed when the user taps on the close button or when the + /// [GarageDoorBloc] state changes. Text( 'Preferences', style: context.textTheme.titleLarge!.copyWith( @@ -311,13 +314,32 @@ class GarageDoorDialogHelper { child: GestureDetector( onTap: () { context.customAlertDialog( - alertBody: TimeOutAlarmDialogBody(), + alertBody: TimeOutAlarmDialogBody(bloc), title: 'Time Out Alarm', - onConfirm: () {}); + onConfirm: () { + final updatedState = + context.read().state; + if (updatedState + is GarageDoorLoadedState) { + context.read().add( + GarageDoorControlEvent( + deviceId: + updatedState.status.uuid, + code: 'countdown_alarm', + value: updatedState + .status.countdownAlarm, + ), + ); + Navigator.pop(context); + // context.read().add( + // GarageDoorInitialEvent( + // bloc.deviceId)); + } + }); }, child: ToggleWidget( icon: "-1", - value: bloc.deviceStatus.countdownAlarm > 0, + value: state.status.countdownAlarm > 0, code: 'countdown_alarm', deviceId: bloc.deviceId, label: 'Alarm when door is open', @@ -333,12 +355,38 @@ class GarageDoorDialogHelper { child: GestureDetector( onTap: () { context.customAlertDialog( - alertBody: OpeningClosingTimeDialogBody(), + alertBody: OpeningAndClosingTimeDialogBody( + bloc: bloc, + onDurationChanged: (newDuration) { + context.read().add( + UpdateTrTimeConEvent(newDuration), + ); + }, + ), title: 'Opening and Closing Time', - onConfirm: () {}); + onConfirm: () { + final updatedState = + context.read().state; + if (updatedState + is GarageDoorLoadedState) { + context.read().add( + GarageDoorControlEvent( + deviceId: + updatedState.status.uuid, + code: 'tr_timecon', + value: updatedState + .status.trTimeCon, + ), + ); + Navigator.pop(context); + // context.read().add( + // GarageDoorInitialEvent( + // bloc.deviceId)); + } + }); }, child: PresenceDisplayValue( - value: bloc.deviceStatus.trTimeCon.toString(), + value: state.status.trTimeCon.toString(), postfix: 'sec', description: 'Opening & Closing Time', ), diff --git a/lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart b/lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart index ea76d901..843bac9b 100644 --- a/lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart +++ b/lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart @@ -1,13 +1,53 @@ +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'; -class OpeningClosingTimeDialogBody extends StatelessWidget { - const OpeningClosingTimeDialogBody({super.key}); +class OpeningAndClosingTimeDialogBody extends StatefulWidget { + final ValueChanged onDurationChanged; + final GarageDoorBloc bloc; + + OpeningAndClosingTimeDialogBody({ + required this.onDurationChanged, + required this.bloc, + }); + + @override + _OpeningAndClosingTimeDialogBodyState createState() => + _OpeningAndClosingTimeDialogBodyState(); +} + +class _OpeningAndClosingTimeDialogBodyState + extends State { + late int durationInSeconds; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + final currentState = widget.bloc.state; + if (currentState is GarageDoorLoadedState) { + setState(() { + durationInSeconds = currentState.status.trTimeCon; + }); + } + } @override Widget build(BuildContext context) { return Container( - width: 350, - child: Text('asdasd'), + height: 120, + color: Colors.white, + child: SecondsPicker( + initialSeconds: durationInSeconds, + onSecondsChanged: (newSeconds) { + setState(() { + durationInSeconds = newSeconds; + }); + widget.onDurationChanged(newSeconds); + }, + ), ); } } diff --git a/lib/pages/device_managment/garage_door/widgets/seconds_picker.dart b/lib/pages/device_managment/garage_door/widgets/seconds_picker.dart new file mode 100644 index 00000000..491be37b --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/seconds_picker.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; + +class SecondsPicker extends StatefulWidget { + final int initialSeconds; + final ValueChanged onSecondsChanged; + + SecondsPicker({ + required this.initialSeconds, + required this.onSecondsChanged, + }); + + @override + _SecondsPickerState createState() => _SecondsPickerState(); +} + +class _SecondsPickerState extends State { + late FixedExtentScrollController _scrollController; + + @override + void initState() { + super.initState(); + _scrollController = FixedExtentScrollController( + initialItem: widget.initialSeconds, + ); + } + + @override + Widget build(BuildContext context) { + return Container( + height: 120, + color: Colors.white, + child: ListWheelScrollView.useDelegate( + controller: _scrollController, + itemExtent: 48, + onSelectedItemChanged: (index) { + widget.onSecondsChanged(index); + }, + physics: const FixedExtentScrollPhysics(), + childDelegate: ListWheelChildBuilderDelegate( + builder: (context, index) { + return Center( + child: Text( + '$index sec', + style: const TextStyle(fontSize: 24), + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart b/lib/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart index 948f8190..541ab9e4 100644 --- a/lib/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart +++ b/lib/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart @@ -1,13 +1,49 @@ +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_event.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; -class TimeOutAlarmDialogBody extends StatelessWidget { - const TimeOutAlarmDialogBody({super.key}); +class TimeOutAlarmDialogBody extends StatefulWidget { + TimeOutAlarmDialogBody(this.bloc); + final GarageDoorBloc bloc; + + @override + _TimeOutAlarmDialogBodyState createState() => _TimeOutAlarmDialogBodyState(); +} + +class _TimeOutAlarmDialogBodyState extends State { + int durationInSeconds = 0; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + final currentState = widget.bloc.state; + if (currentState is GarageDoorLoadedState) { + if (currentState.status.countdownAlarm != 0) { + setState(() { + durationInSeconds = currentState.status.countdownAlarm; + }); + } + } + } @override Widget build(BuildContext context) { return Container( - width: 350, - child: Text('asdasd'), + height: 120, + color: Colors.white, + child: CupertinoTimerPicker( + itemExtent: 120, + mode: CupertinoTimerPickerMode.hm, + initialTimerDuration: Duration(seconds: durationInSeconds), + onTimerDurationChanged: (newDuration) { + widget.bloc.add( + UpdateCountdownAlarmEvent(newDuration.inSeconds), + ); + }, + ), ); } } From e508849fa5114f894099abfae3eeae9864f05639 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 7 Oct 2024 09:52:16 +0300 Subject: [PATCH 7/8] push factory reset --- .../helper/route_controls_based_code.dart | 78 ++++++++++++--- .../garage_door/bloc/garage_door_bloc.dart | 54 +++++++++++ .../garage_door/bloc/garage_door_event.dart | 38 ++++++++ .../garage_door/bloc/garage_door_state.dart | 18 ++++ .../view/garage_door_batch_control_view.dart | 95 +++++++++++++++++++ .../bloc/one_gang_glass_switch_bloc.dart | 58 ++++++++--- .../bloc/one_gang_glass_switch_event.dart | 10 ++ .../one_gang_glass_batch_control_view.dart | 23 +++-- .../bloc/three_gang_glass_switch_event.dart | 3 +- .../bloc/two_gang_glass_switch_bloc.dart | 51 ++++++---- 10 files changed, 376 insertions(+), 52 deletions(-) diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index 3515ccf7..f9429bd6 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_st import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_control_view.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart'; @@ -110,56 +111,105 @@ mixin RouteControlsBasedCode { switch (devices.first.productType) { case '1G': return WallLightBatchControlView( - deviceIds: devices.where((e) => (e.productType == '1G')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => (e.productType == '1G')) + .map((e) => e.uuid!) + .toList(), ); case '2G': return TwoGangBatchControlView( - deviceIds: devices.where((e) => (e.productType == '2G')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => (e.productType == '2G')) + .map((e) => e.uuid!) + .toList(), ); case '3G': return LivingRoomBatchControlsView( - deviceIds: devices.where((e) => (e.productType == '3G')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => (e.productType == '3G')) + .map((e) => e.uuid!) + .toList(), ); case '1GT': return OneGangGlassSwitchBatchControlView( - deviceIds: devices.where((e) => (e.productType == '1GT')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => (e.productType == '1GT')) + .map((e) => e.uuid!) + .toList(), ); case '2GT': return TwoGangGlassSwitchBatchControlView( - deviceIds: devices.where((e) => (e.productType == '2GT')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => (e.productType == '2GT')) + .map((e) => e.uuid!) + .toList(), ); case '3GT': return ThreeGangGlassSwitchBatchControlView( - deviceIds: devices.where((e) => (e.productType == '3GT')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => (e.productType == '3GT')) + .map((e) => e.uuid!) + .toList(), ); case 'GW': return GatewayBatchControlView( - gatewayIds: devices.where((e) => (e.productType == 'GW')).map((e) => e.uuid!).toList(), + gatewayIds: devices + .where((e) => (e.productType == 'GW')) + .map((e) => e.uuid!) + .toList(), ); case 'DL': return DoorLockBatchControlView( - devicesIds: devices.where((e) => (e.productType == 'DL')).map((e) => e.uuid!).toList()); + devicesIds: devices + .where((e) => (e.productType == 'DL')) + .map((e) => e.uuid!) + .toList()); case 'WPS': return WallSensorBatchControlView( - devicesIds: devices.where((e) => (e.productType == 'WPS')).map((e) => e.uuid!).toList()); + devicesIds: devices + .where((e) => (e.productType == 'WPS')) + .map((e) => e.uuid!) + .toList()); case 'CPS': return CeilingSensorBatchControlView( - devicesIds: devices.where((e) => (e.productType == 'CPS')).map((e) => e.uuid!).toList(), + devicesIds: devices + .where((e) => (e.productType == 'CPS')) + .map((e) => e.uuid!) + .toList(), ); case 'CUR': return CurtainBatchStatusView( - devicesIds: devices.where((e) => (e.productType == 'CUR')).map((e) => e.uuid!).toList(), + devicesIds: devices + .where((e) => (e.productType == 'CUR')) + .map((e) => e.uuid!) + .toList(), ); case 'AC': return AcDeviceBatchControlView( - devicesIds: devices.where((e) => (e.productType == 'AC')).map((e) => e.uuid!).toList()); + devicesIds: devices + .where((e) => (e.productType == 'AC')) + .map((e) => e.uuid!) + .toList()); case 'WH': return WaterHEaterBatchControlView( - deviceIds: devices.where((e) => (e.productType == 'WH')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => (e.productType == 'WH')) + .map((e) => e.uuid!) + .toList(), ); case 'DS': return MainDoorSensorBatchView( - devicesIds: devices.where((e) => (e.productType == 'DS')).map((e) => e.uuid!).toList(), + devicesIds: devices + .where((e) => (e.productType == 'DS')) + .map((e) => e.uuid!) + .toList(), + ); + case 'GD': + return GarageDoorBatchControlView( + deviceIds: devices + .where((e) => (e.productType == 'GD')) + .map((e) => e.uuid!) + .toList(), ); default: return const SizedBox(); diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart index 04af1e39..69e58c65 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart @@ -35,6 +35,9 @@ class GarageDoorBloc extends Bloc { on(_backToGridView); on(_onUpdateCountdownAlarm); on(_onUpdateTrTimeCon); + on(_onBatchControl); + on(_onFetchBatchStatus); + on(_onFactoryReset); } void _fetchGarageDoorStatus( @@ -50,6 +53,20 @@ class GarageDoorBloc extends Bloc { } } + Future _onFetchBatchStatus(GarageDoorFetchBatchStatusEvent event, + Emitter emit) async { + emit(GarageDoorLoadingState()); + try { + final status = + await DevicesManagementApi().getBatchStatus(event.deviceIds); + deviceStatus = + GarageDoorStatusModel.fromJson(event.deviceIds.first, status.status); + emit(GarageDoorBatchStatusLoaded(deviceStatus)); + } catch (e) { + emit(GarageDoorBatchControlError(e.toString())); + } + } + Future _addSchedule( AddGarageDoorScheduleEvent event, Emitter emit) async { try { @@ -222,6 +239,27 @@ class GarageDoorBloc extends Bloc { } } + Future _onBatchControl( + GarageDoorBatchControlEvent event, Emitter emit) async { + final oldValue = event.code == 'switch_1' ? deviceStatus.switch1 : false; + + _updateLocalValue(event.code, event.value); + emit(GarageDoorBatchStatusLoaded(deviceStatus)); + + final success = await _runDeBouncer( + deviceId: event.deviceIds, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + isBatch: true, + ); + + if (!success) { + _revertValue(event.code, oldValue, emit); + } + } + void _backToGridView( BackToGarageDoorGridViewEvent event, Emitter emit) { emit(GarageDoorLoadedState(status: deviceStatus)); @@ -263,6 +301,22 @@ class GarageDoorBloc extends Bloc { } } + Future _onFactoryReset( + GarageDoorFactoryResetEvent event, Emitter emit) async { + emit(GarageDoorLoadingState()); + try { + final response = await DevicesManagementApi() + .factoryReset(event.factoryReset, event.deviceId); + if (!response) { + emit(const GarageDoorErrorState(message: 'Failed to reset device')); + } else { + emit(GarageDoorLoadedState(status: deviceStatus)); + } + } catch (e) { + emit(GarageDoorErrorState(message: e.toString())); + } + } + void _increaseDelay( IncreaseGarageDoorDelayEvent event, Emitter emit) async { // if (deviceStatus.countdown1 != 0) { diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart index 515b2a84..24eb921e 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart @@ -2,6 +2,7 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; abstract class GarageDoorEvent extends Equatable { const GarageDoorEvent(); @@ -180,3 +181,40 @@ class UpdateTrTimeConEvent extends GarageDoorEvent { const UpdateTrTimeConEvent(this.trTimeCon); } + +class GarageDoorBatchControlEvent extends GarageDoorEvent { + final List deviceIds; + final String code; + final bool value; + + const GarageDoorBatchControlEvent({ + required this.deviceIds, + required this.code, + required this.value, + }); + + @override + List get props => [deviceIds, code, value]; +} + +class GarageDoorFetchBatchStatusEvent extends GarageDoorEvent { + final List deviceIds; + + const GarageDoorFetchBatchStatusEvent(this.deviceIds); + + @override + List get props => [deviceIds]; +} + +class GarageDoorFactoryResetEvent extends GarageDoorEvent { + final FactoryResetModel factoryReset; + final String deviceId; + + const GarageDoorFactoryResetEvent({ + required this.factoryReset, + required this.deviceId, + }); + + @override + List get props => [factoryReset, deviceId]; +} diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart index 6f3a2320..2b63a3f8 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart @@ -121,3 +121,21 @@ class ShowGarageDoorDescriptionState extends GarageDoorState { } class ScheduleGarageLoadingState extends GarageDoorState {} + +class GarageDoorBatchStatusLoaded extends GarageDoorState { + final GarageDoorStatusModel status; + + const GarageDoorBatchStatusLoaded(this.status); + + @override + List get props => [status]; +} + +class GarageDoorBatchControlError extends GarageDoorState { + final String message; + + const GarageDoorBatchControlError(this.message); + + @override + List get props => [message]; +} diff --git a/lib/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart b/lib/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart index e69de29b..8c8b60cf 100644 --- a/lib/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart +++ b/lib/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart @@ -0,0 +1,95 @@ +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/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/models/garage_door_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.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 GarageDoorBatchControlView extends StatelessWidget + with HelperResponsiveLayout { + final List deviceIds; + + const GarageDoorBatchControlView({Key? key, required this.deviceIds}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => GarageDoorBloc(deviceId: deviceIds.first) + ..add(GarageDoorFetchBatchStatusEvent(deviceIds)), + child: BlocBuilder( + builder: (context, state) { + if (state is GarageDoorLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is GarageDoorBatchStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is GarageDoorBatchControlError) { + return Center(child: Text('Error: ${state.message}')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } + + Widget _buildStatusControls( + BuildContext context, GarageDoorStatusModel status) { + final isExtraLarge = isExtraLargeScreenSize(context); + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + return GridView( + padding: const EdgeInsets.symmetric(horizontal: 50), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isLarge || isExtraLarge + ? 3 + : isMedium + ? 2 + : 1, + mainAxisExtent: 140, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + children: [ + ToggleWidget( + value: status.switch1, + code: 'switch_1', + deviceId: deviceIds.first, + label: 'Garage Door', + icon: status.switch1 ? Assets.openedDoor : Assets.closedDoor, + onChange: (value) { + context.read().add( + GarageDoorBatchControlEvent( + deviceIds: deviceIds, + code: 'switch_1', + value: value, + ), + ); + }, + ), + FirmwareUpdateWidget( + deviceId: deviceIds.first, + version: 12, + ), + FactoryResetWidget( + callFactoryReset: () { + context.read().add( + GarageDoorFactoryResetEvent( + deviceId: deviceIds.first, + factoryReset: FactoryResetModel(devicesUuid: deviceIds), + ), + ); + }, + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart index 111d5014..b7760333 100644 --- a/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart +++ b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart @@ -3,38 +3,45 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:meta/meta.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; part 'one_gang_glass_switch_event.dart'; part 'one_gang_glass_switch_state.dart'; -class OneGangGlassSwitchBloc extends Bloc { +class OneGangGlassSwitchBloc + extends Bloc { OneGangGlassStatusModel deviceStatus; Timer? _timer; OneGangGlassSwitchBloc({required String deviceId}) - : deviceStatus = OneGangGlassStatusModel(uuid: deviceId, switch1: false, countDown: 0), + : deviceStatus = OneGangGlassStatusModel( + uuid: deviceId, switch1: false, countDown: 0), super(OneGangGlassSwitchInitial()) { on(_onFetchDeviceStatus); on(_onControl); on(_onBatchControl); on(_onFetchBatchStatus); + on(_onFactoryReset); } - Future _onFetchDeviceStatus( - OneGangGlassSwitchFetchDeviceEvent event, Emitter emit) async { + Future _onFetchDeviceStatus(OneGangGlassSwitchFetchDeviceEvent event, + Emitter emit) async { emit(OneGangGlassSwitchLoading()); try { - final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); - deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceId, status.status); + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = + OneGangGlassStatusModel.fromJson(event.deviceId, status.status); emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); } catch (e) { emit(OneGangGlassSwitchError(e.toString())); } } - Future _onControl(OneGangGlassSwitchControl event, Emitter emit) async { + Future _onControl(OneGangGlassSwitchControl event, + Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value); @@ -50,7 +57,24 @@ class OneGangGlassSwitchBloc extends Bloc _onBatchControl(OneGangGlassSwitchBatchControl event, Emitter emit) async { + Future _onFactoryReset(OneGangGlassFactoryResetEvent event, + Emitter emit) async { + emit(OneGangGlassSwitchLoading()); + try { + final response = await DevicesManagementApi() + .factoryReset(event.factoryReset, event.deviceId); + if (!response) { + emit(OneGangGlassSwitchError('Failed to reset device')); + } else { + emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); + } + } catch (e) { + emit(OneGangGlassSwitchError(e.toString())); + } + } + + Future _onBatchControl(OneGangGlassSwitchBatchControl event, + Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value); @@ -67,11 +91,14 @@ class OneGangGlassSwitchBloc extends Bloc _onFetchBatchStatus( - OneGangGlassSwitchFetchBatchStatusEvent event, Emitter emit) async { + OneGangGlassSwitchFetchBatchStatusEvent event, + Emitter emit) async { emit(OneGangGlassSwitchLoading()); try { - final status = await DevicesManagementApi().getBatchStatus(event.deviceIds); - deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceIds.first, status.status); + final status = + await DevicesManagementApi().getBatchStatus(event.deviceIds); + deviceStatus = OneGangGlassStatusModel.fromJson( + event.deviceIds.first, status.status); emit(OneGangGlassSwitchBatchStatusLoaded(deviceStatus)); } catch (e) { emit(OneGangGlassSwitchError(e.toString())); @@ -101,9 +128,11 @@ class OneGangGlassSwitchBloc extends Bloc emit) { + void _revertValueAndEmit(String deviceId, String code, bool oldValue, + Emitter emit) { _updateLocalValue(code, oldValue); emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); } diff --git a/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_event.dart b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_event.dart index 993e5a8c..83d9b7b9 100644 --- a/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_event.dart +++ b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_event.dart @@ -38,3 +38,13 @@ class OneGangGlassSwitchFetchBatchStatusEvent extends OneGangGlassSwitchEvent { OneGangGlassSwitchFetchBatchStatusEvent(this.deviceIds); } + +class OneGangGlassFactoryResetEvent extends OneGangGlassSwitchEvent { + final FactoryResetModel factoryReset; + final String deviceId; + + OneGangGlassFactoryResetEvent({ + required this.factoryReset, + required this.deviceId, + }); +} diff --git a/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart index e81f448c..7c0064c3 100644 --- a/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart +++ b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart @@ -1,5 +1,6 @@ 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/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.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/shared/batch_control/factory_reset.dart'; @@ -7,16 +8,18 @@ import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class OneGangGlassSwitchBatchControlView extends StatelessWidget with HelperResponsiveLayout { +class OneGangGlassSwitchBatchControlView extends StatelessWidget + with HelperResponsiveLayout { final List deviceIds; - const OneGangGlassSwitchBatchControlView({required this.deviceIds, super.key}); + const OneGangGlassSwitchBatchControlView( + {required this.deviceIds, super.key}); @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => - OneGangGlassSwitchBloc(deviceId: deviceIds.first)..add(OneGangGlassSwitchFetchBatchStatusEvent(deviceIds)), + create: (context) => OneGangGlassSwitchBloc(deviceId: deviceIds.first) + ..add(OneGangGlassSwitchFetchBatchStatusEvent(deviceIds)), child: BlocBuilder( builder: (context, state) { if (state is OneGangGlassSwitchLoading) { @@ -33,7 +36,8 @@ class OneGangGlassSwitchBatchControlView extends StatelessWidget with HelperResp ); } - Widget _buildStatusControls(BuildContext context, OneGangGlassStatusModel status) { + Widget _buildStatusControls( + BuildContext context, OneGangGlassStatusModel status) { final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); @@ -72,7 +76,14 @@ class OneGangGlassSwitchBatchControlView extends StatelessWidget with HelperResp version: 12, // adjust the version according to your requirement ), FactoryResetWidget( - callFactoryReset: () {}, + callFactoryReset: () { + context.read().add( + OneGangGlassFactoryResetEvent( + factoryReset: FactoryResetModel(devicesUuid: deviceIds), + deviceId: deviceIds.first, + ), + ); + }, ), ], ); diff --git a/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_event.dart b/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_event.dart index a40888e7..558b9824 100644 --- a/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_event.dart +++ b/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_event.dart @@ -33,7 +33,8 @@ class ThreeGangGlassSwitchBatchControl extends ThreeGangGlassSwitchEvent { }); } -class ThreeGangGlassSwitchFetchBatchStatusEvent extends ThreeGangGlassSwitchEvent { +class ThreeGangGlassSwitchFetchBatchStatusEvent + extends ThreeGangGlassSwitchEvent { final List deviceIds; ThreeGangGlassSwitchFetchBatchStatusEvent(this.deviceIds); diff --git a/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart b/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart index be2ab687..5169b0e4 100644 --- a/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart +++ b/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart @@ -10,13 +10,18 @@ import 'package:syncrow_web/services/devices_mang_api.dart'; part 'two_gang_glass_switch_event.dart'; part 'two_gang_glass_switch_state.dart'; -class TwoGangGlassSwitchBloc extends Bloc { +class TwoGangGlassSwitchBloc + extends Bloc { TwoGangGlassStatusModel deviceStatus; Timer? _timer; TwoGangGlassSwitchBloc({required String deviceId}) - : deviceStatus = - TwoGangGlassStatusModel(uuid: deviceId, switch1: false, countDown1: 0, switch2: false, countDown2: 0), + : deviceStatus = TwoGangGlassStatusModel( + uuid: deviceId, + switch1: false, + countDown1: 0, + switch2: false, + countDown2: 0), super(TwoGangGlassSwitchInitial()) { on(_onFetchDeviceStatus); on(_onControl); @@ -25,19 +30,22 @@ class TwoGangGlassSwitchBloc extends Bloc(_onFactoryReset); } - Future _onFetchDeviceStatus( - TwoGangGlassSwitchFetchDeviceEvent event, Emitter emit) async { + Future _onFetchDeviceStatus(TwoGangGlassSwitchFetchDeviceEvent event, + Emitter emit) async { emit(TwoGangGlassSwitchLoading()); try { - final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); - deviceStatus = TwoGangGlassStatusModel.fromJson(event.deviceId, status.status); + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = + TwoGangGlassStatusModel.fromJson(event.deviceId, status.status); emit(TwoGangGlassSwitchStatusLoaded(deviceStatus)); } catch (e) { emit(TwoGangGlassSwitchError(e.toString())); } } - Future _onControl(TwoGangGlassSwitchControl event, Emitter emit) async { + Future _onControl(TwoGangGlassSwitchControl event, + Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value); @@ -53,7 +61,8 @@ class TwoGangGlassSwitchBloc extends Bloc _onBatchControl(TwoGangGlassSwitchBatchControl event, Emitter emit) async { + Future _onBatchControl(TwoGangGlassSwitchBatchControl event, + Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value); @@ -70,21 +79,26 @@ class TwoGangGlassSwitchBloc extends Bloc _onFetchBatchStatus( - TwoGangGlassSwitchFetchBatchStatusEvent event, Emitter emit) async { + TwoGangGlassSwitchFetchBatchStatusEvent event, + Emitter emit) async { emit(TwoGangGlassSwitchLoading()); try { - final status = await DevicesManagementApi().getBatchStatus(event.deviceIds); - deviceStatus = TwoGangGlassStatusModel.fromJson(event.deviceIds.first, status.status); + final status = + await DevicesManagementApi().getBatchStatus(event.deviceIds); + deviceStatus = TwoGangGlassStatusModel.fromJson( + event.deviceIds.first, status.status); emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus)); } catch (e) { emit(TwoGangGlassSwitchError(e.toString())); } } - Future _onFactoryReset(TwoGangGlassFactoryReset event, Emitter emit) async { + Future _onFactoryReset(TwoGangGlassFactoryReset event, + Emitter emit) async { emit(TwoGangGlassSwitchLoading()); try { - final response = await DevicesManagementApi().factoryReset(event.factoryReset, event.deviceId); + final response = await DevicesManagementApi() + .factoryReset(event.factoryReset, event.deviceId); if (!response) { emit(TwoGangGlassSwitchError('Failed')); } else { @@ -118,9 +132,11 @@ class TwoGangGlassSwitchBloc extends Bloc emit) { + void _revertValueAndEmit(String deviceId, String code, bool oldValue, + Emitter emit) { _updateLocalValue(code, oldValue); emit(TwoGangGlassSwitchStatusLoaded(deviceStatus)); } From 2b5249e985b8944be6f63029ba28f58c6be521d1 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 7 Oct 2024 10:06:28 +0300 Subject: [PATCH 8/8] push firmware update --- .../one_gang_glass_batch_control_view.dart | 2 +- .../shared/batch_control/firmware_update.dart | 135 ++++++++++++++---- 2 files changed, 110 insertions(+), 27 deletions(-) diff --git a/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart index 7c0064c3..4239b08e 100644 --- a/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart +++ b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart @@ -73,7 +73,7 @@ class OneGangGlassSwitchBatchControlView extends StatelessWidget ), FirmwareUpdateWidget( deviceId: deviceIds.first, - version: 12, // adjust the version according to your requirement + version: 12, ), FactoryResetWidget( callFactoryReset: () { diff --git a/lib/pages/device_managment/shared/batch_control/firmware_update.dart b/lib/pages/device_managment/shared/batch_control/firmware_update.dart index bb7c7516..095d3efc 100644 --- a/lib/pages/device_managment/shared/batch_control/firmware_update.dart +++ b/lib/pages/device_managment/shared/batch_control/firmware_update.dart @@ -1,43 +1,126 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; -class FirmwareUpdateWidget extends StatelessWidget { +class FirmwareUpdateWidget extends StatefulWidget { const FirmwareUpdateWidget( - {super.key, required String deviceId, required int version}); + {super.key, required this.deviceId, required this.version}); + + final String deviceId; + final int version; + + @override + State createState() => _FirmwareUpdateWidgetState(); +} + +class _FirmwareUpdateWidgetState extends State { + bool _showConfirmation = false; + + void _toggleConfirmation() { + setState(() { + _showConfirmation = !_showConfirmation; + }); + } @override Widget build(BuildContext context) { return DeviceControlsContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ClipOval( - child: Container( - color: ColorsManager.whiteColors, - height: 60, - width: 60, - child: Padding( - padding: const EdgeInsets.all(12.0), - child: SvgPicture.asset( - Assets.firmware, - fit: BoxFit.cover, + child: _showConfirmation + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Firmware Update', + style: context.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.bold, + color: ColorsManager.blackColor, + ), + ), + Text( + 'Are you sure?', + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.grayColor, + ), + ), + const SizedBox(height: 16), + Row( + children: [ + Flexible( + child: DefaultButton( + height: 20, + elevation: 0, + padding: 0, + onPressed: _toggleConfirmation, + backgroundColor: ColorsManager.greyColor, + child: Text( + 'Cancel', + style: context.textTheme.bodyMedium!.copyWith( + color: ColorsManager.blackColor, + fontWeight: FontWeight.w400, + fontSize: 12, + ), + ), + ), + ), + const SizedBox(width: 8), + Flexible( + child: DefaultButton( + height: 20, + elevation: 0, + padding: 0, + onPressed: () { + _toggleConfirmation(); + }, + backgroundColor: ColorsManager.primaryColor, + child: Text( + 'Update', + style: context.textTheme.bodyMedium!.copyWith( + color: ColorsManager.whiteColors, + fontWeight: FontWeight.w400, + fontSize: 12, + ), + ), + ), + ), + ], + ), + ], + ) + : GestureDetector( + onTap: _toggleConfirmation, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ClipOval( + child: Container( + color: ColorsManager.whiteColors, + height: 60, + width: 60, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: SvgPicture.asset( + Assets.firmware, + fit: BoxFit.cover, + ), + ), + ), + ), + Text( + 'Firmware Update', + style: context.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor, + ), + ), + ], ), ), - )), - Text( - 'Firmware Update', - style: context.textTheme.titleMedium!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.blackColor, - ), - ), - ], - ), ); } }