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/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index eca980c6..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,8 @@ 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'; import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_control_view.dart'; @@ -82,6 +84,10 @@ mixin RouteControlsBasedCode { ); case 'DS': return MainDoorSensorControlView(device: device); + case 'GD': + return GarageDoorControlView( + deviceId: device.uuid!, + ); default: return const SizedBox(); } @@ -105,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 new file mode 100644 index 00000000..69e58c65 --- /dev/null +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart @@ -0,0 +1,433 @@ +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/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'; + +class GarageDoorBloc extends Bloc { + final String deviceId; + late GarageDoorStatusModel deviceStatus; + Timer? _timer; + + GarageDoorBloc({required this.deviceId}) : super(GarageDoorInitialState()) { + on(_fetchGarageDoorStatus); + on(_garageDoorControlEvent); + on(_addSchedule); + on(_updateSchedule); + on(_deleteSchedule); + on(_fetchSchedules); + on(_increaseDelay); + on(_decreaseDelay); + on(_fetchRecords); + on(_handleUpdate); + on(_updateSelectedTime); + on(_updateSelectedDay); + on(_updateFunctionOn); + on(_initializeAddSchedule); + on(_backToGridView); + on(_onUpdateCountdownAlarm); + on(_onUpdateTrTimeCon); + on(_onBatchControl); + on(_onFetchBatchStatus); + on(_onFactoryReset); + } + + 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)); + } catch (e) { + emit(GarageDoorErrorState(message: e.toString())); + } + } + + 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 { + 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: 'switch_1')); + } else { + emit(GarageDoorLoadedState(status: deviceStatus)); + } + } catch (e) { + emit(GarageDoorLoadedState(status: deviceStatus)); + } + } + + 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) { + 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(GarageDoorLoadedState(status: deviceStatus)); + } + } catch (e) { + emit(GarageDoorLoadedState(status: deviceStatus)); + } + } + + Future _deleteSchedule(DeleteGarageDoorScheduleEvent event, + Emitter emit) async { + 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(GarageDoorLoadedState(status: deviceStatus)); + } + } catch (e) { + emit(GarageDoorLoadedState(status: deviceStatus)); + } + } + + Future _fetchSchedules(FetchGarageDoorSchedulesEvent event, + Emitter emit) async { + emit(ScheduleGarageLoadingState()); + try { + List schedules = await DevicesManagementApi() + .getDeviceSchedules(deviceStatus.uuid, event.category); + deviceStatus = deviceStatus.copyWith(schedules: schedules); + emit( + GarageDoorLoadedState( + status: deviceStatus, + scheduleMode: ScheduleModes.schedule, + ), + ); + } catch (e) { + emit( + GarageDoorLoadedState( + status: deviceStatus, + scheduleMode: ScheduleModes.schedule, + ), + ); + } + } + + 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, selectedTime: currentState.selectedTime)); + } + } + + Future _updateFunctionOn( + UpdateFunctionOnEvent event, Emitter emit) async { + final currentState = state; + if (currentState is GarageDoorLoadedState) { + emit(currentState.copyWith( + functionOn: event.functionOn, + selectedTime: currentState.selectedTime)); + } + } + + 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 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())); + } + } + + 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)); + } + + void _handleUpdate( + GarageDoorUpdatedEvent event, Emitter emit) { + emit(GarageDoorLoadedState(status: deviceStatus)); + } + + Future _runDeBouncer({ + required dynamic deviceId, + required String code, + required dynamic value, + required dynamic oldValue, + required Emitter emit, + required bool isBatch, + }) async { + 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; + } + } + + 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) { + 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': + if (oldValue is bool) { + 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; + } + 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; + case 'countdown_1': + if (value is int) { + 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; + default: + break; + } + } + + @override + Future close() { + _timer?.cancel(); + return super.close(); + } +} 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..24eb921e --- /dev/null +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart @@ -0,0 +1,220 @@ +// lib/pages/device_managment/garage_door/bloc/event.dart + +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(); + + @override + List get props => []; +} + +class GarageDoorInitialEvent extends GarageDoorEvent { + final String deviceId; + + const GarageDoorInitialEvent(this.deviceId); + + @override + List get props => [deviceId]; +} + +class GarageDoorControlEvent extends GarageDoorEvent { + final String deviceId; + final dynamic value; + final String code; + + const GarageDoorControlEvent( + {required this.deviceId, required this.value, required this.code}); + + @override + List get props => [deviceId, value]; +} + +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; + final String code; + + const FetchGarageDoorRecordsEvent( + {required this.deviceId, required this.code}); + + @override + List get props => [deviceId, code]; +} + +class BackToGarageDoorGridViewEvent extends GarageDoorEvent {} + +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, + ]; +} + +class UpdateCountdownAlarmEvent extends GarageDoorEvent { + final int countdownAlarm; + + const UpdateCountdownAlarmEvent(this.countdownAlarm); +} + +class UpdateTrTimeConEvent extends GarageDoorEvent { + final int trTimeCon; + + 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 new file mode 100644 index 00000000..2b63a3f8 --- /dev/null +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart @@ -0,0 +1,141 @@ +// 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/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 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.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, + delay, + records, + selectedDays, + selectedTime, + functionOn, + isEditing, + scheduleMode, + ]; + + GarageDoorLoadedState copyWith({ + GarageDoorStatusModel? status, + Duration? delay, + DeviceReport? records, + List? selectedDays, + TimeOfDay? selectedTime, + bool? functionOn, + bool? isEditing, + ScheduleModes? scheduleMode, + }) { + return GarageDoorLoadedState( + status: status ?? this.status, + 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 {} + +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/helper/garage_door_helper.dart b/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart new file mode 100644 index 00000000..c30f391d --- /dev/null +++ b/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart @@ -0,0 +1,409 @@ +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/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 GarageDoorDialogHelper { + 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'), + ], + ); + } + + 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(), + + /// 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( + 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(bloc), + title: 'Time Out Alarm', + 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: state.status.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: OpeningAndClosingTimeDialogBody( + bloc: bloc, + onDurationChanged: (newDuration) { + context.read().add( + UpdateTrTimeConEvent(newDuration), + ); + }, + ), + title: 'Opening and Closing Time', + 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: state.status.trTimeCon.toString(), + postfix: 'sec', + description: 'Opening & Closing Time', + ), + ), + ), + const SizedBox(width: 24), + ], + ) + ], + ), + ); + } + return const SizedBox(); + }, + ), + ); + }, + ); + } +} 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..dcb4718c --- /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, 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..8c8b60cf --- /dev/null +++ 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/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..e3c44405 --- /dev/null +++ b/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart @@ -0,0 +1,206 @@ +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/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'; +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 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 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) { + 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(), + padding: const EdgeInsets.symmetric(horizontal: 50), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isLarge || isExtraLarge + ? 3 + : isMedium + ? 2 + : 1, + childAspectRatio: 1.5, + mainAxisExtent: 140, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + children: [ + IconNameStatusContainer( + isFullIcon: false, + name: status.switch1 ? 'Opened' : 'Closed', + icon: status.switch1 ? Assets.openedDoor : Assets.closedDoor, + onTap: () { + context.read().add( + GarageDoorControlEvent( + deviceId: status.uuid, + value: !status.switch1, + code: 'switch_1'), + ); + }, + status: status.switch1, + textColor: ColorsManager.blackColor, + ), + IconNameStatusContainer( + onTap: () { + context.read().add( + FetchGarageDoorSchedulesEvent( + deviceId: deviceId, category: 'switch_1'), + ); + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: BlocProvider.of(context), + child: BuildGarageDoorScheduleView(status: status), + )); + }, + name: 'Scheduling', + icon: Assets.acSchedule, + status: false, + textColor: ColorsManager.blackColor, + isFullIcon: false, + ), + ToggleWidget( + label: '', + labelWidget: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IconButton( + onPressed: () { + 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'), + 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: status.uuid)); + }, + icon: const Icon( + Icons.add, + size: 28, + color: ColorsManager.greyColor, + ), + padding: EdgeInsets.zero, + ), + ], + ), + value: status.countdown1 != 0 ? true : false, + code: 'countdown_1', + deviceId: status.uuid, + icon: Assets.doorDelay, + onChange: (value) { + context.read().add( + GarageDoorControlEvent( + deviceId: status.uuid, + value: value ? status.delay.inSeconds : 0, + code: 'countdown_1'), + ); + }, + ), + IconNameStatusContainer( + isFullIcon: false, + name: 'Records', + icon: Assets.records, + onTap: () { + context.read().add(FetchGarageDoorRecordsEvent( + code: 'switch_1', deviceId: status.uuid)); + }, + status: false, + textColor: ColorsManager.blackColor, + ), + IconNameStatusContainer( + isFullIcon: false, + name: 'Preferences', + icon: Assets.preferences, + onTap: () { + GarageDoorDialogHelper.showPreferencesDialog(context); + }, + status: false, + textColor: ColorsManager.blackColor, + ), + ], + ); + } +} 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..843bac9b --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart @@ -0,0 +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 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( + 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/schedule__garage_table.dart b/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart new file mode 100644 index 00000000..48ebbcad --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart @@ -0,0 +1,230 @@ +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.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)), + ), + 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.status.schedules != null) + for (int i = 0; i < state.status.schedules!.length; i++) + _buildScheduleRow( + state.status.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: () { + GarageDoorDialogHelper.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..b30c3596 --- /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 ScheduleGarageModeButtons extends StatelessWidget { + final VoidCallback onSave; + + const ScheduleGarageModeButtons({ + 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..107c8e0a --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart @@ -0,0 +1,96 @@ +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_buttons.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), + ScheduleGarageManagementUI( + state: state, + onAddSchedule: () { + GarageDoorDialogHelper + .showAddGarageDoorScheduleDialog(context, + schedule: null, index: null, isEdit: false); + }, + ), + const SizedBox(height: 20), + ScheduleGarageModeButtons( + onSave: () { + Navigator.pop(context); + }, + ), + ], + ); + } + if (state is ScheduleGarageLoadingState) { + return SizedBox( + height: 200, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const ScheduleGarageHeader(), + const SizedBox( + height: 50, + ), + const Center(child: CircularProgressIndicator()), + const SizedBox( + height: 20, + ), + ScheduleGarageModeButtons( + onSave: () {}, + ), + ], + )); + } + return const SizedBox( + height: 200, + child: ScheduleGarageHeader(), + ); + }, + ), + ), + ), + ), + ), + ); + } +} 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 new file mode 100644 index 00000000..541ab9e4 --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart @@ -0,0 +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 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( + height: 120, + color: Colors.white, + child: CupertinoTimerPicker( + itemExtent: 120, + mode: CupertinoTimerPickerMode.hm, + initialTimerDuration: Duration(seconds: durationInSeconds), + onTimerDurationChanged: (newDuration) { + widget.bloc.add( + UpdateCountdownAlarmEvent(newDuration.inSeconds), + ); + }, + ), + ); + } +} 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/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..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 @@ -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); @@ -69,10 +73,17 @@ class OneGangGlassSwitchBatchControlView extends StatelessWidget with HelperResp ), FirmwareUpdateWidget( deviceId: deviceIds.first, - version: 12, // adjust the version according to your requirement + version: 12, ), FactoryResetWidget( - callFactoryReset: () {}, + callFactoryReset: () { + context.read().add( + OneGangGlassFactoryResetEvent( + factoryReset: FactoryResetModel(devicesUuid: deviceIds), + deviceId: deviceIds.first, + ), + ); + }, ), ], ); 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, - ), - ), - ], - ), ); } } 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/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), + ), ], ); }), 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/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)); } 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(', '); -// } -// } 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'; } 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), + ), + ), + ), + ), + ], + ), + ), + ); + }, + ); + } }