From 208fbd63f34789d6abc2170d4649076ba35e8d15 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 7 Oct 2024 10:26:08 +0300 Subject: [PATCH] garage door wizard --- .../garage_door_bloc/garage_door_bloc.dart | 216 ++++++++++++++++-- .../garage_door_bloc/garage_door_event.dart | 13 ++ .../garage_door_bloc/garage_door_state.dart | 12 + .../devices/model/group_garage_model.dart | 11 + .../view/widgets/curtains/curtain_view.dart | 3 +- .../view/widgets/garage_door/garage_list.dart | 70 ++++++ .../widgets/garage_door/garage_wizard.dart | 50 ++++ .../devices/view/widgets/wizard_page.dart | 9 + 8 files changed, 361 insertions(+), 23 deletions(-) create mode 100644 lib/features/devices/model/group_garage_model.dart create mode 100644 lib/features/devices/view/widgets/garage_door/garage_list.dart create mode 100644 lib/features/devices/view/widgets/garage_door/garage_wizard.dart 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 index a8cb8a4..ae11720 100644 --- a/lib/features/devices/bloc/garage_door_bloc/garage_door_bloc.dart +++ b/lib/features/devices/bloc/garage_door_bloc/garage_door_bloc.dart @@ -2,11 +2,14 @@ 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/app_layout/bloc/home_cubit.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_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/group_garage_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'; @@ -32,6 +35,10 @@ class GarageDoorBloc extends Bloc { on(_getCounterValue); on(_setCounterValue); on(_onTickTimer); + on(_fetchWizardStatus); + on(_groupAllOn); + on(_groupAllOff); + on(_changeFirstWizardSwitch); } void _onClose(OnClose event, Emitter emit) { _timer?.cancel(); @@ -54,7 +61,8 @@ class GarageDoorBloc extends Bloc { batteryPercentage: 0, ); - void _fetchStatus(GarageDoorInitial event, Emitter emit) async { + void _fetchStatus( + GarageDoorInitial event, Emitter emit) async { emit(GarageDoorLoadingState()); try { var response = await DevicesAPI.getDeviceStatus(GDId); @@ -76,8 +84,8 @@ class GarageDoorBloc extends Bloc { } } - void _toggleClosingReminder( - ToggleClosingReminderEvent event, Emitter emit) async { + void _toggleClosingReminder(ToggleClosingReminderEvent event, + Emitter emit) async { emit(LoadingNewSate(doorSensor: deviceStatus)); try { closingReminder = event.isClosingReminderEnabled; @@ -95,7 +103,8 @@ class GarageDoorBloc extends Bloc { } } - void _toggleDoorAlarm(ToggleDoorAlarmEvent event, Emitter emit) async { + void _toggleDoorAlarm( + ToggleDoorAlarmEvent event, Emitter emit) async { emit(LoadingNewSate(doorSensor: deviceStatus)); try { doorAlarm = event.isDoorAlarmEnabled; @@ -113,7 +122,8 @@ class GarageDoorBloc extends Bloc { } } - DeviceReport recordGroups = DeviceReport(startTime: '0', endTime: '0', data: []); + DeviceReport recordGroups = + DeviceReport(startTime: '0', endTime: '0', data: []); Future fetchLogsForLastMonth( ReportLogsInitial event, Emitter emit) async { @@ -141,14 +151,16 @@ class GarageDoorBloc extends Bloc { _listenToChanges() { try { - DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$GDId'); + 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; + Map usersMap = + event.snapshot.value as Map; List statusList = []; usersMap['status'].forEach((element) { statusList.add(StatusModel(code: element['code'], value: true)); @@ -222,7 +234,8 @@ class GarageDoorBloc extends Bloc { deviceId: GDId, ); List jsonData = response; - listSchedule = jsonData.map((item) => ScheduleModel.fromJson(item)).toList(); + listSchedule = + jsonData.map((item) => ScheduleModel.fromJson(item)).toList(); } on DioException catch (e) { final errorData = e.response!.data; String errorMessage = errorData['message']; @@ -232,12 +245,13 @@ class GarageDoorBloc 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(GarageDoorLoadingState()); final response = await DevicesAPI.changeSchedule( @@ -255,7 +269,8 @@ class GarageDoorBloc extends Bloc { } } - Future deleteSchedule(DeleteScheduleEvent event, Emitter emit) async { + Future deleteSchedule( + DeleteScheduleEvent event, Emitter emit) async { try { emit(GarageDoorLoadingState()); final response = await DevicesAPI.deleteSchedule( @@ -274,13 +289,15 @@ class GarageDoorBloc extends Bloc { } } - void toggleSelectedIndex(ToggleSelectedEvent event, Emitter emit) { + void toggleSelectedIndex( + ToggleSelectedEvent event, Emitter emit) { emit(GarageDoorLoadingState()); selectedTabIndex = event.index; emit(ChangeSlidingSegmentState(value: selectedTabIndex)); } - void toggleCreateSchedule(ToggleCreateScheduleEvent event, Emitter emit) { + void toggleCreateSchedule( + ToggleCreateScheduleEvent event, Emitter emit) { emit(GarageDoorLoadingState()); createSchedule = !createSchedule; selectedDays.clear(); @@ -297,13 +314,16 @@ class GarageDoorBloc extends Bloc { int secondSelected = 0; bool toggleDoor = false; - Future selectSeconds(SelectSecondsEvent event, Emitter emit) async { + Future selectSeconds( + SelectSecondsEvent event, Emitter emit) async { try { emit(GarageDoorLoadingState()); secondSelected = event.seconds; await DevicesAPI.controlDevice( - DeviceControlModel(deviceId: GDId, code: 'tr_timecon', value: secondSelected), GDId); + DeviceControlModel( + deviceId: GDId, code: 'tr_timecon', value: secondSelected), + GDId); emit(UpdateState(garageSensor: deviceStatus)); } on DioException catch (e) { final errorData = e.response!.data; @@ -312,12 +332,15 @@ class GarageDoorBloc extends Bloc { } } - openCloseGarageDoor(ToggleDoorEvent event, Emitter emit) async { + openCloseGarageDoor( + ToggleDoorEvent event, Emitter emit) async { emit(GarageDoorLoadingState()); try { toggleDoor = !event.toggle; await DevicesAPI.controlDevice( - DeviceControlModel(deviceId: GDId, code: 'switch_1', value: toggleDoor), GDId); + DeviceControlModel( + deviceId: GDId, code: 'switch_1', value: toggleDoor), + GDId); add(const GarageDoorInitial()); emit(UpdateState(garageSensor: deviceStatus)); } on DioException catch (e) { @@ -327,13 +350,16 @@ class GarageDoorBloc extends Bloc { } } - void _setCounterValue(SetCounterValue event, Emitter emit) async { + 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); + DeviceControlModel( + deviceId: GDId, code: 'countdown_1', value: seconds), + GDId); if (response['success'] ?? false) { deviceStatus.countdown1 = seconds; @@ -353,7 +379,8 @@ class GarageDoorBloc extends Bloc { } } - void _getCounterValue(GetCounterEvent event, Emitter emit) async { + void _getCounterValue( + GetCounterEvent event, Emitter emit) async { emit(LoadingInitialState()); try { var response = await DevicesAPI.getDeviceStatus(GDId); @@ -388,4 +415,151 @@ class GarageDoorBloc extends Bloc { emit(TimerRunComplete()); } } + + bool oneGangGroup = false; + List devicesList = []; + List groupOneGangList = []; + bool allSwitchesOn = true; + + void _fetchWizardStatus( + InitialWizardEvent event, Emitter emit) async { + emit(LoadingInitialState()); + try { + devicesList = []; + groupOneGangList = []; + allSwitchesOn = true; + devicesList = await DevicesAPI.getDeviceByGroupName( + HomeCubit.getInstance().selectedSpace?.id ?? '', 'GD'); + + for (int i = 0; i < devicesList.length; i++) { + var response = + await DevicesAPI.getDeviceStatus(devicesList[i].uuid ?? ''); + List statusModelList = []; + for (var status in response['status']) { + statusModelList.add(StatusModel.fromJson(status)); + } + deviceStatus = GarageDoorModel.fromJson(statusModelList); + groupOneGangList.add(GroupGarageModel( + deviceId: devicesList[i].uuid ?? '', + deviceName: devicesList[i].name ?? '', + firstSwitch: deviceStatus.switch1, + )); + } + + if (groupOneGangList.isNotEmpty) { + groupOneGangList.firstWhere((element) { + if (!element.firstSwitch) { + allSwitchesOn = false; + } + return true; + }); + } + emit(UpdateGroupState( + oneGangList: groupOneGangList, allSwitches: allSwitchesOn)); + } catch (e) { + emit(GarageDoorFailedState(errorMessage: e.toString())); + + return; + } + } + + void _groupAllOn( + GroupAllOnEvent event, Emitter emit) async { + emit(LoadingNewSate(doorSensor: deviceStatus)); + try { + // Set all switches (firstSwitch and secondSwitch) based on the event value (on/off) + for (int i = 0; i < groupOneGangList.length; i++) { + groupOneGangList[i].firstSwitch = true; + } + + // Emit the state with updated values + emit(UpdateGroupState(oneGangList: groupOneGangList, allSwitches: true)); + + // Get a list of all device IDs + List allDeviceIds = + groupOneGangList.map((device) => device.deviceId).toList(); + + // First call for switch_1 + final response = await DevicesAPI.deviceBatchController( + code: 'switch_1', // Controls first switch for all devices + devicesUuid: allDeviceIds, + value: true, // true (on) or false (off) depending on the event value + ); + // Check if either response is unsuccessful, then reset to initial state + if (response['failedResults'].toString() != '[]') { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: true)); + } + } catch (_) { + // In case of an error, delay and reset the screen to initial state + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: true)); + } + } + + void _groupAllOff( + GroupAllOffEvent event, Emitter emit) async { + emit(LoadingNewSate(doorSensor: deviceStatus)); + try { + // Set all switches (firstSwitch and secondSwitch) based on the event value (on/off) + for (int i = 0; i < groupOneGangList.length; i++) { + groupOneGangList[i].firstSwitch = false; + } + + // Emit the state with updated values + emit(UpdateGroupState(oneGangList: groupOneGangList, allSwitches: false)); + + // Get a list of all device IDs + List allDeviceIds = + groupOneGangList.map((device) => device.deviceId).toList(); + + // First call for switch_1 + final response = await DevicesAPI.deviceBatchController( + code: 'switch_1', // Controls first switch for all devices + devicesUuid: allDeviceIds, + value: false, // true (on) or false (off) depending on the event value + ); + + // Check if either response is unsuccessful, then reset to initial state + if (response['failedResults'].toString() != '[]') { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: true)); + } + } catch (_) { + // In case of an error, delay and reset the screen to initial state + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: true)); + } + } + + void _changeFirstWizardSwitch(ChangeFirstWizardSwitchStatusEvent event, + Emitter emit) async { + emit(LoadingNewSate(doorSensor: deviceStatus)); + try { + bool allSwitchesValue = true; + groupOneGangList.forEach((element) { + if (element.deviceId == event.deviceId) { + element.firstSwitch = !event.value; + } + if (!element.firstSwitch) { + allSwitchesValue = false; + } + }); + + emit(UpdateGroupState( + oneGangList: groupOneGangList, allSwitches: allSwitchesValue)); + + final response = await DevicesAPI.deviceBatchController( + code: 'switch_1', + devicesUuid: [event.deviceId], + value: !event.value, + ); + + if (response['failedResults'].toString() != '[]') { + add(InitialEvent(groupScreen: oneGangGroup)); + } + } catch (_) { + add(InitialEvent(groupScreen: oneGangGroup)); + } + } } 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 index 17ee34a..7114ce3 100644 --- a/lib/features/devices/bloc/garage_door_bloc/garage_door_event.dart +++ b/lib/features/devices/bloc/garage_door_bloc/garage_door_event.dart @@ -160,3 +160,16 @@ class ToggleDoorEvent extends GarageDoorEvent { @override List get props => [toggle]; } + +class InitialWizardEvent extends GarageDoorEvent {} + +class GroupAllOnEvent extends GarageDoorEvent {} + +class GroupAllOffEvent extends GarageDoorEvent {} + +class InitialEvent extends GarageDoorEvent { + final bool groupScreen; + const InitialEvent({required this.groupScreen}); + @override + List get props => [groupScreen]; +} 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 index 4cb4e6a..86f3f5e 100644 --- a/lib/features/devices/bloc/garage_door_bloc/garage_door_state.dart +++ b/lib/features/devices/bloc/garage_door_bloc/garage_door_state.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_app/features/devices/model/garage_door_model.dart'; +import 'package:syncrow_app/features/devices/model/group_garage_model.dart'; class GarageDoorSensorState extends Equatable { const GarageDoorSensorState(); @@ -80,3 +81,14 @@ class UpdateCreateScheduleState extends GarageDoorSensorState { final bool createSchedule; UpdateCreateScheduleState(this.createSchedule); } + +class UpdateGroupState extends GarageDoorSensorState { + final List oneGangList; + final bool allSwitches; + + const UpdateGroupState( + {required this.oneGangList, required this.allSwitches}); + + @override + List get props => [oneGangList, allSwitches]; +} diff --git a/lib/features/devices/model/group_garage_model.dart b/lib/features/devices/model/group_garage_model.dart new file mode 100644 index 0000000..e4012ec --- /dev/null +++ b/lib/features/devices/model/group_garage_model.dart @@ -0,0 +1,11 @@ +class GroupGarageModel { + final String deviceId; + final String deviceName; + bool firstSwitch; + + GroupGarageModel({ + required this.deviceId, + required this.deviceName, + required this.firstSwitch, + }); +} diff --git a/lib/features/devices/view/widgets/curtains/curtain_view.dart b/lib/features/devices/view/widgets/curtains/curtain_view.dart index 7c0e4d0..6965008 100644 --- a/lib/features/devices/view/widgets/curtains/curtain_view.dart +++ b/lib/features/devices/view/widgets/curtains/curtain_view.dart @@ -117,8 +117,7 @@ class CurtainView extends StatelessWidget { curve: Curves.linear, right: rightMostPosition, child: SizedBox( - height: - MediaQuery.sizeOf(context).height * 0.35, + height: MediaQuery.sizeOf(context).height * 0.35, width: MediaQuery.sizeOf(context).width * 0.08, child: SvgPicture.asset( Assets.rightVerticalBlade, diff --git a/lib/features/devices/view/widgets/garage_door/garage_list.dart b/lib/features/devices/view/widgets/garage_door/garage_list.dart new file mode 100644 index 0000000..b3d3f81 --- /dev/null +++ b/lib/features/devices/view/widgets/garage_door/garage_list.dart @@ -0,0 +1,70 @@ +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/devices/model/group_garage_model.dart'; +import 'package:syncrow_app/features/shared_widgets/devices_default_switch.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; + +class GarageList extends StatelessWidget { + const GarageList( + {super.key, required this.garageList, required this.allSwitches}); + + final List garageList; + final bool allSwitches; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 10), + const BodySmall(text: 'All Lights'), + const SizedBox(height: 5), + DevicesDefaultSwitch( + switchValue: allSwitches, + action: () { + BlocProvider.of(context) + .add(GroupAllOnEvent()); + }, + secondAction: () { + BlocProvider.of(context) + .add(GroupAllOffEvent()); + }, + ), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.all(0), + itemCount: garageList.length, + itemBuilder: (context, index) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + BodySmall(text: garageList[index].deviceName), + const SizedBox(height: 5), + DevicesDefaultSwitch( + switchValue: garageList[index].firstSwitch, + action: () { + BlocProvider.of(context).add( + ChangeFirstWizardSwitchStatusEvent( + value: garageList[index].firstSwitch, + deviceId: garageList[index].deviceId)); + }, + ), + ], + ); + }, + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/devices/view/widgets/garage_door/garage_wizard.dart b/lib/features/devices/view/widgets/garage_door/garage_wizard.dart new file mode 100644 index 0000000..e143dab --- /dev/null +++ b/lib/features/devices/view/widgets/garage_door/garage_wizard.dart @@ -0,0 +1,50 @@ +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/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/group_garage_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/garage_door/garage_list.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; + +class GarageWizard extends StatelessWidget { + const GarageWizard({super.key, this.device}); + + final DeviceModel? device; + + @override + Widget build(BuildContext context) { + List groupGarageModel = []; + + return DefaultScaffold( + title: 'Garage Door Opener', + child: BlocProvider( + create: (context) => + GarageDoorBloc( GDId: device?.uuid ?? '') + ..add(InitialWizardEvent()), + child: BlocBuilder( + builder: (context, state) { + bool allSwitchesOn = false; + + if (state is UpdateGroupState) { + groupGarageModel = state.oneGangList; + allSwitchesOn = state.allSwitches; + } + return state is LoadingInitialState + ? const Center( + child: DefaultContainer( + width: 50, + height: 50, + child: CircularProgressIndicator()), + ) + : GarageList( + garageList: groupGarageModel, + allSwitches: allSwitchesOn, + ); + }, + ), + )); + } +} diff --git a/lib/features/devices/view/widgets/wizard_page.dart b/lib/features/devices/view/widgets/wizard_page.dart index d617bad..3b62d1c 100644 --- a/lib/features/devices/view/widgets/wizard_page.dart +++ b/lib/features/devices/view/widgets/wizard_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:syncrow_app/features/devices/model/device_category_model.dart'; import 'package:syncrow_app/features/devices/view/widgets/ACs/acs_view.dart'; +import 'package:syncrow_app/features/devices/view/widgets/garage_door/garage_wizard.dart'; import 'package:syncrow_app/features/devices/view/widgets/one_gang/one_gang_wizard.dart'; import 'package:syncrow_app/features/devices/view/widgets/one_touch/one_touch_wizard.dart'; import 'package:syncrow_app/features/devices/view/widgets/three_gang/three_gang_wizard.dart'; @@ -91,6 +92,14 @@ class WizardPage extends StatelessWidget { pageBuilder: (context, animation1, animation2) => const ThreeTouchWizard())); } + + if (groupsList[index].name == 'GD') { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + const GarageWizard())); + } }, child: DefaultContainer( padding: const EdgeInsets.all(15),