diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c169a71 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "Scheduleapp" + ] +} \ No newline at end of file diff --git a/assets/icons/closed_garage_door.svg b/assets/icons/closed_garage_door.svg new file mode 100644 index 0000000..464aa07 --- /dev/null +++ b/assets/icons/closed_garage_door.svg @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/garage_countdown.svg b/assets/icons/garage_countdown.svg new file mode 100644 index 0000000..b2a3ead --- /dev/null +++ b/assets/icons/garage_countdown.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/garage_preferences_icon.svg b/assets/icons/garage_preferences_icon.svg new file mode 100644 index 0000000..d0883b0 --- /dev/null +++ b/assets/icons/garage_preferences_icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/icons/garage_schedule.svg b/assets/icons/garage_schedule.svg new file mode 100644 index 0000000..04e7763 --- /dev/null +++ b/assets/icons/garage_schedule.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/open_garage_door.svg b/assets/icons/open_garage_door.svg new file mode 100644 index 0000000..d55a690 --- /dev/null +++ b/assets/icons/open_garage_door.svg @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/features/devices/bloc/garage_door_bloc/garage_door_bloc.dart b/lib/features/devices/bloc/garage_door_bloc/garage_door_bloc.dart new file mode 100644 index 0000000..f6c3d4f --- /dev/null +++ b/lib/features/devices/bloc/garage_door_bloc/garage_door_bloc.dart @@ -0,0 +1,411 @@ +import 'dart:async'; +import 'package:dio/dio.dart'; +import 'package:firebase_database/firebase_database.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_event.dart'; +import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_state.dart'; +import 'package:syncrow_app/features/devices/model/device_control_model.dart'; +import 'package:syncrow_app/features/devices/model/device_report_model.dart'; +import 'package:syncrow_app/features/devices/model/garage_door_model.dart'; +import 'package:syncrow_app/features/devices/model/schedule_model.dart'; +import 'package:syncrow_app/features/devices/model/status_model.dart'; +import 'package:syncrow_app/services/api/devices_api.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; + +class GarageDoorBloc extends Bloc { + final String GDId; + GarageDoorBloc({ + required this.GDId, + }) : super(const GarageDoorSensorState()) { + on(_fetchStatus); + on(fetchLogsForLastMonth); + on(_toggleClosingReminder); + on(_toggleDoorAlarm); + on(toggleDaySelection); + on(saveSchedule); + on(getSchedule); + on(toggleChange); + on(toggleCreateSchedule); + on(_onClose); + on(selectSeconds); + on(openCloseGarageDoor); + on(_getCounterValue); + on(_setCounterValue); + on(_onTickTimer); + } + void _onClose(OnClose event, Emitter emit) { + _timer?.cancel(); + } + + Timer? _timer; + bool lowBattery = false; + bool closingReminder = false; + bool doorAlarm = false; + + GarageDoorModel deviceStatus = GarageDoorModel( + tr_timecon: 0, + countdown1: 0, + countdownAlarm: 0, + doorContactState: false, + doorControl1: '', + doorState1: '', + switch1: false, + voiceControl1: false, + batteryPercentage: 0, + ); + + void _fetchStatus( + GarageDoorInitial event, Emitter emit) async { + emit(GarageDoorLoadingState()); + try { + var response = await DevicesAPI.getDeviceStatus(GDId); + List statusModelList = []; + for (var status in response['status']) { + statusModelList.add(StatusModel.fromJson(status)); + } + deviceStatus = GarageDoorModel.fromJson( + statusModelList, + ); + secondSelected = deviceStatus.tr_timecon; + toggleDoor = deviceStatus.switch1; + emit(UpdateState(garageSensor: deviceStatus)); + Future.delayed(const Duration(milliseconds: 500)); + _listenToChanges(); + } catch (e) { + emit(GarageDoorFailedState(errorMessage: e.toString())); + return; + } + } + + void _toggleClosingReminder(ToggleClosingReminderEvent event, + Emitter emit) async { + emit(LoadingNewSate(doorSensor: deviceStatus)); + try { + closingReminder = event.isClosingReminderEnabled; + emit(UpdateState(garageSensor: deviceStatus)); + await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: GDId, + code: 'closing_reminder', + value: closingReminder, + ), + GDId, + ); + } catch (e) { + emit(GarageDoorFailedState(errorMessage: e.toString())); + } + } + + void _toggleDoorAlarm( + ToggleDoorAlarmEvent event, Emitter emit) async { + emit(LoadingNewSate(doorSensor: deviceStatus)); + try { + doorAlarm = event.isDoorAlarmEnabled; + emit(UpdateState(garageSensor: deviceStatus)); + await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: GDId, + code: 'door_alarm', + value: doorAlarm, + ), + GDId, + ); + } catch (e) { + emit(GarageDoorFailedState(errorMessage: e.toString())); + } + } + + DeviceReport recordGroups = + DeviceReport(startTime: '0', endTime: '0', data: []); + + Future fetchLogsForLastMonth( + ReportLogsInitial event, Emitter emit) async { + DateTime now = DateTime.now(); + + DateTime lastMonth = DateTime(now.year, now.month - 1, now.day); + int startTime = lastMonth.millisecondsSinceEpoch; + int endTime = now.millisecondsSinceEpoch; + try { + emit(GarageDoorLoadingState()); + var response = await DevicesAPI.getReportLogs( + startTime: startTime.toString(), + endTime: endTime.toString(), + deviceUuid: GDId, + code: 'doorcontact_state', + ); + recordGroups = response; + emit(UpdateState(garageSensor: deviceStatus)); + } on DioException catch (e) { + final errorData = e.response!.data; + String errorMessage = errorData['message']; + emit(GarageDoorFailedState(errorMessage: errorMessage)); + } + } + + _listenToChanges() { + try { + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$GDId'); + Stream stream = ref.onValue; + + stream.listen((DatabaseEvent event) async { + if (_timer != null) { + await Future.delayed(const Duration(seconds: 2)); + } + Map usersMap = + event.snapshot.value as Map; + List statusList = []; + usersMap['status'].forEach((element) { + statusList.add(StatusModel(code: element['code'], value: true)); + }); + + deviceStatus = GarageDoorModel.fromJson(statusList); + if (!isClosed) { + // add( + // DoorSensorSwitch(switchD: deviceStatus.doorContactState), + // ); + } + }); + } catch (_) {} + } + + List> days = [ + {"day": "Sun", "key": "Sun"}, + {"day": "Mon", "key": "Mon"}, + {"day": "Tue", "key": "Tue"}, + {"day": "Wed", "key": "Wed"}, + {"day": "Thu", "key": "Thu"}, + {"day": "Fri", "key": "Fri"}, + {"day": "Sat", "key": "Sat"}, + ]; + + Future toggleDaySelection( + ToggleDaySelectionEvent event, + Emitter emit, + ) async { + emit(GarageDoorLoadingState()); + if (selectedDays.contains(event.key)) { + selectedDays.remove(event.key); + } else { + selectedDays.add(event.key); + } + emit(ChangeTimeState()); + } + + Future saveSchedule( + ScheduleSaveapp event, + Emitter emit, + ) async { + try { + if (selectedDays.isNotEmpty) { + emit(GarageDoorLoadingState()); + await DevicesAPI.postSchedule( + category: 'switch_1', + deviceId: GDId, + time: getTimeStampWithoutSeconds(selectedTime).toString(), + code: 'switch_1', + value: toggleSchedule, + days: selectedDays); + CustomSnackBar.displaySnackBar('Save Successfully'); + add(GetScheduleEvent()); + } else { + CustomSnackBar.displaySnackBar('Please select days'); + } + } catch (e) { + emit(GarageDoorFailedState(errorMessage: e.toString())); + } + } + + Future getSchedule( + GetScheduleEvent event, + Emitter emit, + ) async { + try { + emit(GarageDoorLoadingState()); + final response = await DevicesAPI.getSchedule( + category: 'switch_1', + deviceId: GDId, + ); + List jsonData = response; + listSchedule = + jsonData.map((item) => ScheduleModel.fromJson(item)).toList(); + } on DioException catch (e) { + final errorData = e.response!.data; + String errorMessage = errorData['message']; + emit(GarageDoorFailedState(errorMessage: errorMessage.toString())); + } + } + + int? getTimeStampWithoutSeconds(DateTime? dateTime) { + if (dateTime == null) return null; + DateTime dateTimeWithoutSeconds = DateTime(dateTime.year, dateTime.month, + dateTime.day, dateTime.hour, dateTime.minute); + return dateTimeWithoutSeconds.millisecondsSinceEpoch ~/ 1000; + } + + Future toggleChange( + ToggleScheduleEvent event, Emitter emit) async { + try { + emit(GarageDoorLoadingState()); + final response = await DevicesAPI.changeSchedule( + scheduleId: event.id, deviceUuid: GDId, enable: event.toggle); + if (response == true) { + add(GetScheduleEvent()); + toggleSchedule = event.toggle; + return toggleSchedule; + } + emit(IsToggleState(onOff: toggleSchedule)); + } on DioException catch (e) { + final errorData = e.response!.data; + String errorMessage = errorData['message']; + emit(GarageDoorFailedState(errorMessage: errorMessage.toString())); + } + } + + Future deleteSchedule( + DeleteScheduleEvent event, Emitter emit) async { + try { + emit(GarageDoorLoadingState()); + final response = await DevicesAPI.deleteSchedule( + scheduleId: event.id, + deviceUuid: GDId, + ); + if (response == true) { + add(GetScheduleEvent()); + return toggleSchedule; + } + emit(IsToggleState(onOff: toggleSchedule)); + } on DioException catch (e) { + final errorData = e.response!.data; + String errorMessage = errorData['message']; + emit(GarageDoorFailedState(errorMessage: errorMessage.toString())); + } + } + + void toggleSelectedIndex( + ToggleSelectedEvent event, Emitter emit) { + emit(GarageDoorLoadingState()); + selectedTabIndex = event.index; + emit(ChangeSlidingSegmentState(value: selectedTabIndex)); + } + + void toggleCreateSchedule( + ToggleCreateScheduleEvent event, Emitter emit) { + emit(GarageDoorLoadingState()); + createSchedule = !createSchedule; + selectedDays.clear(); + selectedTime = DateTime.now(); + emit(UpdateCreateScheduleState(createSchedule)); + } + + int selectedTabIndex = 0; + bool toggleSchedule = true; + List selectedDays = []; + bool createSchedule = false; + List listSchedule = []; + DateTime? selectedTime = DateTime.now(); + + int secondSelected = 0; + bool toggleDoor = false; + Future selectSeconds( + SelectSecondsEvent event, Emitter emit) async { + try { + emit(GarageDoorLoadingState()); + secondSelected = event.seconds; + + final response = await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: GDId, code: 'tr_timecon', value: secondSelected), + GDId); + emit(UpdateState(garageSensor: deviceStatus)); + } on DioException catch (e) { + final errorData = e.response!.data; + String errorMessage = errorData['message']; + emit(GarageDoorFailedState(errorMessage: errorMessage.toString())); + } + } + + openCloseGarageDoor( + ToggleDoorEvent event, Emitter emit) async { + emit(GarageDoorLoadingState()); + try { + toggleDoor = !event.toggle; + final response = await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: GDId, code: 'switch_1', value: toggleDoor), + GDId); + add(GarageDoorInitial()); + emit(UpdateState(garageSensor: deviceStatus)); + } on DioException catch (e) { + final errorData = e.response!.data; + String errorMessage = errorData['message']; + emit(GarageDoorFailedState(errorMessage: errorMessage.toString())); + } + } + + void _setCounterValue( + SetCounterValue event, Emitter emit) async { + emit(LoadingNewSate(doorSensor: deviceStatus)); + int seconds = 0; + try { + seconds = event.duration.inSeconds; + final response = await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: GDId, code: 'countdown_1', value: seconds), + GDId); + + if (response['success'] ?? false) { + deviceStatus.countdown1 = seconds; + } else { + emit(GarageDoorFailedState(errorMessage: 'Something went wrong')); + return; + } + } catch (e) { + emit(GarageDoorFailedState(errorMessage: e.toString())); + return; + } + if (seconds > 0) { + _onStartTimer(seconds); + } else { + _timer?.cancel(); + emit(TimerRunComplete()); + } + } + + void _getCounterValue( + GetCounterEvent event, Emitter emit) async { + emit(LoadingInitialState()); + try { + var response = await DevicesAPI.getDeviceStatus(GDId); + List statusModelList = []; + for (var status in response['status']) { + statusModelList.add(StatusModel.fromJson(status)); + } + deviceStatus = GarageDoorModel.fromJson(statusModelList); + if (event.deviceCode == 'countdown_1') { + deviceStatus.countdown1 > 0 + ? _onStartTimer(deviceStatus.countdown1) + : emit(UpdateTimerState(seconds: deviceStatus.countdown1)); + } + } catch (e) { + emit(GarageDoorFailedState(errorMessage: e.toString())); + return; + } + } + + void _onStartTimer(int seconds) { + _timer?.cancel(); + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + add(TickTimer(remainingTime: seconds - timer.tick)); + }); + } + + void _onTickTimer(TickTimer event, Emitter emit) { + if (event.remainingTime > 0) { + emit(TimerRunInProgress(event.remainingTime)); + } else { + _timer?.cancel(); + emit(TimerRunComplete()); + } + } +} diff --git a/lib/features/devices/bloc/garage_door_bloc/garage_door_event.dart b/lib/features/devices/bloc/garage_door_bloc/garage_door_event.dart new file mode 100644 index 0000000..17ee34a --- /dev/null +++ b/lib/features/devices/bloc/garage_door_bloc/garage_door_event.dart @@ -0,0 +1,162 @@ +import 'package:equatable/equatable.dart'; + +abstract class GarageDoorEvent extends Equatable { + const GarageDoorEvent(); + + @override + List get props => []; +} + +class GarageDoorLoading extends GarageDoorEvent {} + +class GarageDoorSwitch extends GarageDoorEvent { + final bool switchD; + final String deviceId; + final String productId; + const GarageDoorSwitch( + {required this.switchD, this.deviceId = '', this.productId = ''}); + + @override + List get props => [switchD, deviceId, productId]; +} + +class GarageDoorUpdated extends GarageDoorEvent {} + +class GarageDoorInitial extends GarageDoorEvent { + const GarageDoorInitial(); +} + +class ReportLogsInitial extends GarageDoorEvent { + const ReportLogsInitial(); +} + +class GarageDoorChangeStatus extends GarageDoorEvent {} + +class GetCounterEvent extends GarageDoorEvent { + final String deviceCode; + const GetCounterEvent({required this.deviceCode}); + @override + List get props => [deviceCode]; +} + +class ToggleLowBatteryEvent extends GarageDoorEvent { + final bool isLowBatteryEnabled; + + const ToggleLowBatteryEvent(this.isLowBatteryEnabled); + + @override + List get props => [isLowBatteryEnabled]; +} + +class ToggleClosingReminderEvent extends GarageDoorEvent { + final bool isClosingReminderEnabled; + + const ToggleClosingReminderEvent(this.isClosingReminderEnabled); + + @override + List get props => [isClosingReminderEnabled]; +} + +class ToggleDoorAlarmEvent extends GarageDoorEvent { + final bool isDoorAlarmEnabled; + + const ToggleDoorAlarmEvent(this.isDoorAlarmEnabled); + + @override + List get props => [isDoorAlarmEnabled]; +} + +class SetCounterValue extends GarageDoorEvent { + final Duration duration; + final String deviceCode; + const SetCounterValue({required this.duration, required this.deviceCode}); + @override + List get props => [duration, deviceCode]; +} + +class StartTimer extends GarageDoorEvent { + final int duration; + + const StartTimer(this.duration); + + @override + List get props => [duration]; +} + +class TickTimer extends GarageDoorEvent { + final int remainingTime; + + const TickTimer({required this.remainingTime}); + + @override + List get props => [remainingTime]; +} + +class StopTimer extends GarageDoorEvent {} + +class OnClose extends GarageDoorEvent {} + +//------------------- Schedule ----------=--------- +class GetScheduleEvent extends GarageDoorEvent {} + +class ScheduleSaveapp extends GarageDoorEvent {} + +class ToggleScheduleEvent extends GarageDoorEvent { + final String id; + final bool toggle; + const ToggleScheduleEvent({required this.toggle, required this.id}); + @override + List get props => [toggle, id]; +} + +class ToggleDaySelectionEvent extends GarageDoorEvent { + final String key; + + const ToggleDaySelectionEvent({required this.key}); + @override + List get props => [key]; +} + +class DeleteScheduleEvent extends GarageDoorEvent { + final String id; + const DeleteScheduleEvent({required this.id}); + @override + List get props => [id]; +} + +class ToggleSelectedEvent extends GarageDoorEvent { + final int index; + const ToggleSelectedEvent({required this.index}); + @override + List get props => [index]; +} + +class ToggleCreateScheduleEvent extends GarageDoorEvent { + final int index; + const ToggleCreateScheduleEvent({required this.index}); + @override + List get props => [index]; +} + +class ChangeFirstWizardSwitchStatusEvent extends GarageDoorEvent { + final bool value; + final String deviceId; + const ChangeFirstWizardSwitchStatusEvent( + {required this.value, this.deviceId = ''}); + @override + List get props => [value, deviceId]; +} + +class SelectSecondsEvent extends GarageDoorEvent { + final int seconds; + const SelectSecondsEvent({required this.seconds}); + @override + List get props => [seconds]; +} + +class ToggleDoorEvent extends GarageDoorEvent { + final bool toggle; + const ToggleDoorEvent({required this.toggle}); + @override + List get props => [toggle]; +} diff --git a/lib/features/devices/bloc/garage_door_bloc/garage_door_state.dart b/lib/features/devices/bloc/garage_door_bloc/garage_door_state.dart new file mode 100644 index 0000000..4cb4e6a --- /dev/null +++ b/lib/features/devices/bloc/garage_door_bloc/garage_door_state.dart @@ -0,0 +1,82 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/devices/model/garage_door_model.dart'; + +class GarageDoorSensorState extends Equatable { + const GarageDoorSensorState(); + + @override + List get props => []; +} + +class GarageDoorInitialState extends GarageDoorSensorState {} + +class GarageDoorLoadingState extends GarageDoorSensorState {} + +class GarageDoorFailedState extends GarageDoorSensorState { + final String errorMessage; + + const GarageDoorFailedState({required this.errorMessage}); + + @override + List get props => [errorMessage]; +} + +class UpdateState extends GarageDoorSensorState { + final GarageDoorModel garageSensor; + const UpdateState({required this.garageSensor}); + + @override + List get props => [garageSensor]; +} + +class LoadingNewSate extends GarageDoorSensorState { + final GarageDoorModel doorSensor; + const LoadingNewSate({required this.doorSensor}); + + @override + List get props => [doorSensor]; +} + +class ChangeSlidingSegmentState extends GarageDoorSensorState { + final int value; + + const ChangeSlidingSegmentState({required this.value}); + + @override + List get props => [value]; +} + +class UpdateTimerState extends GarageDoorSensorState { + final int seconds; + const UpdateTimerState({required this.seconds}); + + @override + List get props => [seconds]; +} + +class TimerRunInProgress extends GarageDoorSensorState { + final int remainingTime; + + const TimerRunInProgress(this.remainingTime); + + @override + List get props => [remainingTime]; +} + +class TimerRunComplete extends GarageDoorSensorState {} + +class SaveSchedule extends GarageDoorSensorState {} + +class IsToggleState extends GarageDoorSensorState { + final bool? onOff; + const IsToggleState({this.onOff}); +} + +class ChangeTimeState extends GarageDoorSensorState {} + +class LoadingInitialState extends GarageDoorSensorState {} + +class UpdateCreateScheduleState extends GarageDoorSensorState { + final bool createSchedule; + UpdateCreateScheduleState(this.createSchedule); +} diff --git a/lib/features/devices/bloc/one_touch_bloc/one_touch_bloc.dart b/lib/features/devices/bloc/one_touch_bloc/one_touch_bloc.dart index 14d85f3..3d6512f 100644 --- a/lib/features/devices/bloc/one_touch_bloc/one_touch_bloc.dart +++ b/lib/features/devices/bloc/one_touch_bloc/one_touch_bloc.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:dio/dio.dart'; import 'package:firebase_database/firebase_database.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; import 'package:syncrow_app/features/devices/bloc/one_touch_bloc/one_touch_state.dart'; diff --git a/lib/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart b/lib/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart index 087d569..242f2c5 100644 --- a/lib/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart +++ b/lib/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart @@ -31,7 +31,8 @@ class ThreeGangBloc extends Bloc { List groupThreeGangList = []; bool allSwitchesOn = true; - ThreeGangBloc({required this.threeGangId, required this.switchCode}) : super(InitialState()) { + ThreeGangBloc({required this.threeGangId, required this.switchCode}) + : super(InitialState()) { on(_fetchThreeGangStatus); on(_threeGangUpdated); on(_changeFirstSwitch); @@ -57,7 +58,8 @@ class ThreeGangBloc extends Bloc { on(toggleCreateSchedule); } - void _fetchThreeGangStatus(InitialEvent event, Emitter emit) async { + void _fetchThreeGangStatus( + InitialEvent event, Emitter emit) async { emit(LoadingInitialState()); try { threeGangGroup = event.groupScreen; @@ -69,7 +71,8 @@ class ThreeGangBloc extends Bloc { HomeCubit.getInstance().selectedSpace?.id ?? '', '3G'); for (int i = 0; i < devicesList.length; i++) { - var response = await DevicesAPI.getDeviceStatus(devicesList[i].uuid ?? ''); + var response = + await DevicesAPI.getDeviceStatus(devicesList[i].uuid ?? ''); List statusModelList = []; for (var status in response['status']) { statusModelList.add(StatusModel.fromJson(status)); @@ -86,13 +89,16 @@ class ThreeGangBloc extends Bloc { if (groupThreeGangList.isNotEmpty) { groupThreeGangList.firstWhere((element) { - if (!element.firstSwitch || !element.secondSwitch || !element.thirdSwitch) { + if (!element.firstSwitch || + !element.secondSwitch || + !element.thirdSwitch) { allSwitchesOn = false; } return true; }); } - emit(UpdateGroupState(threeGangList: groupThreeGangList, allSwitches: allSwitchesOn)); + emit(UpdateGroupState( + threeGangList: groupThreeGangList, allSwitches: allSwitchesOn)); } else { var response = await DevicesAPI.getDeviceStatus(threeGangId); List statusModelList = []; @@ -111,18 +117,21 @@ class ThreeGangBloc extends Bloc { _listenToChanges() { try { - DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$threeGangId'); + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$threeGangId'); Stream stream = ref.onValue; - stream.listen((DatabaseEvent event) async { + stream.listen((DatabaseEvent event) async { if (_timer != null) { await Future.delayed(const Duration(seconds: 2)); } - Map usersMap = event.snapshot.value as Map; + Map usersMap = + event.snapshot.value as Map; List statusList = []; usersMap['status'].forEach((element) { - statusList.add(StatusModel(code: element['code'], value: element['value'])); + statusList + .add(StatusModel(code: element['code'], value: element['value'])); }); deviceStatus = ThreeGangModel.fromJson(statusList); @@ -137,7 +146,8 @@ class ThreeGangBloc extends Bloc { emit(UpdateState(threeGangModel: deviceStatus)); } - void _changeFirstSwitch(ChangeFirstSwitchStatusEvent event, Emitter emit) async { + void _changeFirstSwitch( + ChangeFirstSwitchStatusEvent event, Emitter emit) async { emit(LoadingNewSate(threeGangModel: deviceStatus)); try { if (threeGangGroup) { @@ -146,11 +156,14 @@ class ThreeGangBloc extends Bloc { if (element.deviceId == event.deviceId) { element.firstSwitch = !event.value; } - if (!element.firstSwitch || !element.secondSwitch || !element.thirdSwitch) { + if (!element.firstSwitch || + !element.secondSwitch || + !element.thirdSwitch) { allSwitchesValue = false; } }); - emit(UpdateGroupState(threeGangList: groupThreeGangList, allSwitches: allSwitchesValue)); + emit(UpdateGroupState( + threeGangList: groupThreeGangList, allSwitches: allSwitchesValue)); } else { deviceStatus.firstSwitch = !event.value; emit(UpdateState(threeGangModel: deviceStatus)); @@ -187,11 +200,14 @@ class ThreeGangBloc extends Bloc { if (element.deviceId == event.deviceId) { element.secondSwitch = !event.value; } - if (!element.firstSwitch || !element.secondSwitch || !element.thirdSwitch) { + if (!element.firstSwitch || + !element.secondSwitch || + !element.thirdSwitch) { allSwitchesValue = false; } }); - emit(UpdateGroupState(threeGangList: groupThreeGangList, allSwitches: allSwitchesValue)); + emit(UpdateGroupState( + threeGangList: groupThreeGangList, allSwitches: allSwitchesValue)); } else { deviceStatus.secondSwitch = !event.value; emit(UpdateState(threeGangModel: deviceStatus)); @@ -217,7 +233,8 @@ class ThreeGangBloc extends Bloc { } } - void _changeThirdSwitch(ChangeThirdSwitchStatusEvent event, Emitter emit) async { + void _changeThirdSwitch( + ChangeThirdSwitchStatusEvent event, Emitter emit) async { emit(LoadingNewSate(threeGangModel: deviceStatus)); try { if (threeGangGroup) { @@ -226,11 +243,14 @@ class ThreeGangBloc extends Bloc { if (element.deviceId == event.deviceId) { element.thirdSwitch = !event.value; } - if (!element.firstSwitch || !element.secondSwitch || !element.thirdSwitch) { + if (!element.firstSwitch || + !element.secondSwitch || + !element.thirdSwitch) { allSwitchesValue = false; } }); - emit(UpdateGroupState(threeGangList: groupThreeGangList, allSwitches: allSwitchesValue)); + emit(UpdateGroupState( + threeGangList: groupThreeGangList, allSwitches: allSwitchesValue)); } else { deviceStatus.thirdSwitch = !event.value; emit(UpdateState(threeGangModel: deviceStatus)); @@ -269,15 +289,21 @@ class ThreeGangBloc extends Bloc { final response = await Future.wait([ DevicesAPI.controlDevice( DeviceControlModel( - deviceId: threeGangId, code: 'switch_1', value: deviceStatus.firstSwitch), + deviceId: threeGangId, + code: 'switch_1', + value: deviceStatus.firstSwitch), threeGangId), DevicesAPI.controlDevice( DeviceControlModel( - deviceId: threeGangId, code: 'switch_2', value: deviceStatus.secondSwitch), + deviceId: threeGangId, + code: 'switch_2', + value: deviceStatus.secondSwitch), threeGangId), DevicesAPI.controlDevice( DeviceControlModel( - deviceId: threeGangId, code: 'switch_3', value: deviceStatus.thirdSwitch), + deviceId: threeGangId, + code: 'switch_3', + value: deviceStatus.thirdSwitch), threeGangId), ]); @@ -303,15 +329,21 @@ class ThreeGangBloc extends Bloc { final response = await Future.wait([ DevicesAPI.controlDevice( DeviceControlModel( - deviceId: threeGangId, code: 'switch_1', value: deviceStatus.firstSwitch), + deviceId: threeGangId, + code: 'switch_1', + value: deviceStatus.firstSwitch), threeGangId), DevicesAPI.controlDevice( DeviceControlModel( - deviceId: threeGangId, code: 'switch_2', value: deviceStatus.secondSwitch), + deviceId: threeGangId, + code: 'switch_2', + value: deviceStatus.secondSwitch), threeGangId), DevicesAPI.controlDevice( DeviceControlModel( - deviceId: threeGangId, code: 'switch_3', value: deviceStatus.thirdSwitch), + deviceId: threeGangId, + code: 'switch_3', + value: deviceStatus.thirdSwitch), threeGangId), ]); @@ -333,21 +365,28 @@ class ThreeGangBloc extends Bloc { groupThreeGangList[i].secondSwitch = true; groupThreeGangList[i].thirdSwitch = true; } - emit(UpdateGroupState(threeGangList: groupThreeGangList, allSwitches: true)); + emit(UpdateGroupState( + threeGangList: groupThreeGangList, allSwitches: true)); for (int i = 0; i < groupThreeGangList.length; i++) { final response = await Future.wait([ DevicesAPI.controlDevice( DeviceControlModel( - deviceId: groupThreeGangList[i].deviceId, code: 'switch_1', value: true), + deviceId: groupThreeGangList[i].deviceId, + code: 'switch_1', + value: true), groupThreeGangList[i].deviceId), DevicesAPI.controlDevice( DeviceControlModel( - deviceId: groupThreeGangList[i].deviceId, code: 'switch_2', value: true), + deviceId: groupThreeGangList[i].deviceId, + code: 'switch_2', + value: true), groupThreeGangList[i].deviceId), DevicesAPI.controlDevice( DeviceControlModel( - deviceId: groupThreeGangList[i].deviceId, code: 'switch_3', value: true), + deviceId: groupThreeGangList[i].deviceId, + code: 'switch_3', + value: true), groupThreeGangList[i].deviceId), ]); @@ -363,7 +402,8 @@ class ThreeGangBloc extends Bloc { } } - void _groupAllOff(GroupAllOffEvent event, Emitter emit) async { + void _groupAllOff( + GroupAllOffEvent event, Emitter emit) async { emit(LoadingNewSate(threeGangModel: deviceStatus)); try { for (int i = 0; i < groupThreeGangList.length; i++) { @@ -371,21 +411,28 @@ class ThreeGangBloc extends Bloc { groupThreeGangList[i].secondSwitch = false; groupThreeGangList[i].thirdSwitch = false; } - emit(UpdateGroupState(threeGangList: groupThreeGangList, allSwitches: false)); + emit(UpdateGroupState( + threeGangList: groupThreeGangList, allSwitches: false)); for (int i = 0; i < groupThreeGangList.length; i++) { final response = await Future.wait([ DevicesAPI.controlDevice( DeviceControlModel( - deviceId: groupThreeGangList[i].deviceId, code: 'switch_1', value: false), + deviceId: groupThreeGangList[i].deviceId, + code: 'switch_1', + value: false), groupThreeGangList[i].deviceId), DevicesAPI.controlDevice( DeviceControlModel( - deviceId: groupThreeGangList[i].deviceId, code: 'switch_2', value: false), + deviceId: groupThreeGangList[i].deviceId, + code: 'switch_2', + value: false), groupThreeGangList[i].deviceId), DevicesAPI.controlDevice( DeviceControlModel( - deviceId: groupThreeGangList[i].deviceId, code: 'switch_3', value: false), + deviceId: groupThreeGangList[i].deviceId, + code: 'switch_3', + value: false), groupThreeGangList[i].deviceId), ]); @@ -401,17 +448,20 @@ class ThreeGangBloc extends Bloc { } } - void _changeSliding(ChangeSlidingSegment event, Emitter emit) async { + void _changeSliding( + ChangeSlidingSegment event, Emitter emit) async { emit(ChangeSlidingSegmentState(value: event.value)); } - void _setCounterValue(SetCounterValue event, Emitter emit) async { + void _setCounterValue( + SetCounterValue event, Emitter emit) async { emit(LoadingNewSate(threeGangModel: deviceStatus)); int seconds = 0; try { seconds = event.duration.inSeconds; final response = await DevicesAPI.controlDevice( - DeviceControlModel(deviceId: threeGangId, code: event.deviceCode, value: seconds), + DeviceControlModel( + deviceId: threeGangId, code: event.deviceCode, value: seconds), threeGangId); if (response['success'] ?? false) { @@ -438,7 +488,8 @@ class ThreeGangBloc extends Bloc { } } - void _getCounterValue(GetCounterEvent event, Emitter emit) async { + void _getCounterValue( + GetCounterEvent event, Emitter emit) async { emit(LoadingInitialState()); try { var response = await DevicesAPI.getDeviceStatus(threeGangId); @@ -541,6 +592,8 @@ class ThreeGangBloc extends Bloc { GetScheduleEvent event, Emitter emit, ) async { + print('getSchedule=${switchCode}'); + try { emit(LoadingInitialState()); final response = await DevicesAPI.getSchedule( @@ -548,7 +601,8 @@ class ThreeGangBloc extends Bloc { deviceId: threeGangId, ); List jsonData = response; - listSchedule = jsonData.map((item) => ScheduleModel.fromJson(item)).toList(); + listSchedule = + jsonData.map((item) => ScheduleModel.fromJson(item)).toList(); emit(InitialState()); } on DioException catch (e) { final errorData = e.response!.data; @@ -559,12 +613,13 @@ class ThreeGangBloc extends Bloc { int? getTimeStampWithoutSeconds(DateTime? dateTime) { if (dateTime == null) return null; - DateTime dateTimeWithoutSeconds = - DateTime(dateTime.year, dateTime.month, dateTime.day, dateTime.hour, dateTime.minute); + DateTime dateTimeWithoutSeconds = DateTime(dateTime.year, dateTime.month, + dateTime.day, dateTime.hour, dateTime.minute); return dateTimeWithoutSeconds.millisecondsSinceEpoch ~/ 1000; } - Future toggleChange(ToggleScheduleEvent event, Emitter emit) async { + Future toggleChange( + ToggleScheduleEvent event, Emitter emit) async { try { emit(LoadingInitialState()); final response = await DevicesAPI.changeSchedule( @@ -583,7 +638,8 @@ class ThreeGangBloc extends Bloc { } } - Future deleteSchedule(DeleteScheduleEvent event, Emitter emit) async { + Future deleteSchedule( + DeleteScheduleEvent event, Emitter emit) async { try { emit(LoadingInitialState()); final response = await DevicesAPI.deleteSchedule( @@ -603,13 +659,15 @@ class ThreeGangBloc extends Bloc { } } - void toggleSelectedIndex(ToggleSelectedEvent event, Emitter emit) { + void toggleSelectedIndex( + ToggleSelectedEvent event, Emitter emit) { emit(LoadingInitialState()); selectedTabIndex = event.index; emit(ChangeSlidingSegmentState(value: selectedTabIndex)); } - void toggleCreateSchedule(ToggleCreateScheduleEvent event, Emitter emit) { + void toggleCreateSchedule( + ToggleCreateScheduleEvent event, Emitter emit) { emit(LoadingInitialState()); createSchedule = !createSchedule; selectedDays.clear(); diff --git a/lib/features/devices/model/garage_door_model.dart b/lib/features/devices/model/garage_door_model.dart new file mode 100644 index 0000000..146523a --- /dev/null +++ b/lib/features/devices/model/garage_door_model.dart @@ -0,0 +1,81 @@ +import 'package:syncrow_app/features/devices/model/status_model.dart'; + +class GarageDoorModel { + bool switch1; + bool doorContactState; + int countdown1; + int countdownAlarm; + String doorControl1; + bool voiceControl1; + String doorState1; + int batteryPercentage; + int tr_timecon; + + GarageDoorModel({ + required this.switch1, + required this.doorContactState, + required this.countdown1, + required this.countdownAlarm, + required this.doorControl1, + required this.voiceControl1, + required this.doorState1, + required this.batteryPercentage, + required this.tr_timecon, + }); + + factory GarageDoorModel.fromJson(List jsonList) { + late bool _switch1 = false; + late bool _doorContactState = false; + late int _countdown1 = 0; + late int _countdownAlarm = 0; + late String _doorControl1 = "closed"; + late bool _voiceControl1 = false; + late String _doorState1 = "closed"; + late int _batteryPercentage = 0; + late int _tr_timecon = 0; + + for (var status in jsonList) { + switch (status.code) { + case 'tr_timecon': + _tr_timecon = status.value ?? "closed"; + break; + case 'switch_1': + _switch1 = status.value ?? false; + break; + case 'doorcontact_state': + _doorContactState = status.value ?? false; + break; + case 'countdown_1': + _countdown1 = 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; + case 'battery_percentage': + _batteryPercentage = status.value ?? 0; + break; + } + } + + return GarageDoorModel( + switch1: _switch1, + doorContactState: _doorContactState, + countdown1: _countdown1, + countdownAlarm: _countdownAlarm, + doorControl1: _doorControl1, + voiceControl1: _voiceControl1, + doorState1: _doorState1, + batteryPercentage: _batteryPercentage, + tr_timecon: _tr_timecon); + } +} diff --git a/lib/features/devices/view/widgets/garage_door/create_schedule_garage.dart b/lib/features/devices/view/widgets/garage_door/create_schedule_garage.dart new file mode 100644 index 0000000..9410935 --- /dev/null +++ b/lib/features/devices/view/widgets/garage_door/create_schedule_garage.dart @@ -0,0 +1,204 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/devices/view/widgets/garage_door/garagedialog.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class CreateGarageSchedule extends StatefulWidget { + final List> days; + final void Function(List selectedDays)? + selectDays; + final Function(DateTime) onDateTimeChanged; + final void Function(bool isOn)? onToggleChanged; + + const CreateGarageSchedule({ + Key? key, + required this.days, + required this.selectDays, + required this.onDateTimeChanged, + this.onToggleChanged, + }) : super(key: key); + + @override + _CreateGarageScheduleState createState() => _CreateGarageScheduleState(); +} + +class _CreateGarageScheduleState extends State { + List selectedDays = []; + bool isOn = true; + String selectedControlOption = 'Open'; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), + child: Column( + children: [ + const Divider( + color: ColorsManager.greyColor, + ), + SizedBox( + height: 110, + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.time, + initialDateTime: DateTime.now(), + onDateTimeChanged: widget.onDateTimeChanged, + ), + ), + const Divider( + color: ColorsManager.greyColor, + ), + const SizedBox(height: 20), + SizedBox( + height: MediaQuery.of(context).size.height * 0.08, + child: ListView( + scrollDirection: Axis.horizontal, + children: widget.days.map((day) { + bool isSelected = selectedDays.contains(day['day']); + return Padding( + padding: const EdgeInsets.all(8.0), + child: InkWell( + onTap: () { + setState(() { + if (isSelected) { + selectedDays.remove(day['day']); + } else { + selectedDays.add(day['day']!); + } + if (widget.selectDays != null) { + widget.selectDays!(selectedDays); + } + }); + }, + child: Container( + width: 50, + padding: const EdgeInsets.symmetric( + vertical: 8, horizontal: 8), + decoration: BoxDecoration( + border: Border.all( + color: isSelected + ? Colors.black + : ColorsManager.grayColor, + ), + color: Colors.transparent, + borderRadius: BorderRadius.circular(30), + ), + child: Center( + child: Text( + day['day']!, + style: TextStyle( + fontSize: 13, + color: isSelected + ? Colors.black + : ColorsManager.grayColor, + ), + ), + ), + ), + ), + ); + }).toList(), + ), + ), + const SizedBox(height: 20), + DefaultContainer( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 50, + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: const BodyMedium( + text: 'Notification', + fontWeight: FontWeight.normal, + ), + trailing: Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: true, + onChanged: (value) { + }, + applyTheme: true, + )), + ), + ), + const Divider( + color: ColorsManager.graysColor, + ), + SizedBox( + height: 50, + child: InkWell( + onTap: () { + showDialog( + context: context, + builder: (context) { + return GarageDialog( + label2: 'Close', + initialSelectedLabel: selectedControlOption, + cancelTab: () { + Navigator.of(context).pop(); + }, + confirmTab: () { + setState(() {}); + if (selectedControlOption == "Open") { + widget.onToggleChanged!(true); + } else if (selectedControlOption == 'Close') { + widget.onToggleChanged!(false); + } + + Navigator.of(context).pop(); + }, + title: 'Control', + label1: 'Open', + onTapLabel1: (selected) { + setState(() { + selectedControlOption = 'Open'; + }); + }, + onTapLabel2: (selected) { + setState(() { + selectedControlOption = 'Close'; + }); + }, + ); + }, + ); + }, + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: const BodyMedium( + text: 'Control', + fontWeight: FontWeight.normal, + ), + trailing: SizedBox( + width: 70, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyMedium( + fontColor: ColorsManager.textGray, + text: selectedControlOption, + fontWeight: FontWeight.normal, + ), + const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.textGray, + size: 17, + ) + ], + ), + )), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/garage_door/garage_door_screen.dart b/lib/features/devices/view/widgets/garage_door/garage_door_screen.dart new file mode 100644 index 0000000..8fb6a9f --- /dev/null +++ b/lib/features/devices/view/widgets/garage_door/garage_door_screen.dart @@ -0,0 +1,428 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_event.dart'; +import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/garage_door_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/garage_door/garage_preferences_settings.dart'; +import 'package:syncrow_app/features/devices/view/widgets/garage_door/garage_records_screen.dart'; +import 'package:syncrow_app/features/devices/view/widgets/garage_door/schedule_garage_screen.dart'; +import 'package:syncrow_app/features/devices/view/widgets/garage_door/timer_page.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/generated/assets.dart'; + +class GarageDoorScreen extends StatelessWidget { + final DeviceModel? device; + + const GarageDoorScreen({super.key, this.device}); + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Garage Door Opener', + child: BlocProvider( + create: (context) => GarageDoorBloc(GDId: device?.uuid ?? '') + ..add(const GarageDoorInitial()), + child: BlocBuilder( + builder: (context, state) { + final garageBloc = BlocProvider.of(context); + GarageDoorModel model = GarageDoorModel( + tr_timecon: 0, + countdown1: 0, + countdownAlarm: 0, + doorContactState: false, + doorControl1: '', + doorState1: '', + switch1: false, + voiceControl1: false, + batteryPercentage: 0, + ); + + if (state is LoadingNewSate) { + model = state.doorSensor; + } else if (state is UpdateState) { + model = state.garageSensor; + } + return state is GarageDoorLoadingState + ? const Center( + child: DefaultContainer( + width: 50, + height: 50, + child: CircularProgressIndicator()), + ) + : RefreshIndicator( + onRefresh: () async { + garageBloc.add(const GarageDoorInitial()); + }, + child: ListView( + children: [ + SizedBox( + height: MediaQuery.sizeOf(context).height * 0.8, + child: Column( + children: [ + Expanded( + flex: 4, + child: InkWell( + overlayColor: WidgetStateProperty.all( + Colors.transparent), + onTap: () {}, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + Container( + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(890), + boxShadow: [ + BoxShadow( + color: Colors.white + .withOpacity(0.1), + blurRadius: 24, + offset: const Offset(-5, -5), + blurStyle: BlurStyle.outer, + ), + BoxShadow( + color: Colors.black + .withOpacity(0.11), + blurRadius: 25, + offset: const Offset(5, 5), + blurStyle: BlurStyle.outer, + ), + BoxShadow( + color: Colors.black + .withOpacity(0.13), + blurRadius: 30, + offset: const Offset(5, 5), + blurStyle: BlurStyle.inner, + ), + ], + ), + child: InkWell( + onTap: () { + garageBloc.add(ToggleDoorEvent( + toggle: + garageBloc.toggleDoor)); + }, + child: GradientWidget( + doorStatus: garageBloc.toggleDoor, + seconds: + garageBloc.secondSelected, + ), + )), + ], + ), + ), + ), + Flexible( + child: Row( + children: [ + Expanded( + child: DefaultContainer( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + TimerScheduleScreen( + device: device!, + switchCode: 'switch_1', + deviceCode: 'countdown_1', + )), + ); + }, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + ConstrainedBox( + constraints: const BoxConstraints( + maxHeight: 46, maxWidth: 50), + child: SvgPicture.asset( + Assets.garageSchedule), + ), + const SizedBox( + height: 15, + ), + const Flexible( + child: FittedBox( + child: BodySmall( + text: 'Schedule', + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: DefaultContainer( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TimerPage( + device: device!, + switchCode: 'switch_1', + deviceCode: 'countdown_1', + )), + ); + }, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + ConstrainedBox( + constraints: const BoxConstraints( + maxHeight: 46, maxWidth: 50), + child: SvgPicture.asset( + Assets.garageCountdown), + ), + const SizedBox( + height: 15, + ), + const Flexible( + child: FittedBox( + child: BodySmall( + text: 'Countdown', + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + const SizedBox( + height: 10, + ), + Flexible( + child: Row( + children: [ + Expanded( + child: DefaultContainer( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + GarageRecordsScreen( + GDId: device!.uuid!)), + ); + }, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + ConstrainedBox( + constraints: const BoxConstraints( + maxHeight: 46, maxWidth: 50), + child: SvgPicture.asset( + Assets.doorRecordsIcon), + ), + const SizedBox( + height: 15, + ), + const Flexible( + child: FittedBox( + child: BodySmall( + text: 'Records', + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: DefaultContainer( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + PreferencesPage( + GDId: device!.uuid!)), + ); + }, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + ConstrainedBox( + constraints: const BoxConstraints( + maxHeight: 46, maxWidth: 50), + child: SvgPicture.asset( + Assets.garagePreferencesIcon), + ), + const SizedBox( + height: 15, + ), + const Flexible( + child: FittedBox( + child: BodySmall( + text: 'Preferences', + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ) + ], + ), + ), + ], + ), + ); + }, + ), + ), + ); + } +} + +class GradientWidget extends StatefulWidget { + const GradientWidget({required this.seconds, required this.doorStatus}); + final int seconds; + final bool doorStatus; + + @override + State createState() => _GradientWidgetState(); +} + +class _GradientWidgetState extends State + with SingleTickerProviderStateMixin { + late ScrollController _scrollController; + late AnimationController _animationController; + late Animation _itemExtentAnimation; + + @override + void initState() { + super.initState(); + _scrollController = ScrollController(); + _animationController = AnimationController( + vsync: this, + duration: Duration(seconds: widget.seconds), + ); + _itemExtentAnimation = Tween( + begin: widget.doorStatus ? 0 : 15, end: widget.doorStatus ? 15 : 0) + .animate( + CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + ), + )..addListener(() { + setState(() {}); + }); + + WidgetsBinding.instance.addPostFrameCallback((_) { + if (widget.doorStatus) { + _openAnimation(); + } else { + _closeAnimation(); + } + }); + } + + void _openAnimation() { + _animationController.forward(); + } + + void _closeAnimation() { + _animationController.reverse(); + } + + @override + void dispose() { + _scrollController.dispose(); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + width: 300, + height: 300, + padding: EdgeInsets.only(right: 10), + child: Stack( + children: [ + SvgPicture.asset( + Assets.openGarageIcon, + fit: BoxFit.fill, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded(child: Container()), + Expanded( + flex: 2, + child: Center( + child: Container( + height: 104, + child: ListView.builder( + itemExtent: _itemExtentAnimation.value, + controller: _scrollController, + itemCount: 4, + itemBuilder: (context, index) { + return Center(child: GradientWidget1()); + }, + ), + ), + ), + ), + ], + ), + ], + ), + ); + } +} + +class GradientWidget1 extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + width: 97, + height: 15, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xFF017297), + Color(0xFF024C67), + ], + stops: [0.0, 1.0], + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/garage_door/garage_preferences_settings.dart b/lib/features/devices/view/widgets/garage_door/garage_preferences_settings.dart new file mode 100644 index 0000000..e591b33 --- /dev/null +++ b/lib/features/devices/view/widgets/garage_door/garage_preferences_settings.dart @@ -0,0 +1,341 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_event.dart'; +import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_state.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class PreferencesPage extends StatelessWidget { + final String GDId; + const PreferencesPage({super.key, required this.GDId}); + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Preferences', + child: BlocProvider( + create: (context) => + GarageDoorBloc(GDId: GDId)..add(const GarageDoorInitial()), + child: BlocBuilder( + builder: (context, state) { + final garageDoorBloc = BlocProvider.of(context); + + return state is GarageDoorLoadingState + ? const Center( + child: DefaultContainer( + width: 50, + height: 50, + child: CircularProgressIndicator()), + ) + : Column( + children: [ + const Row( + children: [ + BodyMedium( + text: 'Warnings', + fontColor: ColorsManager.grayColor, + fontWeight: FontWeight.w700, + ), + ], + ), + DefaultContainer( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 50, + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: const BodyMedium( + text: 'Alarm when door is open', + fontWeight: FontWeight.normal, + ), + trailing: Container( + width: 100, + child: Row( + mainAxisAlignment: + MainAxisAlignment.end, + children: [ + Container( + height: 30, + width: 1, + color: ColorsManager.graysColor, + ), + Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: + garageDoorBloc.lowBattery, + onChanged: (value) { + // context + // .read() + // .add( + // ToggleLowBatteryEvent( + // value)); + }, + applyTheme: true, + )), + ], + ), + )), + ), + ], + ), + ), + const SizedBox( + height: 10, + ), + const Row( + children: [ + BodyMedium( + text: 'Other Settings', + fontColor: ColorsManager.grayColor, + fontWeight: FontWeight.w700, + ), + ], + ), + DefaultContainer( + child: InkWell( + onTap: () { + showDialog( + context: context, + builder: (context) { + return SecondDialog( + label2: 'Close', + initialSelectedLabel: garageDoorBloc + .secondSelected + .toString(), + cancelTab: () { + Navigator.of(context).pop(); + }, + confirmTab: (v) { + garageDoorBloc + .add(SelectSecondsEvent(seconds: v)); + Navigator.of(context).pop(); + }, + title: 'Control', + label1: 'Open', + onTapLabel1: (selected) {}, + ); + }, + ); + }, + child: SizedBox( + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: const BodyMedium( + text: 'Opening and Closing Time', + fontWeight: FontWeight.normal, + ), + trailing: Container( + height: 90, + width: 120, + child: Row( + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + BodyMedium( + fontColor: ColorsManager.textGray, + text: + '${garageDoorBloc.secondSelected.toString()} Seconds', + fontWeight: FontWeight.normal, + ), + const Icon( + Icons.arrow_forward_ios, + size: 15, + color: ColorsManager.textGray, + ) + ], + ), + )), + ), + ), + ) + ], + ); + }, + ), + )); + } +} + +class SecondDialog extends StatefulWidget { + final String label1; + final String label2; + final String title; + + final Function(String)? onTapLabel1; + final Function()? cancelTab; + final Function(int selectedSecond)? confirmTab; + + final String? initialSelectedLabel; + + SecondDialog({ + required this.label1, + required this.label2, + required this.title, + this.onTapLabel1, + required this.cancelTab, + required this.confirmTab, + this.initialSelectedLabel, + }); + + @override + _SecondDialogState createState() => _SecondDialogState(); +} + +class _SecondDialogState extends State { + late String _selectedOption; + late int selectedSecond; + + @override + void initState() { + super.initState(); + // Parse the initialSelectedLabel as an integer. Default to 10 if invalid or not provided. + selectedSecond = int.tryParse(widget.initialSelectedLabel ?? '10') ?? 10; + _selectedOption = widget.initialSelectedLabel ?? ''; + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 10, + ), + BodyLarge( + text: widget.title, + fontWeight: FontWeight.w700, + fontColor: ColorsManager.primaryColor, + fontSize: 16, + ), + const Padding( + padding: EdgeInsets.only(left: 15, right: 15), + child: Divider( + color: ColorsManager.textGray, + ), + ), + Padding( + padding: const EdgeInsets.only(left: 15, right: 15, bottom: 10), + child: Column( + children: [ + Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 100, + child: CupertinoPicker( + itemExtent: 40.0, + scrollController: FixedExtentScrollController( + // Set the initial position based on selectedSecond + initialItem: selectedSecond - 10, + ), + onSelectedItemChanged: (int index) { + setState(() { + selectedSecond = index + 10; + }); + }, + children: List.generate(111, (int index) { + return Center( + child: BodyLarge( + text: (index + 10).toString().padLeft(2, '0'), + style: const TextStyle( + fontWeight: FontWeight.w400, + fontSize: 30, + color: Colors.blue), + ), + ); + }), + ), + ), + const Text('Sec'), + ], + ), + ) + ], + ), + ), + Row( + children: [ + Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.textGray, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.textGray, + width: 1.0, + ), + )), + child: SizedBox( + child: InkWell( + onTap: widget.cancelTab, + child: const Padding( + padding: EdgeInsets.all(15), + child: Center( + child: Text( + 'Cancel', + style: TextStyle( + color: ColorsManager.textGray, + fontSize: 14, + fontWeight: FontWeight.w400), + ), + ), + ), + ), + ), + ), + ), + Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border( + left: BorderSide( + color: ColorsManager.textGray, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.textGray, + width: 1.0, + ), + )), + child: InkWell( + onTap: () { + widget.confirmTab?.call(selectedSecond); + }, + child: const Padding( + padding: EdgeInsets.all(15), + child: Center( + child: Text( + 'Confirm', + style: TextStyle( + color: ColorsManager.primaryColor, + fontSize: 14, + fontWeight: FontWeight.w400), + ), + ), + )), + )) + ], + ) + ], + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/garage_door/garage_records_screen.dart b/lib/features/devices/view/widgets/garage_door/garage_records_screen.dart new file mode 100644 index 0000000..f2389e1 --- /dev/null +++ b/lib/features/devices/view/widgets/garage_door/garage_records_screen.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:intl/intl.dart'; +import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_event.dart'; +import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_state.dart'; +import 'package:syncrow_app/features/devices/model/device_report_model.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class GarageRecordsScreen extends StatelessWidget { + final String GDId; + const GarageRecordsScreen({super.key, required this.GDId}); + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Records', + child: BlocProvider( + create: (context) => + GarageDoorBloc(GDId: GDId)..add(const ReportLogsInitial()), + child: BlocBuilder( + builder: (context, state) { + final garageDoorBloc = BlocProvider.of(context); + final Map> groupedRecords = {}; + + if (state is GarageDoorLoadingState) { + return const Center( + child: DefaultContainer( + width: 50, height: 50, child: CircularProgressIndicator()), + ); + } else if (state is UpdateState) { + for (var record in garageDoorBloc.recordGroups.data!) { + final DateTime eventDateTime = + DateTime.fromMillisecondsSinceEpoch(record.eventTime!); + final String formattedDate = + DateFormat('EEEE, dd/MM/yyyy').format(eventDateTime); + + // Group by formatted date + if (groupedRecords.containsKey(formattedDate)) { + groupedRecords[formattedDate]!.add(record); + } else { + groupedRecords[formattedDate] = [record]; + } + } + } + return ListView.builder( + itemCount: groupedRecords.length, + itemBuilder: (context, index) { + final String date = groupedRecords.keys.elementAt(index); + final List recordsForDate = groupedRecords[date]!; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 5, top: 10), + child: Text( + date, + style: const TextStyle( + color: ColorsManager.grayColor, + fontSize: 13, + fontWeight: FontWeight.w700, + ), + ), + ), + DefaultContainer( + child: Column( + children: [ + ...recordsForDate.asMap().entries.map((entry) { + final int idx = entry.key; + final DeviceEvent record = entry.value; + final DateTime eventDateTime = + DateTime.fromMillisecondsSinceEpoch( + record.eventTime!); + final String formattedTime = + DateFormat('HH:mm:ss').format(eventDateTime); + + return Column( + children: [ + Container( + child: ListTile( + leading: Icon( + record.value == 'true' + ? Icons.radio_button_checked + : Icons.radio_button_unchecked, + color: record.value == 'true' + ? Colors.blue + : Colors.grey, + ), + title: Text( + record.value == 'true' + ? "Opened" + : "Closed", + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + subtitle: Text('$formattedTime'), + ), + ), + if (idx != recordsForDate.length - 1) + const Divider( + color: ColorsManager.graysColor, + ), + ], + ); + }).toList(), + ], + ), + ), + ], + ); + }, + ); + }), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/garage_door/garagedialog.dart b/lib/features/devices/view/widgets/garage_door/garagedialog.dart new file mode 100644 index 0000000..d002d0b --- /dev/null +++ b/lib/features/devices/view/widgets/garage_door/garagedialog.dart @@ -0,0 +1,221 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class GarageDialog extends StatefulWidget { + final String label1; + final String label2; + final String title; + + final Function(String)? onTapLabel1; + final Function(String)? onTapLabel2; + final Function()? cancelTab; + final Function()? confirmTab; + + final String? initialSelectedLabel; + + GarageDialog({ + required this.label1, + required this.label2, + required this.title, + this.onTapLabel1, + this.onTapLabel2, + required this.cancelTab, + required this.confirmTab, + this.initialSelectedLabel, + }); + + @override + _GarageDialogState createState() => _GarageDialogState(); +} + +class _GarageDialogState extends State { + late String _selectedOption; + + @override + void initState() { + super.initState(); + + _selectedOption = widget.initialSelectedLabel ?? ''; + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 10, + ), + BodyLarge( + text: widget.title, + fontWeight: FontWeight.w700, + fontColor: ColorsManager.primaryColor, + fontSize: 16, + ), + const Padding( + padding: EdgeInsets.only(left: 15, right: 15), + child: Divider( + color: ColorsManager.textGray, + ), + ), + Padding( + padding: const EdgeInsets.only(left: 15, right: 15), + child: Column( + children: [ + _buildCheckboxOption( + label: widget.label1, + onTap: widget.onTapLabel1, + ), + _buildCheckboxOption( + label: widget.label2, + onTap: widget.onTapLabel2, + ), + ], + ), + ), + Row( + children: [ + Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.textGray, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.textGray, + width: 1.0, + ), + )), + child: SizedBox( + child: InkWell( + onTap: widget.cancelTab, + child: const Padding( + padding: EdgeInsets.all(15), + child: Center( + child: Text( + 'Cancel', + style: TextStyle( + color: ColorsManager.textGray, + fontSize: 14, + fontWeight: FontWeight.w400), + ), + ), + ), + ), + ), + ), + ), + Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border( + left: BorderSide( + color: ColorsManager.textGray, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.textGray, + width: 1.0, + ), + )), + child: InkWell( + onTap: widget.confirmTab, + child: const Padding( + padding: EdgeInsets.all(15), + child: Center( + child: Text( + 'Confirm', + style: TextStyle( + color: ColorsManager.primaryColor, + fontSize: 14, + fontWeight: FontWeight.w400), + ), + ), + )), + )) + ], + ) + ], + ), + ); + } + + Widget _buildCheckboxOption( + {required String label, Function(String)? onTap}) { + return Padding( + padding: const EdgeInsets.only(bottom: 10, top: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyMedium( + text: label, + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w400), + ), + CircularCheckbox( + value: _selectedOption == label, + onChanged: (bool? value) { + if (value == true) { + setState(() { + _selectedOption = label; + }); + if (onTap != null) { + onTap(label); + } + } + }, + ), + ], + ), + ); + } +} + +class CircularCheckbox extends StatefulWidget { + final bool value; + final ValueChanged onChanged; + + CircularCheckbox({required this.value, required this.onChanged}); + + @override + _CircularCheckboxState createState() => _CircularCheckboxState(); +} + +class _CircularCheckboxState extends State { + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + widget.onChanged(!widget.value); + }, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: widget.value + ? ColorsManager.primaryColorWithOpacity.withOpacity(0.01) + : Colors.grey, + width: 2.0, + ), + color: widget.value + ? ColorsManager.primaryColorWithOpacity + : Colors.transparent, + ), + width: 24.0, + height: 24.0, + child: widget.value + ? const Icon( + Icons.check, + color: Colors.white, + size: 16.0, + ) + : null, + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/garage_door/schedule_garage_screen.dart b/lib/features/devices/view/widgets/garage_door/schedule_garage_screen.dart new file mode 100644 index 0000000..ba56a98 --- /dev/null +++ b/lib/features/devices/view/widgets/garage_door/schedule_garage_screen.dart @@ -0,0 +1,163 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_event.dart'; +import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/garage_door/create_schedule_garage.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/schedule_list.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; + +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class TimerScheduleScreen extends StatelessWidget { + final DeviceModel device; + final String deviceCode; + final String switchCode; + const TimerScheduleScreen( + {required this.device, + required this.deviceCode, + required this.switchCode, + super.key}); + + @override + Widget build(BuildContext context) { + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarColor: ColorsManager.primaryColor.withOpacity(0.5), + statusBarIconBrightness: Brightness.light, + ), + child: BlocProvider( + create: (context) => + GarageDoorBloc(GDId: device.uuid ?? '')..add(GetScheduleEvent()), + child: BlocBuilder( + builder: (context, state) { + final garageBloc = BlocProvider.of(context); + Duration duration = Duration.zero; + int countNum = 0; + if (state is UpdateTimerState) { + countNum = state.seconds; + } else if (state is TimerRunInProgress) { + countNum = state.remainingTime; + } else if (state is TimerRunComplete) { + countNum = 0; + } else if (state is LoadingNewSate) { + countNum = 0; + } + return PopScope( + canPop: false, + onPopInvoked: (didPop) { + if (!didPop) { + garageBloc.add(OnClose()); + Navigator.pop(context); + } + }, + child: DefaultTabController( + length: 2, + child: DefaultScaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + title: const BodyLarge( + text: 'Schedule', + fontColor: ColorsManager.primaryColor, + fontWeight: FontsManager.bold, + ), + actions: [ + garageBloc.createSchedule == true + ? TextButton( + onPressed: () { + garageBloc.add(ScheduleSaveapp()); + }, + child: const Text('Save')) + : IconButton( + onPressed: () { + garageBloc.add( + const ToggleCreateScheduleEvent( + index: 1)); + }, + icon: const Icon(Icons.add), + ) + ], + ), + child: state is GarageDoorLoading + ? const Center(child: CircularProgressIndicator()) + : Column( + children: [ + Container( + width: MediaQuery.of(context).size.width, + decoration: const ShapeDecoration( + color: ColorsManager.onPrimaryColor, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.all(Radius.circular(30)), + ), + ), + ), + Expanded( + child: SizedBox( + child: garageBloc.createSchedule == true + ? CreateGarageSchedule( + + onToggleChanged: (bool value) { + garageBloc.toggleSchedule = value; + }, + onDateTimeChanged: + (DateTime dateTime) { + garageBloc.selectedTime = + dateTime; + }, + days: garageBloc.days, + selectDays: + (List selectedDays) { + garageBloc.selectedDays = + selectedDays; + }, + ) + : Padding( + padding: + const EdgeInsets.only(top: 10), + child: ScheduleListView( + listSchedule: + garageBloc.listSchedule, + onDismissed: (scheduleId) { + garageBloc.listSchedule + .removeWhere((schedule) => + schedule.scheduleId == + scheduleId); + garageBloc.add( + DeleteScheduleEvent( + id: scheduleId)); + }, + onToggleSchedule: + (scheduleId, isEnabled) { + garageBloc + .add(ToggleScheduleEvent( + id: scheduleId, + toggle: isEnabled, + )); + }, + ), + ), + ), + ), + ], + ), + ))); + }, + ), + ), + ); + } + + String _formatDuration(int seconds) { + final hours = (seconds ~/ 3600).toString().padLeft(2, '0'); + final minutes = ((seconds % 3600) ~/ 60).toString().padLeft(2, '0'); + final secs = (seconds % 60).toString().padLeft(2, '0'); + return '$hours:$minutes:$secs'; + } +} diff --git a/lib/features/devices/view/widgets/garage_door/timer_page.dart b/lib/features/devices/view/widgets/garage_door/timer_page.dart new file mode 100644 index 0000000..47c1b37 --- /dev/null +++ b/lib/features/devices/view/widgets/garage_door/timer_page.dart @@ -0,0 +1,148 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_event.dart'; +import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class TimerPage extends StatelessWidget { + final DeviceModel device; + final String deviceCode; + final String switchCode; + const TimerPage( + {required this.device, + required this.deviceCode, + required this.switchCode, + super.key}); + + @override + Widget build(BuildContext context) { + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarColor: ColorsManager.primaryColor.withOpacity(0.5), + statusBarIconBrightness: Brightness.light, + ), + child: BlocProvider( + create: (context) => GarageDoorBloc(GDId: device.uuid ?? '') + ..add(GetCounterEvent(deviceCode: deviceCode)), + child: BlocBuilder( + builder: (context, state) { + final oneGangBloc = BlocProvider.of(context); + Duration duration = Duration.zero; + int countNum = 0; + if (state is UpdateTimerState) { + countNum = state.seconds; + } else if (state is TimerRunInProgress) { + countNum = state.remainingTime; + } else if (state is TimerRunComplete) { + countNum = 0; + } else if (state is LoadingNewSate) { + countNum = 0; + } + return PopScope( + canPop: false, + onPopInvoked: (didPop) { + if (!didPop) { + oneGangBloc.add(OnClose()); + Navigator.pop(context); + } + }, + child: DefaultTabController( + length: 2, + child: DefaultScaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + title: const BodyLarge( + text: 'Countdown', + fontColor: ColorsManager.primaryColor, + fontWeight: FontsManager.bold, + ), + ), + child: state is GarageDoorLoadingState + ? const Center(child: CircularProgressIndicator()) + : Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: MediaQuery.of(context).size.width, + decoration: const ShapeDecoration( + color: ColorsManager.onPrimaryColor, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.all(Radius.circular(30)), + ), + ), + ), + Center( + child: Container( + child: Column( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + countNum > 0 + ? BodyLarge( + text: _formatDuration(countNum), + fontColor: ColorsManager + .slidingBlueColor, + fontSize: 40, + ) + : CupertinoTimerPicker( + mode: + CupertinoTimerPickerMode.hm, + onTimerDurationChanged: + (Duration newDuration) { + duration = newDuration; + }, + ), + GestureDetector( + onTap: () { + if (state is LoadingNewSate) { + return; + } + if (countNum > 0) { + oneGangBloc.add( + const SetCounterValue( + deviceCode: + 'countdown_1', + duration: + Duration.zero)); + } else if (duration != + Duration.zero) { + oneGangBloc.add(SetCounterValue( + deviceCode: 'countdown_1', + duration: duration)); + } + }, + child: SvgPicture.asset(countNum > 0 + ? Assets.pauseIcon + : Assets.playIcon)), + ], + ), + ), + ), + ], + ), + ))); + }, + ), + ), + ); + } + + String _formatDuration(int seconds) { + final hours = (seconds ~/ 3600).toString().padLeft(2, '0'); + final minutes = ((seconds % 3600) ~/ 60).toString().padLeft(2, '0'); + final secs = (seconds % 60).toString().padLeft(2, '0'); + return '$hours:$minutes:$secs'; + } +} diff --git a/lib/features/devices/view/widgets/one_touch/one_touch_setting.dart b/lib/features/devices/view/widgets/one_touch/one_touch_setting.dart index 52be48f..38eda41 100644 --- a/lib/features/devices/view/widgets/one_touch/one_touch_setting.dart +++ b/lib/features/devices/view/widgets/one_touch/one_touch_setting.dart @@ -142,8 +142,7 @@ class OneTouchSetting extends StatelessWidget { bottom: 10, top: 10), child: InkWell( onTap: () async { - oneTouchBloc.optionSelected = - 'light_mode'; + oneTouchBloc.optionSelected = 'light_mode'; final result = await showDialog( context: context, builder: (context) { diff --git a/lib/features/devices/view/widgets/room_page_switch.dart b/lib/features/devices/view/widgets/room_page_switch.dart index 851aabf..3eb0d6c 100644 --- a/lib/features/devices/view/widgets/room_page_switch.dart +++ b/lib/features/devices/view/widgets/room_page_switch.dart @@ -10,6 +10,7 @@ import 'package:syncrow_app/features/devices/model/device_model.dart'; import 'package:syncrow_app/features/devices/view/widgets/ACs/acs_view.dart'; import 'package:syncrow_app/features/devices/view/widgets/curtains/curtain_view.dart'; import 'package:syncrow_app/features/devices/view/widgets/door_sensor/door_sensor_screen.dart'; +import 'package:syncrow_app/features/devices/view/widgets/garage_door/garage_door_screen.dart'; import 'package:syncrow_app/features/devices/view/widgets/gateway/gateway_view.dart'; import 'package:syncrow_app/features/devices/view/widgets/lights/light_interface.dart'; import 'package:syncrow_app/features/devices/view/widgets/one_gang/one_gang_Interface.dart'; @@ -187,6 +188,14 @@ void showDeviceInterface(DeviceModel device, BuildContext context) { PageRouteBuilder( pageBuilder: (context, animation1, animation2) => ThreeTouchInterface(touchSwitch: device))); + + case DeviceType.GarageDoor: + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + GarageDoorScreen(device: device))); + break; default: } diff --git a/lib/features/shared_widgets/create_schedule.dart b/lib/features/shared_widgets/create_schedule.dart index c8b298a..310b97d 100644 --- a/lib/features/shared_widgets/create_schedule.dart +++ b/lib/features/shared_widgets/create_schedule.dart @@ -137,3 +137,5 @@ class _CreateScheduleState extends State { ); } } + + diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart index 2699f62..753e527 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -1059,4 +1059,10 @@ class Assets { "assets/icons/door_notification_setting_icon.svg"; static const String doorRecordsIcon = "assets/icons/door_records_icon.svg"; static const String doorSensorIcon = "assets/icons/door_sensor_icon.svg"; + static const String closedGarageIcon = "assets/icons/closed_garage_door.svg"; + static const String openGarageIcon = "assets/icons/open_garage_door.svg"; + static const String garageCountdown = "assets/icons/garage_countdown.svg"; + static const String garagePreferencesIcon = "assets/icons/garage_preferences_icon.svg"; + static const String garageSchedule = "assets/icons/garage_schedule.svg"; + //open_garage_door.svg } diff --git a/lib/utils/resource_manager/constants.dart b/lib/utils/resource_manager/constants.dart index 7b7114d..5bd1667 100644 --- a/lib/utils/resource_manager/constants.dart +++ b/lib/utils/resource_manager/constants.dart @@ -53,6 +53,7 @@ enum DeviceType { OneTouch, TowTouch, ThreeTouch, + GarageDoor, Other, } @@ -83,6 +84,7 @@ Map devicesTypesMap = { "1GT": DeviceType.OneTouch, "2GT": DeviceType.TowTouch, "3GT": DeviceType.ThreeTouch, + "GD": DeviceType.GarageDoor, }; Map> devicesFunctionsMap = { DeviceType.AC: [ @@ -370,7 +372,6 @@ Map> devicesFunctionsMap = { "range": ['power_off', 'power_on', 'last'] })), ], - DeviceType.ThreeTouch: [ FunctionModel( code: 'switch_1', @@ -430,6 +431,43 @@ Map> devicesFunctionsMap = { "range": ['power_off', 'power_on', 'last'] })), ], + DeviceType.GarageDoor: [ + FunctionModel( + code: 'switch_1', + type: functionTypesMap['Boolean'], + values: ValueModel.fromJson({})), + FunctionModel( + code: 'countdown_1', + type: functionTypesMap['Integer'], + values: ValueModel.fromJson( + {"unit": "s", "min": 0, "max": 86400, "scale": 0, "step": 1})), + FunctionModel( + code: 'tr_timecon', + type: functionTypesMap['Integer'], + values: ValueModel.fromJson( + {"unit": "s", "min": 0, "max": 120, "scale": 0, "step": 1})), + FunctionModel( + code: 'countdown_alarm', + type: functionTypesMap['Integer'], + values: ValueModel.fromJson( + {"unit": "s", "min": 0, "max": 86400, "scale": 0, "step": 1})), + FunctionModel( + code: 'door_control_1', + type: functionTypesMap['Enum'], + values: ValueModel.fromJson({ + "range": ['open', 'open'] + })), + FunctionModel( + code: 'voice_control_1', + type: functionTypesMap['Boolean'], + values: ValueModel.fromJson({})), + FunctionModel( + code: 'door_state_1', + type: functionTypesMap['Enum'], + values: ValueModel.fromJson({ + "range": ["unclosed_time", "close_time_alarm", "none"] + })), + ], }; enum TempModes { hot, cold, wind } diff --git a/pubspec.yaml b/pubspec.yaml index d2fab73..1a9b3e5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ description: This is the mobile application project, developed with Flutter for # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: "none" # Remove this line if you wish to publish to pub.dev -version: 1.0.3+22 +version: 1.0.3+25 environment: sdk: ">=3.0.6 <4.0.0"