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 new file mode 100644 index 0000000..adae357 --- /dev/null +++ b/lib/features/devices/bloc/one_touch_bloc/one_touch_bloc.dart @@ -0,0 +1,503 @@ +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/one_touch_bloc/one_touch_state.dart'; +import 'package:syncrow_app/features/devices/bloc/one_touch_bloc/one_touch_event.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/group_one_touch_model.dart'; +import 'package:syncrow_app/features/devices/model/one_touch_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 OneTouchBloc extends Bloc { + final String oneTouchId; + final String switchCode; + OneTouchModel deviceStatus = OneTouchModel( + firstSwitch: false, + firstCountDown: 0, + ); + Timer? _timer; + + bool oneTouchGroup = false; + List devicesList = []; + + OneTouchBloc({required this.oneTouchId, required this.switchCode}) + : super(InitialState()) { + on(_fetchOneTouchStatus); + on(_oneTouchUpdated); + on(_changeFirstSwitch); + on(_changeSliding); + on(_setCounterValue); + on(_getCounterValue); + on(_onTickTimer); + on(_onClose); + on(toggleDaySelection); + on(saveSchedule); + on(getSchedule); + on(toggleChange); + on(deleteSchedule); + on(toggleSelectedIndex); + on(toggleCreateSchedule); + on(_fetchOneTouchWizardStatus); + on(_changeFirstWizardSwitch); + on(_groupAllOn); + on(_groupAllOff); + } + + void _fetchOneTouchStatus( + InitialEvent event, Emitter emit) async { + emit(LoadingInitialState()); + try { + var response = await DevicesAPI.getDeviceStatus(oneTouchId); + List statusModelList = []; + for (var status in response['status']) { + statusModelList.add(StatusModel.fromJson(status)); + } + deviceStatus = OneTouchModel.fromJson(statusModelList); + emit(UpdateState(oneTouchModel: deviceStatus)); + _listenToChanges(); + } catch (e) { + emit(FailedState(error: e.toString())); + return; + } + } + + _listenToChanges() { + try { + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$oneTouchId'); + 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: element['value'])); + }); + + deviceStatus = OneTouchModel.fromJson(statusList); + if (!isClosed) { + add(OneTouchUpdated()); + } + }); + } catch (_) {} + } + + _oneTouchUpdated(OneTouchUpdated event, Emitter emit) { + emit(UpdateState(oneTouchModel: deviceStatus)); + } + + void _changeFirstSwitch( + ChangeFirstSwitchStatusEvent event, Emitter emit) async { + emit(LoadingNewSate(oneTouchModel: deviceStatus)); + try { + deviceStatus.firstSwitch = !event.value; + emit(UpdateState(oneTouchModel: deviceStatus)); + if (_timer != null) { + _timer!.cancel(); + } + _timer = Timer(const Duration(milliseconds: 500), () async { + final response = await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: oneTouchGroup ? event.deviceId : oneTouchId, + code: 'switch_1', + value: !event.value), + oneTouchGroup ? event.deviceId : oneTouchId); + + if (!response['success']) { + add(InitialEvent(groupScreen: oneTouchGroup)); + } + }); + } catch (_) { + add(InitialEvent(groupScreen: oneTouchGroup)); + } + } + + void _changeSliding( + ChangeSlidingSegment event, Emitter emit) async { + emit(ChangeSlidingSegmentState(value: event.value)); + } + + void _setCounterValue( + SetCounterValue event, Emitter emit) async { + emit(LoadingNewSate(oneTouchModel: deviceStatus)); + int seconds = 0; + try { + seconds = event.duration.inSeconds; + final response = await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: oneTouchId, code: event.deviceCode, value: seconds), + oneTouchId); + + if (response['success'] ?? false) { + if (event.deviceCode == 'countdown_1') { + deviceStatus.firstCountDown = seconds; + } + } else { + emit(const FailedState(error: 'Something went wrong')); + return; + } + } catch (e) { + emit(FailedState(error: 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(oneTouchId); + List statusModelList = []; + for (var status in response['status']) { + statusModelList.add(StatusModel.fromJson(status)); + } + deviceStatus = OneTouchModel.fromJson(statusModelList); + + if (event.deviceCode == 'countdown_1') { + deviceStatus.firstCountDown > 0 + ? _onStartTimer(deviceStatus.firstCountDown) + : emit(UpdateTimerState(seconds: deviceStatus.firstCountDown)); + } + } catch (e) { + emit(FailedState(error: e.toString())); + return; + } + } + + void _onClose(OnClose event, Emitter emit) { + _timer?.cancel(); + } + + void _onStartTimer(int seconds) { + _timer?.cancel(); + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + add(TickTimer(seconds - timer.tick)); + }); + } + + void _onTickTimer(TickTimer event, Emitter emit) { + if (event.remainingTime > 0) { + emit(TimerRunInProgress(event.remainingTime)); + } else { + _timer?.cancel(); + emit(TimerRunComplete()); + } + } + + 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 saveSchedule( + ScheduleSave event, + Emitter emit, + ) async { + try { + if (selectedDays.isNotEmpty) { + emit(LoadingInitialState()); + await DevicesAPI.postSchedule( + category: switchCode, + deviceId: oneTouchId, + time: getTimeStampWithoutSeconds(selectedTime).toString(), + code: switchCode, + value: toggleSchedule, + days: selectedDays); + CustomSnackBar.displaySnackBar('Save Successfully'); + add(GetScheduleEvent()); + emit(SaveSchedule()); + + add(const ToggleCreateScheduleEvent(index: 1)); + } else { + CustomSnackBar.displaySnackBar('Please select days'); + } + } catch (e) { + emit(FailedState(error: e.toString())); + } + } + + Future getSchedule( + GetScheduleEvent event, + Emitter emit, + ) async { + try { + emit(LoadingInitialState()); + final response = await DevicesAPI.getSchedule( + category: switchCode, + deviceId: oneTouchId, + ); + List jsonData = response; + listSchedule = + jsonData.map((item) => ScheduleModel.fromJson(item)).toList(); + emit(InitialState()); + } on DioException catch (e) { + final errorData = e.response!.data; + String errorMessage = errorData['message']; + emit(FailedState(error: 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(LoadingInitialState()); + final response = await DevicesAPI.changeSchedule( + scheduleId: event.id, deviceUuid: oneTouchId, enable: event.toggle); + if (response == true) { + add(GetScheduleEvent()); + toggleSchedule = event.toggle; + return toggleSchedule; + } + emit(IsToggleState(onOff: toggleSchedule)); + add(const ChangeSlidingSegment(value: 1)); + } on DioException catch (e) { + final errorData = e.response!.data; + String errorMessage = errorData['message']; + emit(FailedState(error: errorMessage.toString())); + } + } + + Future deleteSchedule( + DeleteScheduleEvent event, Emitter emit) async { + try { + emit(LoadingInitialState()); + final response = await DevicesAPI.deleteSchedule( + scheduleId: event.id, + deviceUuid: oneTouchId, + ); + if (response == true) { + add(GetScheduleEvent()); + return toggleSchedule; + } + emit(IsToggleState(onOff: toggleSchedule)); + add(const ChangeSlidingSegment(value: 1)); + } on DioException catch (e) { + final errorData = e.response!.data; + String errorMessage = errorData['message']; + emit(FailedState(error: errorMessage.toString())); + } + } + + void toggleCreateSchedule( + ToggleCreateScheduleEvent event, Emitter emit) { + emit(LoadingInitialState()); + createSchedule = !createSchedule; + selectedDays.clear(); + selectedTime = DateTime.now(); + emit(UpdateCreateScheduleState(createSchedule)); + } + + bool toggleSchedule = true; + List selectedDays = []; + bool createSchedule = false; + List listSchedule = []; + DateTime? selectedTime = DateTime.now(); + Future toggleDaySelection( + ToggleDaySelectionEvent event, + Emitter emit, + ) async { + emit(LoadingInitialState()); + if (selectedDays.contains(event.key)) { + selectedDays.remove(event.key); + } else { + selectedDays.add(event.key); + } + emit(ChangeTimeState()); + add(ChangeSlidingSegment(value: 1)); + } + + int selectedTabIndex = 0; + + void toggleSelectedIndex( + ToggleSelectedEvent event, Emitter emit) { + emit(LoadingInitialState()); + selectedTabIndex = event.index; + emit(ChangeSlidingSegmentState(value: selectedTabIndex)); + } + + List groupOneTouchList = []; + bool allSwitchesOn = true; + + void _fetchOneTouchWizardStatus( + InitialWizardEvent event, Emitter emit) async { + emit(LoadingInitialState()); + try { + devicesList = []; + groupOneTouchList = []; + allSwitchesOn = true; + devicesList = await DevicesAPI.getDeviceByGroupName( + HomeCubit.getInstance().selectedSpace?.id ?? '', '1GT'); + + 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 = OneTouchModel.fromJson(statusModelList); + groupOneTouchList.add(GroupOneTouchModel( + deviceId: devicesList[i].uuid ?? '', + deviceName: devicesList[i].name ?? '', + firstSwitch: deviceStatus.firstSwitch, + )); + } + + if (groupOneTouchList.isNotEmpty) { + groupOneTouchList.firstWhere((element) { + if (!element.firstSwitch) { + allSwitchesOn = false; + } + return true; + }); + } + emit(UpdateGroupState( + oneTouchList: groupOneTouchList, allSwitches: allSwitchesOn)); + } catch (e) { + emit(FailedState(error: e.toString())); + return; + } + } + + void _changeFirstWizardSwitch(ChangeFirstWizardSwitchStatusEvent event, + Emitter emit) async { + emit(LoadingNewSate(oneTouchModel: deviceStatus)); + try { + bool allSwitchesValue = true; + groupOneTouchList.forEach((element) { + if (element.deviceId == event.deviceId) { + element.firstSwitch = !event.value; + } + if (!element.firstSwitch) { + allSwitchesValue = false; + } + }); + final response = await DevicesAPI.deviceBatchController( + code: 'switch_1', + devicesUuid: [event.deviceId], + value: !event.value, + ); + + emit(UpdateGroupState( + oneTouchList: groupOneTouchList, allSwitches: allSwitchesValue)); + if (!response['success']) { + add(InitialEvent(groupScreen: oneTouchGroup)); + } + } catch (_) { + add(InitialEvent(groupScreen: oneTouchGroup)); + } + } + + void _groupAllOn(GroupAllOnEvent event, Emitter emit) async { + emit(LoadingNewSate(oneTouchModel: deviceStatus)); + try { + // Set all switches (firstSwitch and secondSwitch) based on the event value (on/off) + for (int i = 0; i < groupOneTouchList.length; i++) { + groupOneTouchList[i].firstSwitch = true; + } + + // Emit the state with updated values + emit( + UpdateGroupState(oneTouchList: groupOneTouchList, allSwitches: true)); + + // Get a list of all device IDs + List allDeviceIds = + groupOneTouchList.map((device) => device.deviceId).toList(); + + // First call for switch_1 + final response1 = 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 + ); + + // Second call for switch_2 + final response2 = await DevicesAPI.deviceBatchController( + code: 'switch_2', // Controls second 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 (!response1['success'] || !response2['success']) { + 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(oneTouchModel: deviceStatus)); + try { + // Set all switches (firstSwitch and secondSwitch) based on the event value (on/off) + for (int i = 0; i < groupOneTouchList.length; i++) { + groupOneTouchList[i].firstSwitch = false; + } + + // Emit the state with updated values + emit(UpdateGroupState( + oneTouchList: groupOneTouchList, allSwitches: false)); + + // Get a list of all device IDs + List allDeviceIds = + groupOneTouchList.map((device) => device.deviceId).toList(); + + // First call for switch_1 + final response1 = 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 + ); + + // Second call for switch_2 + final response2 = await DevicesAPI.deviceBatchController( + code: 'switch_2', // Controls second 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 (!response1['success'] || !response2['success']) { + 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)); + } + } +} diff --git a/lib/features/devices/bloc/one_touch_bloc/one_touch_event.dart b/lib/features/devices/bloc/one_touch_bloc/one_touch_event.dart new file mode 100644 index 0000000..dc6c8fb --- /dev/null +++ b/lib/features/devices/bloc/one_touch_bloc/one_touch_event.dart @@ -0,0 +1,141 @@ +import 'package:equatable/equatable.dart'; + +abstract class OneTouchEvent extends Equatable { + const OneTouchEvent(); + + @override + List get props => []; +} + +class LoadingEvent extends OneTouchEvent {} + +class OneTouchUpdated extends OneTouchEvent {} + +class InitialEvent extends OneTouchEvent { + final bool groupScreen; + const InitialEvent({required this.groupScreen}); + @override + List get props => [groupScreen]; +} + +class ChangeFirstSwitchStatusEvent extends OneTouchEvent { + final bool value; + final String deviceId; + const ChangeFirstSwitchStatusEvent({required this.value, this.deviceId = ''}); + @override + List get props => [value, deviceId]; +} + +class ChangeSecondSwitchStatusEvent extends OneTouchEvent { + final bool value; + final String deviceId; + const ChangeSecondSwitchStatusEvent( + {required this.value, this.deviceId = ''}); + @override + List get props => [value, deviceId]; +} + +class AllOffEvent extends OneTouchEvent {} + +class AllOnEvent extends OneTouchEvent {} + +class GroupAllOnEvent extends OneTouchEvent {} + +class GroupAllOffEvent extends OneTouchEvent {} + +class ChangeSlidingSegment extends OneTouchEvent { + final int value; + const ChangeSlidingSegment({required this.value}); + @override + List get props => [value]; +} + +class GetCounterEvent extends OneTouchEvent { + final String deviceCode; + const GetCounterEvent({required this.deviceCode}); + @override + List get props => [deviceCode]; +} + +class SetCounterValue extends OneTouchEvent { + final Duration duration; + final String deviceCode; + const SetCounterValue({required this.duration, required this.deviceCode}); + @override + List get props => [duration, deviceCode]; +} + +class StartTimer extends OneTouchEvent { + final int duration; + + const StartTimer(this.duration); + + @override + List get props => [duration]; +} + +class TickTimer extends OneTouchEvent { + final int remainingTime; + + const TickTimer(this.remainingTime); + + @override + List get props => [remainingTime]; +} + +class StopTimer extends OneTouchEvent {} + +class OnClose extends OneTouchEvent {} + +class InitialWizardEvent extends OneTouchEvent {} + +//------------------- Schedule ----------=--------- +class GetScheduleEvent extends OneTouchEvent {} + +class ScheduleSave extends OneTouchEvent {} + +class ToggleScheduleEvent extends OneTouchEvent { + final String id; + final bool toggle; + const ToggleScheduleEvent({required this.toggle, required this.id}); + @override + List get props => [toggle, id]; +} + +class ToggleDaySelectionEvent extends OneTouchEvent { + final String key; + + const ToggleDaySelectionEvent({required this.key}); + @override + List get props => [key]; +} + +class DeleteScheduleEvent extends OneTouchEvent { + final String id; + const DeleteScheduleEvent({required this.id}); + @override + List get props => [id]; +} + +class ToggleSelectedEvent extends OneTouchEvent { + final int index; + const ToggleSelectedEvent({required this.index}); + @override + List get props => [index]; +} + +class ToggleCreateScheduleEvent extends OneTouchEvent { + final int index; + const ToggleCreateScheduleEvent({required this.index}); + @override + List get props => [index]; +} + +class ChangeFirstWizardSwitchStatusEvent extends OneTouchEvent { + final bool value; + final String deviceId; + const ChangeFirstWizardSwitchStatusEvent( + {required this.value, this.deviceId = ''}); + @override + List get props => [value, deviceId]; +} diff --git a/lib/features/devices/bloc/one_touch_bloc/one_touch_state.dart b/lib/features/devices/bloc/one_touch_bloc/one_touch_state.dart new file mode 100644 index 0000000..96e1888 --- /dev/null +++ b/lib/features/devices/bloc/one_touch_bloc/one_touch_state.dart @@ -0,0 +1,92 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/devices/model/group_one_touch_model.dart'; +import 'package:syncrow_app/features/devices/model/one_touch_model.dart'; + + +class OneTouchState extends Equatable { + const OneTouchState(); + + @override + List get props => []; +} + +class InitialState extends OneTouchState {} + +class LoadingInitialState extends OneTouchState {} + +class UpdateState extends OneTouchState { + final OneTouchModel oneTouchModel; + const UpdateState({required this.oneTouchModel}); + + @override + List get props => [oneTouchModel]; +} + +class LoadingNewSate extends OneTouchState { + final OneTouchModel oneTouchModel; + const LoadingNewSate({required this.oneTouchModel}); + + @override + List get props => [OneTouchModel]; +} + +class UpdateGroupState extends OneTouchState { + final List oneTouchList; + final bool allSwitches; + + const UpdateGroupState({required this.oneTouchList, required this.allSwitches}); + + @override + List get props => [oneTouchList, allSwitches]; +} + +class FailedState extends OneTouchState { + final String error; + + const FailedState({required this.error}); + + @override + List get props => [error]; +} + +class ChangeSlidingSegmentState extends OneTouchState { + final int value; + + const ChangeSlidingSegmentState({required this.value}); + + @override + List get props => [value]; +} + +class UpdateTimerState extends OneTouchState { + final int seconds; + const UpdateTimerState({required this.seconds}); + + @override + List get props => [seconds]; +} + +class TimerRunInProgress extends OneTouchState { + final int remainingTime; + + const TimerRunInProgress(this.remainingTime); + + @override + List get props => [remainingTime]; +} + +class TimerRunComplete extends OneTouchState {} + +class SaveSchedule extends OneTouchState {} + +class IsToggleState extends OneTouchState { + final bool? onOff; + const IsToggleState({this.onOff}); +} + +class ChangeTimeState extends OneTouchState {} + +class UpdateCreateScheduleState extends OneTouchState { + final bool createSchedule; + UpdateCreateScheduleState(this.createSchedule); +} diff --git a/lib/features/devices/model/group_one_touch_model.dart b/lib/features/devices/model/group_one_touch_model.dart new file mode 100644 index 0000000..d6ac8bf --- /dev/null +++ b/lib/features/devices/model/group_one_touch_model.dart @@ -0,0 +1,11 @@ +class GroupOneTouchModel { + final String deviceId; + final String deviceName; + bool firstSwitch; + + GroupOneTouchModel({ + required this.deviceId, + required this.deviceName, + required this.firstSwitch, + }); +} diff --git a/lib/features/devices/model/one_touch_model.dart b/lib/features/devices/model/one_touch_model.dart new file mode 100644 index 0000000..c3fb372 --- /dev/null +++ b/lib/features/devices/model/one_touch_model.dart @@ -0,0 +1,28 @@ +import 'package:syncrow_app/features/devices/model/status_model.dart'; + +class OneTouchModel { + bool firstSwitch; + int firstCountDown; + + OneTouchModel({ + required this.firstSwitch, + required this.firstCountDown, + }); + + factory OneTouchModel.fromJson(List jsonList) { + late bool _switch; + late int _count; + + for (int i = 0; i < jsonList.length; i++) { + if (jsonList[i].code == 'switch_1') { + _switch = jsonList[i].value ?? false; + } else if (jsonList[i].code == 'countdown_1') { + _count = jsonList[i].value ?? 0; + } + } + return OneTouchModel( + firstSwitch: _switch, + firstCountDown: _count, + ); + } +} diff --git a/lib/features/devices/view/widgets/circular_button.dart b/lib/features/devices/view/widgets/circular_button.dart new file mode 100644 index 0000000..cef8a4b --- /dev/null +++ b/lib/features/devices/view/widgets/circular_button.dart @@ -0,0 +1,86 @@ + + +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class CircularButton extends StatelessWidget { + const CircularButton({ + super.key, + required this.device, + required this.label, + required this.onTap, + required this.icons, + }); + + final DeviceModel? device; + final String label; + final Function()? onTap; + final IconData icons; + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Card( + elevation: 3, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100), + ), + child: GestureDetector( + onTap: onTap, + // () { + // Navigator.push( + // context, + // PageRouteBuilder( + // pageBuilder: (context, animation1, animation2) => + // TimerScheduleScreen( + // switchCode: 'switch_1', + // device: device!, + // deviceCode: 'countdown_1', + // ))); + // }, + child: Stack( + alignment: Alignment.center, + children: [ + Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(100), + ), + ), + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(100), + ), + child: Center( + child: Icon( + icons, + color: ColorsManager.primaryColorWithOpacity, + size: 25, + ), + ), + ), + ], + ), + ), + ), + const SizedBox(height: 10), + BodySmall( + // text: "Timer", + text: label, + style: context.bodyMedium.copyWith( + color: ColorsManager.textPrimaryColor, + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/features/devices/view/widgets/one_touch/one_touch_screen.dart b/lib/features/devices/view/widgets/one_touch/one_touch_screen.dart new file mode 100644 index 0000000..0987e01 --- /dev/null +++ b/lib/features/devices/view/widgets/one_touch/one_touch_screen.dart @@ -0,0 +1,223 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/one_touch_bloc/one_touch_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/one_touch_bloc/one_touch_event.dart'; +import 'package:syncrow_app/features/devices/bloc/one_touch_bloc/one_touch_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/group_one_touch_model.dart'; +import 'package:syncrow_app/features/devices/model/one_touch_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/circular_button.dart'; +import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart'; +import 'package:syncrow_app/features/devices/view/widgets/one_touch/one_touch_timer_screen.dart'; +import 'package:syncrow_app/features/devices/view/widgets/three_gang/gang_switch.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class OneTouchScreen extends StatelessWidget { + const OneTouchScreen({super.key, this.device}); + + final DeviceModel? device; + @override + Widget build(BuildContext context) { + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarColor: ColorsManager.primaryColor.withOpacity(0.5), + statusBarIconBrightness: Brightness.light, + ), + child: Scaffold( + backgroundColor: ColorsManager.backgroundColor, + extendBodyBehindAppBar: true, + extendBody: true, + appBar: device != null + ? DeviceAppbar( + deviceName: device!.name!, + deviceUuid: device!.uuid!, + ) + : AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + title: BodyLarge( + text: device?.name ?? 'Lights', + fontColor: ColorsManager.primaryColor, + fontWeight: FontsManager.bold, + ), + ), + body: Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.assetsImagesBackground, + ), + fit: BoxFit.cover, + opacity: 0.4, + ), + ), + child: SafeArea( + child: Padding( + padding: EdgeInsets.only( + left: Constants.defaultPadding, + right: Constants.defaultPadding, + bottom: Constants.bottomNavBarHeight, + ), + child: BlocProvider( + create: (context) => OneTouchBloc( + switchCode: 'switch_1', oneTouchId: device?.uuid ?? '') + ..add(const InitialEvent(groupScreen: false)), + child: BlocBuilder( + builder: (context, state) { + OneTouchModel oneTouchModel = OneTouchModel( + firstSwitch: false, + firstCountDown: 0, + ); + + List groupOneTouchModel = []; + bool allSwitchesOn = false; + + if (state is LoadingNewSate) { + oneTouchModel = state.oneTouchModel; + } else if (state is UpdateState) { + oneTouchModel = state.oneTouchModel; + } else if (state is UpdateGroupState) { + groupOneTouchModel = state.oneTouchList; + allSwitchesOn = state.allSwitches; + } + return state is LoadingInitialState + ? const Center( + child: DefaultContainer( + width: 50, + height: 50, + child: CircularProgressIndicator()), + ) + : RefreshIndicator( + onRefresh: () async { + BlocProvider.of(context).add( + InitialEvent( + groupScreen: + device != null ? false : true)); + }, + child: ListView( + children: [ + SizedBox( + height: MediaQuery.of(context).size.height, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + const Expanded(child: SizedBox.shrink()), + Expanded( + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + Column( + children: [ + GangSwitch( + threeGangSwitch: device!, + value: + oneTouchModel.firstSwitch, + action: () { + BlocProvider.of< + OneTouchBloc>( + context) + .add(ChangeFirstSwitchStatusEvent( + value: oneTouchModel + .firstSwitch)); + }, + ), + const SizedBox(height: 20), + const SizedBox( + width: 70, + child: BodySmall( + text: " Entrance Light", + fontColor: ColorsManager + .textPrimaryColor, + textAlign: TextAlign.center, + ), + ), + ], + ), + ], + ), + ), + Center( + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + CircularButton( + device: device, + icons: Icons.access_time, + label: 'Timer', + onTap: () { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, + animation1, + animation2) => + TimerScheduleScreen( + switchCode: + 'switch_1', + device: device!, + deviceCode: + 'countdown_1', + ))); + }, + ), + const SizedBox( + width: 30, + ), + CircularButton( + device: device, + icons: Icons.access_time, + label: 'Setting', + onTap: () { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, + animation1, + animation2) => + TimerScheduleScreen( + switchCode: + 'switch_1', + device: device!, + deviceCode: + 'countdown_1', + ))); + }, + ), + ], + ), + ), + const Expanded(child: SizedBox()) + ], + ), + ), + ], + ), + ); + }, + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/one_touch/one_touch_timer_screen.dart b/lib/features/devices/view/widgets/one_touch/one_touch_timer_screen.dart new file mode 100644 index 0000000..c9525a6 --- /dev/null +++ b/lib/features/devices/view/widgets/one_touch/one_touch_timer_screen.dart @@ -0,0 +1,296 @@ +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/one_touch_bloc/one_touch_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/one_touch_bloc/one_touch_event.dart'; +import 'package:syncrow_app/features/devices/bloc/one_touch_bloc/one_touch_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/shared_widgets/create_schedule.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/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.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) => + OneTouchBloc(switchCode: switchCode, oneTouchId: device.uuid ?? '') + ..add(GetCounterEvent(deviceCode: deviceCode)) + ..add(GetScheduleEvent()), + child: BlocBuilder( + builder: (context, state) { + final oneTouchBloc = 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) { + oneTouchBloc.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: [ + oneTouchBloc.createSchedule == true + ? TextButton( + onPressed: () { + oneTouchBloc.add(ScheduleSave()); + }, + child: const Text('Save')) + : oneTouchBloc.selectedTabIndex == 1 + ? IconButton( + onPressed: () { + // oneTouchBloc.toggleCreateSchedule(); + oneTouchBloc.add( + const ToggleCreateScheduleEvent( + index: 1)); + }, + icon: const Icon(Icons.add), + ) + : const SizedBox(), + ], + ), + child: state is LoadingInitialState + ? 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)), + ), + ), + child: TabBar( + onTap: (value) { + if (value == 0) { + if (oneTouchBloc.createSchedule == + true) { + // oneTouchBloc.toggleCreateSchedule(); + oneTouchBloc.add( + const ToggleCreateScheduleEvent( + index: 0)); + } + oneTouchBloc.add( + const ToggleSelectedEvent( + index: 0)); + } else { + oneTouchBloc.add( + const ToggleSelectedEvent( + index: 1)); + } + }, + indicatorColor: + Colors.white, // Customize the indicator + dividerHeight: 0, + indicatorSize: TabBarIndicatorSize.tab, + indicator: const ShapeDecoration( + color: ColorsManager.slidingBlueColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(20)), + ), + ), + tabs: [ + Tab( + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 10), + child: BodySmall( + text: 'Countdown', + style: context.bodySmall.copyWith( + color: ColorsManager.blackColor, + fontSize: 12, + fontWeight: FontWeight.w400, + ), + ), + ), + ), + Tab( + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 10), + child: Text( + 'Schedule', + style: context.bodySmall.copyWith( + color: ColorsManager.blackColor, + fontSize: 12, + fontWeight: FontWeight.w400, + ), + ), + ), + ), + ], + ), + ), + Expanded( + child: TabBarView( + physics: + const NeverScrollableScrollPhysics(), // Disable swiping + + children: [ + 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) { + oneTouchBloc.add( + SetCounterValue( + deviceCode: + deviceCode, + duration: Duration + .zero)); + } else if (duration != + Duration.zero) { + oneTouchBloc.add( + SetCounterValue( + deviceCode: + deviceCode, + duration: + duration)); + } + }, + child: SvgPicture.asset( + countNum > 0 + ? Assets.pauseIcon + : Assets.playIcon)), + ], + ), + ), + ), + SizedBox( + child: oneTouchBloc.createSchedule == + true + ? CreateSchedule( + onToggleChanged: (bool isOn) { + oneTouchBloc.toggleSchedule = + isOn; + }, + onDateTimeChanged: + (DateTime dateTime) { + oneTouchBloc.selectedTime = + dateTime; + }, + days: oneTouchBloc.days, + selectDays: (List + selectedDays) { + oneTouchBloc.selectedDays = + selectedDays; + }, + ) + : Padding( + padding: const EdgeInsets.only( + top: 10), + child: ScheduleListView( + listSchedule: oneTouchBloc + .listSchedule, // Pass the schedule list here + onDismissed: (scheduleId) { + oneTouchBloc.listSchedule + .removeWhere((schedule) => + schedule + .scheduleId == + scheduleId); + oneTouchBloc.add( + DeleteScheduleEvent( + id: scheduleId)); + }, + onToggleSchedule: + (scheduleId, isEnabled) { + oneTouchBloc.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/one_touch/one_touch_wizard.dart b/lib/features/devices/view/widgets/one_touch/one_touch_wizard.dart new file mode 100644 index 0000000..92013ed --- /dev/null +++ b/lib/features/devices/view/widgets/one_touch/one_touch_wizard.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/one_gang_bloc/one_gang_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/one_gang_bloc/one_gang_event.dart'; +import 'package:syncrow_app/features/devices/bloc/one_gang_bloc/one_gang_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/group_one_gang_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/one_gang/one_gang_list.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; + +class OneGangWizard extends StatelessWidget { + const OneGangWizard({super.key, this.device}); + + final DeviceModel? device; + + @override + Widget build(BuildContext context) { + List groupOneGangModel = []; + + return DefaultScaffold( + child: BlocProvider( + create: (context) => + OneGangBloc(switchCode: '', oneGangId: device?.uuid ?? '')..add(InitialWizardEvent()), + child: BlocBuilder( + builder: (context, state) { + bool allSwitchesOn = false; + + if (state is UpdateGroupState) { + groupOneGangModel = state.oneGangList; + allSwitchesOn = state.allSwitches; + } + return state is LoadingInitialState + ? const Center( + child: + DefaultContainer(width: 50, height: 50, child: CircularProgressIndicator()), + ) + : OneGangList( + oneGangList: groupOneGangModel, + allSwitches: allSwitchesOn, + ); + }, + ), + )); + } +} diff --git a/lib/features/devices/view/widgets/room_page_switch.dart b/lib/features/devices/view/widgets/room_page_switch.dart index 93260dd..639547c 100644 --- a/lib/features/devices/view/widgets/room_page_switch.dart +++ b/lib/features/devices/view/widgets/room_page_switch.dart @@ -13,6 +13,7 @@ import 'package:syncrow_app/features/devices/view/widgets/door_sensor/door_senso 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'; +import 'package:syncrow_app/features/devices/view/widgets/one_touch/one_touch_screen.dart'; import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_gang_Interface.dart'; import 'package:syncrow_app/features/devices/view/widgets/wall_sensor/wall_sensor_interface.dart'; import 'package:syncrow_app/features/devices/view/widgets/ceiling_sensor/ceiling_sensor_interface.dart'; @@ -164,6 +165,13 @@ void showDeviceInterface(DeviceModel device, BuildContext context) { PageRouteBuilder( pageBuilder: (context, animation1, animation2) => DoorSensorScreen(device: device))); + + case DeviceType.OneTouch: + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + OneTouchScreen(device: device))); break; default: } diff --git a/lib/utils/resource_manager/constants.dart b/lib/utils/resource_manager/constants.dart index 2ce280f..725c2ce 100644 --- a/lib/utils/resource_manager/constants.dart +++ b/lib/utils/resource_manager/constants.dart @@ -50,6 +50,7 @@ enum DeviceType { WallSensor, WH, DS, + OneTouch, Other, } @@ -76,6 +77,7 @@ Map devicesTypesMap = { "CUR": DeviceType.Curtain, "WH": DeviceType.WH, "DS": DeviceType.DS, + "1GT": DeviceType.OneTouch, }; Map> devicesFunctionsMap = { DeviceType.AC: [ @@ -284,13 +286,40 @@ Map> devicesFunctionsMap = { FunctionModel( code: 'doorcontact_state', type: functionTypesMap['Raw'], - values: ValueModel.fromJson({}) - ), + values: ValueModel.fromJson({})), FunctionModel( code: 'battery_percentage', type: functionTypesMap['Integer'], - values: ValueModel.fromJson({}) - ), + values: ValueModel.fromJson({})), + ], + DeviceType.OneTouch: [ + 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": 43200, "scale": 0, "step": 1})), + FunctionModel( + code: 'relay_status', + type: functionTypesMap['Enum'], + values: ValueModel.fromJson({ + "range": ['power_off', 'power_on', 'last'] + })), + FunctionModel( + code: 'light_mode', + type: functionTypesMap['Enum'], + values: ValueModel.fromJson({ + "range": ['none', 'relay', 'pos'] + })), + FunctionModel( + code: 'relay_status_1', + type: functionTypesMap['Enum'], + values: ValueModel.fromJson({ + "range": ['power_off', 'power_on', 'last'] + })), ], };