diff --git a/assets/icons/1gang.svg b/assets/icons/1gang.svg new file mode 100644 index 0000000..647dc35 --- /dev/null +++ b/assets/icons/1gang.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/2gang.svg b/assets/icons/2gang.svg new file mode 100644 index 0000000..6cfe191 --- /dev/null +++ b/assets/icons/2gang.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/emptySchedule.svg b/assets/icons/emptySchedule.svg new file mode 100644 index 0000000..da72622 --- /dev/null +++ b/assets/icons/emptySchedule.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/lib/features/devices/bloc/one_gang_bloc/one_gang_bloc.dart b/lib/features/devices/bloc/one_gang_bloc/one_gang_bloc.dart new file mode 100644 index 0000000..987b873 --- /dev/null +++ b/lib/features/devices/bloc/one_gang_bloc/one_gang_bloc.dart @@ -0,0 +1,326 @@ +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/one_gang_bloc/one_gang_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/one_gang_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'; +import 'one_gang_event.dart'; + +class OneGangBloc extends Bloc { + final String oneGangId; + final String switchCode; + OneGangModel deviceStatus = OneGangModel( + firstSwitch: false, + firstCountDown: 0, + ); + Timer? _timer; + + bool oneGangGroup = false; + List devicesList = []; + + OneGangBloc({required this.oneGangId,required this.switchCode}) : super(InitialState()) { + on(_fetchOneGangStatus); + on(_oneGangUpdated); + on(_changeFirstSwitch); + on(_changeSliding); + on(_setCounterValue); + on(_getCounterValue); + on(_onTickTimer); + on(_onClose); + + + + + on(toggleDaySelection); + on(saveSchedule); + on(getSchedule); + on(toggleChange); + on(deleteSchedule); + } + + void _fetchOneGangStatus(InitialEvent event, Emitter emit) async { + emit(LoadingInitialState()); + try { + var response = await DevicesAPI.getDeviceStatus(oneGangId); + List statusModelList = []; + for (var status in response['status']) { + statusModelList.add(StatusModel.fromJson(status)); + } + deviceStatus = OneGangModel.fromJson(statusModelList); + emit(UpdateState(oneGangModel: deviceStatus)); + _listenToChanges(); + } catch (e) { + emit(FailedState(error: e.toString())); + return; + } + } + + _listenToChanges() { + try { + DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$oneGangId'); + 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 = OneGangModel.fromJson(statusList); + if (!isClosed) { + add(OneGangUpdated()); + } + }); + } catch (_) {} + } + + _oneGangUpdated(OneGangUpdated event, Emitter emit) { + emit(UpdateState(oneGangModel: deviceStatus)); + } + + void _changeFirstSwitch(ChangeFirstSwitchStatusEvent event, Emitter emit) async { + emit(LoadingNewSate(oneGangModel: deviceStatus)); + try { + deviceStatus.firstSwitch = !event.value; + emit(UpdateState(oneGangModel: deviceStatus)); + if (_timer != null) { + _timer!.cancel(); + } + _timer = Timer(const Duration(milliseconds: 500), () async { + final response = await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: oneGangGroup ? event.deviceId : oneGangId, + code: 'switch_1', + value: !event.value), + oneGangGroup ? event.deviceId : oneGangId); + + if (!response['success']) { + add(InitialEvent(groupScreen: oneGangGroup)); + } + }); + } catch (_) { + add(InitialEvent(groupScreen: oneGangGroup)); + } + } + + + void _changeSliding(ChangeSlidingSegment event, Emitter emit) async { + emit(ChangeSlidingSegmentState(value: event.value)); + } + + void _setCounterValue(SetCounterValue event, Emitter emit) async { + emit(LoadingNewSate(oneGangModel: deviceStatus)); + int seconds = 0; + try { + seconds = event.duration.inSeconds; + final response = await DevicesAPI.controlDevice( + DeviceControlModel(deviceId: oneGangId, code: event.deviceCode, value: seconds), + oneGangId); + + 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(oneGangId); + List statusModelList = []; + for (var status in response['status']) { + statusModelList.add(StatusModel.fromJson(status)); + } + deviceStatus = OneGangModel.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(ThreeGangSave event, Emitter emit,) async { + try { + if(selectedDays.isNotEmpty) { + emit(LoadingInitialState()); + final response = await DevicesAPI.postSchedule( + category: switchCode, + deviceId: oneGangId, + time: getTimeStampWithoutSeconds(selectedTime).toString(), + code: switchCode, + value: toggleSchedule, + days: selectedDays); + CustomSnackBar.displaySnackBar('Save Successfully'); + add(GetScheduleEvent()); + emit(ThreeGangSaveSchedule()); + toggleCreateSchedule(); + }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: oneGangId , + ); + 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: oneGangId, 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: oneGangId, ); + 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() { + emit(LoadingInitialState()); + createSchedule = !createSchedule; + selectedDays.clear(); + selectedTime=DateTime.now(); + emit(UpdateCreateScheduleState(createSchedule)); + emit(ChangeSlidingSegmentState(value: 1)); + } + + 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(index) { + emit(LoadingInitialState()); + selectedTabIndex = index; + emit(ChangeSlidingSegmentState(value: selectedTabIndex)); + } + +} diff --git a/lib/features/devices/bloc/one_gang_bloc/one_gang_event.dart b/lib/features/devices/bloc/one_gang_bloc/one_gang_event.dart new file mode 100644 index 0000000..af6dee2 --- /dev/null +++ b/lib/features/devices/bloc/one_gang_bloc/one_gang_event.dart @@ -0,0 +1,116 @@ +import 'package:equatable/equatable.dart'; + +abstract class OneGangEvent extends Equatable { + const OneGangEvent(); + + @override + List get props => []; +} + +class LoadingEvent extends OneGangEvent {} + +class OneGangUpdated extends OneGangEvent {} + +class InitialEvent extends OneGangEvent { + final bool groupScreen; + const InitialEvent({required this.groupScreen}); + @override + List get props => [groupScreen]; +} + +class ChangeFirstSwitchStatusEvent extends OneGangEvent { + final bool value; + final String deviceId; + const ChangeFirstSwitchStatusEvent({required this.value, this.deviceId = ''}); + @override + List get props => [value, deviceId]; +} + +class ChangeSecondSwitchStatusEvent extends OneGangEvent { + final bool value; + final String deviceId; + const ChangeSecondSwitchStatusEvent({required this.value, this.deviceId = ''}); + @override + List get props => [value, deviceId]; +} + + +class AllOffEvent extends OneGangEvent {} + +class AllOnEvent extends OneGangEvent {} + +class GroupAllOnEvent extends OneGangEvent {} + +class GroupAllOffEvent extends OneGangEvent {} + +class ChangeSlidingSegment extends OneGangEvent { + final int value; + const ChangeSlidingSegment({required this.value}); + @override + List get props => [value]; +} + +class GetCounterEvent extends OneGangEvent { + final String deviceCode; + const GetCounterEvent({required this.deviceCode}); + @override + List get props => [deviceCode]; +} + +class SetCounterValue extends OneGangEvent { + final Duration duration; + final String deviceCode; + const SetCounterValue({required this.duration, required this.deviceCode}); + @override + List get props => [duration, deviceCode]; +} + +class StartTimer extends OneGangEvent { + final int duration; + + const StartTimer(this.duration); + + @override + List get props => [duration]; +} + +class TickTimer extends OneGangEvent { + final int remainingTime; + + const TickTimer(this.remainingTime); + + @override + List get props => [remainingTime]; +} + +class StopTimer extends OneGangEvent {} + +class OnClose extends OneGangEvent {} + + + + +//------------------- Schedule ----------=--------- +class GetScheduleEvent extends OneGangEvent {} +class ThreeGangSave extends OneGangEvent {} +class ToggleScheduleEvent extends OneGangEvent { + final String id; + final bool toggle; + const ToggleScheduleEvent({required this.toggle,required this.id}); + @override + List get props => [toggle,id]; +} +class ToggleDaySelectionEvent extends OneGangEvent { + + final String key; + + const ToggleDaySelectionEvent({required this.key}); + @override + List get props => [key]; +} +class DeleteScheduleEvent extends OneGangEvent { + final String id; + const DeleteScheduleEvent({required this.id}); + @override + List get props => [id]; +} diff --git a/lib/features/devices/bloc/one_gang_bloc/one_gang_state.dart b/lib/features/devices/bloc/one_gang_bloc/one_gang_state.dart new file mode 100644 index 0000000..17f375a --- /dev/null +++ b/lib/features/devices/bloc/one_gang_bloc/one_gang_state.dart @@ -0,0 +1,89 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/devices/model/groupTwoGangModel.dart'; +import 'package:syncrow_app/features/devices/model/one_gang_model.dart'; +import 'package:syncrow_app/features/devices/model/two_gang_model.dart'; + + +class OneGangState extends Equatable { + const OneGangState(); + + @override + List get props => []; +} + +class InitialState extends OneGangState {} + +class LoadingInitialState extends OneGangState {} + +class UpdateState extends OneGangState { + final OneGangModel oneGangModel; + const UpdateState({required this.oneGangModel}); + + @override + List get props => [TwoGangModel]; +} + +class LoadingNewSate extends OneGangState { + final OneGangModel oneGangModel; + const LoadingNewSate({required this.oneGangModel}); + + @override + List get props => [OneGangModel]; +} + +class UpdateGroupState extends OneGangState { + final List twoGangList; + final bool allSwitches; + + const UpdateGroupState({required this.twoGangList, required this.allSwitches}); + + @override + List get props => [twoGangList, allSwitches]; +} + +class FailedState extends OneGangState { + final String error; + + const FailedState({required this.error}); + + @override + List get props => [error]; +} + +class ChangeSlidingSegmentState extends OneGangState { + final int value; + + const ChangeSlidingSegmentState({required this.value}); + + @override + List get props => [value]; +} + +class UpdateTimerState extends OneGangState { + final int seconds; + const UpdateTimerState({required this.seconds}); + + @override + List get props => [seconds]; +} + +class TimerRunInProgress extends OneGangState { + final int remainingTime; + + const TimerRunInProgress(this.remainingTime); + + @override + List get props => [remainingTime]; +} + +class TimerRunComplete extends OneGangState {} +class ThreeGangSaveSchedule extends OneGangState {} +class IsToggleState extends OneGangState { + final bool? onOff; + const IsToggleState({this.onOff}); +} +class ChangeTimeState extends OneGangState {} +class UpdateCreateScheduleState extends OneGangState { + final bool createSchedule; + UpdateCreateScheduleState(this.createSchedule); +} \ No newline at end of file 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 53bb034..a707013 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 @@ -1,4 +1,5 @@ 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'; @@ -7,12 +8,16 @@ import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_sta 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_three_gang_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/features/devices/model/three_gang_model.dart'; import 'package:syncrow_app/services/api/devices_api.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; + class ThreeGangBloc extends Bloc { final String threeGangId; + final String switchCode; ThreeGangModel deviceStatus = ThreeGangModel( firstSwitch: false, secondSwitch: false, @@ -30,7 +35,7 @@ class ThreeGangBloc extends Bloc { List groupThreeGangList = []; bool allSwitchesOn = true; - ThreeGangBloc({required this.threeGangId}) : super(InitialState()) { + ThreeGangBloc({required this.threeGangId,required this.switchCode}) : super(InitialState()) { on(_fetchThreeGangStatus); on(_threeGangUpdated); on(_changeFirstSwitch); @@ -45,6 +50,14 @@ class ThreeGangBloc extends Bloc { on(_onClose); on(_groupAllOn); on(_groupAllOff); + + + + on(toggleDaySelection); + on(saveSchedule); + on(getSchedule); + on(toggleChange); + on(deleteSchedule); } void _fetchThreeGangStatus(InitialEvent event, Emitter emit) async { @@ -190,7 +203,6 @@ class ThreeGangBloc extends Bloc { if (_timer != null) { _timer!.cancel(); } - _timer = Timer(const Duration(milliseconds: 500), () async { final response = await DevicesAPI.controlDevice( DeviceControlModel( @@ -460,9 +472,6 @@ class ThreeGangBloc extends Bloc { void _onClose(OnClose event, Emitter emit) { _timer?.cancel(); - // _firstSwitchTimer?.cancel(); - // _secondSwitchTimer?.cancel(); - // _thirdSwitchTimer?.cancel(); } void _onStartTimer(int seconds) { @@ -480,4 +489,143 @@ class ThreeGangBloc extends Bloc { 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 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)); + } + + Future saveSchedule(ThreeGangSave event, Emitter emit,) async { + try { + if(selectedDays.isNotEmpty) { + emit(LoadingInitialState()); + final response = await DevicesAPI.postSchedule( + category: switchCode, + deviceId: threeGangId, + time: getTimeStampWithoutSeconds(selectedTime).toString(), + code: switchCode, + value: toggleSchedule, + days: selectedDays); + CustomSnackBar.displaySnackBar('Save Successfully'); + add(GetScheduleEvent()); + emit(ThreeGangSaveSchedule()); + toggleCreateSchedule(); + }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: threeGangId , + ); + 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: threeGangId, 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: threeGangId, ); + 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() { + emit(LoadingInitialState()); + createSchedule = !createSchedule; + selectedDays.clear(); + selectedTime=DateTime.now(); + emit(UpdateCreateScheduleState(createSchedule)); + emit(ChangeSlidingSegmentState(value: 1)); + } + + int selectedTabIndex = 0; + + void toggleSelectedIndex(index) { + emit(LoadingInitialState()); + selectedTabIndex = index; + emit(ChangeSlidingSegmentState(value: selectedTabIndex)); + } + + bool toggleSchedule = true; + List selectedDays = []; + bool createSchedule = false; + List listSchedule = []; + DateTime? selectedTime=DateTime.now(); + } diff --git a/lib/features/devices/bloc/three_gang_bloc/three_gang_event.dart b/lib/features/devices/bloc/three_gang_bloc/three_gang_event.dart index 5cd3cfb..89ad267 100644 --- a/lib/features/devices/bloc/three_gang_bloc/three_gang_event.dart +++ b/lib/features/devices/bloc/three_gang_bloc/three_gang_event.dart @@ -93,3 +93,29 @@ class TickTimer extends ThreeGangEvent { class StopTimer extends ThreeGangEvent {} class OnClose extends ThreeGangEvent {} + + +//------------------- Schedule ----------=--------- +class GetScheduleEvent extends ThreeGangEvent {} +class ThreeGangSave extends ThreeGangEvent {} +class ToggleScheduleEvent extends ThreeGangEvent { + final String id; + final bool toggle; + const ToggleScheduleEvent({required this.toggle,required this.id}); + @override + List get props => [toggle,id]; +} +class ToggleDaySelectionEvent extends ThreeGangEvent { + + final String key; + + const ToggleDaySelectionEvent({required this.key}); + @override + List get props => [key]; +} +class DeleteScheduleEvent extends ThreeGangEvent { + final String id; + const DeleteScheduleEvent({required this.id}); + @override + List get props => [id]; +} diff --git a/lib/features/devices/bloc/three_gang_bloc/three_gang_state.dart b/lib/features/devices/bloc/three_gang_bloc/three_gang_state.dart index 6760dea..e2c555e 100644 --- a/lib/features/devices/bloc/three_gang_bloc/three_gang_state.dart +++ b/lib/features/devices/bloc/three_gang_bloc/three_gang_state.dart @@ -75,3 +75,15 @@ class TimerRunInProgress extends ThreeGangState { } class TimerRunComplete extends ThreeGangState {} + + +class ThreeGangSaveSchedule extends ThreeGangState {} +class IsToggleState extends ThreeGangState { + final bool? onOff; + const IsToggleState({this.onOff}); +} +class ChangeTimeState extends ThreeGangState {} +class UpdateCreateScheduleState extends ThreeGangState { + final bool createSchedule; + UpdateCreateScheduleState(this.createSchedule); +} diff --git a/lib/features/devices/bloc/two_gang_bloc/two_gang_bloc.dart b/lib/features/devices/bloc/two_gang_bloc/two_gang_bloc.dart new file mode 100644 index 0000000..b2f1e45 --- /dev/null +++ b/lib/features/devices/bloc/two_gang_bloc/two_gang_bloc.dart @@ -0,0 +1,553 @@ +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/two_gang_bloc/two_gang_event.dart'; +import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_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/groupTwoGangModel.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/features/devices/model/two_gang_model.dart'; +import 'package:syncrow_app/services/api/devices_api.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; + +class TwoGangBloc extends Bloc { + final String twoGangId; + final String switchCode; + TwoGangModel deviceStatus = TwoGangModel( + firstSwitch: false, + secondSwitch: false, + firstCountDown: 0, + secondCountDown: 0, + ); + Timer? _timer; + + bool twoGangGroup = false; + List devicesList = []; + List groupTwoGangList = []; + bool allSwitchesOn = true; + bool toggleSchedule = true; + List selectedDays = []; + + bool createSchedule = false; + List listSchedule = []; + + TwoGangBloc({required this.twoGangId,required this.switchCode}) : super(InitialState()) { + on(_fetchTwoGangStatus); + on(_twoGangUpdated); + on(_changeFirstSwitch); + on(_changeSecondSwitch); + on(_allOff); + on(_allOn); + on(_changeSliding); + on(_setCounterValue); + on(_getCounterValue); + on(_onTickTimer); + on(_onClose); + on(_groupAllOn); + on(_groupAllOff); + on(toggleDaySelection); + on(saveSchedule); + on(getSchedule); + on(toggleRepeat); + on(deleteSchedule); + + } + + DateTime? selectedTime = DateTime.now(); + + void toggleCreateSchedule() { + emit(LoadingInitialState()); + createSchedule = !createSchedule; + selectedDays.clear(); + selectedTime=DateTime.now(); + emit(UpdateCreateScheduleState(createSchedule)); + emit(ChangeSlidingSegmentState(value: 1)); + } + int selectedTabIndex = 0; + + void toggleSelectedIndex(index) { + emit(LoadingInitialState()); + selectedTabIndex = index; + emit(ChangeSlidingSegmentState(value: selectedTabIndex)); + } + + void _fetchTwoGangStatus(InitialEvent event, Emitter emit) async { + emit(LoadingInitialState()); + try { + twoGangGroup = event.groupScreen; + if (twoGangGroup) { + devicesList = []; + groupTwoGangList = []; + allSwitchesOn = true; + devicesList = await DevicesAPI.getDeviceByGroupName( + HomeCubit.getInstance().selectedSpace?.id ?? '', '2G'); + + 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 = TwoGangModel.fromJson(statusModelList); + + groupTwoGangList.add(GroupTwoGangModel( + deviceId: devicesList[i].uuid ?? '', + deviceName: devicesList[i].name ?? '', + firstSwitch: deviceStatus.firstSwitch, + secondSwitch: deviceStatus.secondSwitch, + )); + } + + if (groupTwoGangList.isNotEmpty) { + groupTwoGangList.firstWhere((element) { + if (!element.firstSwitch || !element.secondSwitch) { + allSwitchesOn = false; + } + return true; + }); + } + emit(UpdateGroupState( + twoGangList: groupTwoGangList, allSwitches: allSwitchesOn)); + } else { + var response = await DevicesAPI.getDeviceStatus(twoGangId); + List statusModelList = []; + for (var status in response['status']) { + statusModelList.add(StatusModel.fromJson(status)); + } + deviceStatus = TwoGangModel.fromJson(statusModelList); + emit(UpdateState(twoGangModel: deviceStatus)); + _listenToChanges(); + } + } catch (e) { + emit(FailedState(error: e.toString())); + return; + } + } + + _listenToChanges() { + try { + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$twoGangId'); + 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 = TwoGangModel.fromJson(statusList); + if (!isClosed) { + add(TwoGangUpdated()); + } + }); + } catch (_) {} + } + + _twoGangUpdated(TwoGangUpdated event, Emitter emit) { + emit(UpdateState(twoGangModel: deviceStatus)); + } + + void _changeFirstSwitch( + ChangeFirstSwitchStatusEvent event, Emitter emit) async { + emit(LoadingNewSate(twoGangModel: deviceStatus)); + try { + deviceStatus.firstSwitch = !event.value; + emit(UpdateState(twoGangModel: deviceStatus)); + if (_timer != null) { + _timer!.cancel(); + } + + _timer = Timer(const Duration(milliseconds: 500), () async { + final response = await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: twoGangGroup ? event.deviceId : twoGangId, + code: 'switch_1', + value: !event.value), + twoGangGroup ? event.deviceId : twoGangId); + + if (!response['success']) { + add(InitialEvent(groupScreen: twoGangGroup)); + } + }); + } catch (_) { + add(InitialEvent(groupScreen: twoGangGroup)); + } + } + + void _changeSecondSwitch( + ChangeSecondSwitchStatusEvent event, Emitter emit) async { + emit(LoadingNewSate(twoGangModel: deviceStatus)); + try { + deviceStatus.secondSwitch = !event.value; + emit(UpdateState(twoGangModel: deviceStatus)); + if (_timer != null) { + _timer!.cancel(); + } + _timer = Timer(const Duration(milliseconds: 500), () async { + final response = await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: twoGangGroup ? event.deviceId : twoGangId, + code: 'switch_2', + value: !event.value), + twoGangGroup ? event.deviceId : twoGangId); + + if (!response['success']) { + add(InitialEvent(groupScreen: twoGangGroup)); + } + }); + } catch (_) { + add(InitialEvent(groupScreen: twoGangGroup)); + } + } + + void _allOff(AllOffEvent event, Emitter emit) async { + emit(LoadingNewSate(twoGangModel: deviceStatus)); + + try { + deviceStatus.firstSwitch = false; + deviceStatus.secondSwitch = false; + emit(UpdateState(twoGangModel: deviceStatus)); + + final response = await Future.wait([ + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: twoGangId, + code: 'switch_1', + value: deviceStatus.firstSwitch), + twoGangId), + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: twoGangId, + code: 'switch_2', + value: deviceStatus.secondSwitch), + twoGangId), + ]); + + if (response.every((element) => !element['success'])) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: false)); + } + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: false)); + } + } + + void _allOn(AllOnEvent event, Emitter emit) async { + emit(LoadingNewSate(twoGangModel: deviceStatus)); + try { + deviceStatus.firstSwitch = true; + deviceStatus.secondSwitch = true; + emit(UpdateState(twoGangModel: deviceStatus)); + final response = await Future.wait([ + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: twoGangId, + code: 'switch_1', + value: deviceStatus.firstSwitch), + twoGangId), + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: twoGangId, + code: 'switch_2', + value: deviceStatus.secondSwitch), + twoGangId), + ]); + if (response.every((element) => !element['success'])) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: false)); + } + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: false)); + } + } + + void _groupAllOn(GroupAllOnEvent event, Emitter emit) async { + emit(LoadingNewSate(twoGangModel: deviceStatus)); + try { + for (int i = 0; i < groupTwoGangList.length; i++) { + groupTwoGangList[i].firstSwitch = true; + groupTwoGangList[i].secondSwitch = true; + } + emit(UpdateGroupState(twoGangList: groupTwoGangList, allSwitches: true)); + + for (int i = 0; i < groupTwoGangList.length; i++) { + final response = await Future.wait([ + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: groupTwoGangList[i].deviceId, + code: 'switch_1', + value: true), + groupTwoGangList[i].deviceId), + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: groupTwoGangList[i].deviceId, + code: 'switch_2', + value: true), + groupTwoGangList[i].deviceId), + ]); + + if (response.every((element) => !element['success'])) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: true)); + break; + } + } + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: true)); + } + } + + void _groupAllOff(GroupAllOffEvent event, Emitter emit) async { + emit(LoadingNewSate(twoGangModel: deviceStatus)); + try { + for (int i = 0; i < groupTwoGangList.length; i++) { + groupTwoGangList[i].firstSwitch = false; + groupTwoGangList[i].secondSwitch = false; + } + emit(UpdateGroupState(twoGangList: groupTwoGangList, allSwitches: false)); + + for (int i = 0; i < groupTwoGangList.length; i++) { + final response = await Future.wait([ + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: groupTwoGangList[i].deviceId, + code: 'switch_1', + value: false), + groupTwoGangList[i].deviceId), + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: groupTwoGangList[i].deviceId, + code: 'switch_2', + value: false), + groupTwoGangList[i].deviceId), + ]); + + if (response.every((element) => !element['success'])) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: true)); + break; + } + } + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: true)); + } + } + + void _changeSliding( + ChangeSlidingSegment event, Emitter emit) async { + emit(ChangeSlidingSegmentState(value: event.value)); + } + + void _setCounterValue( + SetCounterValue event, Emitter emit) async { + emit(LoadingNewSate(twoGangModel: deviceStatus)); + int seconds = 0; + try { + seconds = event.duration.inSeconds; + final response = await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: twoGangId, + code: event.deviceCode, + value: seconds), + twoGangId); + + if (response['success'] ?? false) { + if (event.deviceCode == 'countdown_1') { + deviceStatus.firstCountDown = seconds; + } + else if (event.deviceCode == 'countdown_2') { + deviceStatus.secondCountDown = 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 { + add(GetScheduleEvent()); + var response = await DevicesAPI.getDeviceStatus(twoGangId); + List statusModelList = []; + for (var status in response['status']) { + statusModelList.add(StatusModel.fromJson(status)); + } + deviceStatus = TwoGangModel.fromJson(statusModelList); + + if (event.deviceCode == 'countdown_1') { + deviceStatus.firstCountDown > 0 + ? _onStartTimer(deviceStatus.firstCountDown) + : emit(UpdateTimerState(seconds: deviceStatus.firstCountDown)); + } else if (event.deviceCode == 'countdown_2') { + deviceStatus.secondCountDown > 0 + ? _onStartTimer(deviceStatus.secondCountDown) + : emit(UpdateTimerState(seconds: deviceStatus.secondCountDown)); + } + } 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 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)); + } + + Future saveSchedule(TwoGangSave event, Emitter emit,) async { + try { + if(selectedDays.isNotEmpty) { + emit(LoadingInitialState()); + final response = await DevicesAPI.postSchedule( + category: switchCode, + deviceId: twoGangId, + time: getTimeStampWithoutSeconds(selectedTime).toString(), + code: switchCode, + value: toggleSchedule, + days: selectedDays); + CustomSnackBar.displaySnackBar('Save Successfully'); + add(GetScheduleEvent()); + emit(TwoGangSaveSchedule()); + toggleCreateSchedule(); + }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: twoGangId, + ); + 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 toggleRepeat( + ToggleScheduleEvent event, Emitter emit) async { + try { + emit(LoadingInitialState()); + final response = await DevicesAPI.changeSchedule( + scheduleId: event.id, + deviceUuid: twoGangId, + 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: twoGangId, ); + 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())); + } + } +} diff --git a/lib/features/devices/bloc/two_gang_bloc/two_gang_event.dart b/lib/features/devices/bloc/two_gang_bloc/two_gang_event.dart new file mode 100644 index 0000000..9fd4737 --- /dev/null +++ b/lib/features/devices/bloc/two_gang_bloc/two_gang_event.dart @@ -0,0 +1,130 @@ +import 'package:equatable/equatable.dart'; + +abstract class TwoGangEvent extends Equatable { + const TwoGangEvent(); + + @override + List get props => []; +} + +class LoadingEvent extends TwoGangEvent {} + +class TwoGangUpdated extends TwoGangEvent {} +class TwoGangSave extends TwoGangEvent {} +class ToggleScheduleEvent extends TwoGangEvent { + final String id; + final bool toggle; + const ToggleScheduleEvent({required this.toggle,required this.id}); + @override + List get props => [toggle,id]; +} +class errorMessage extends TwoGangEvent {} +class ToggleDaySelectionEvent extends TwoGangEvent { + + final String key; + + const ToggleDaySelectionEvent({required this.key}); + @override + List get props => [key]; +} + +class InitialEvent extends TwoGangEvent { + final bool groupScreen; + const InitialEvent({required this.groupScreen}); + @override + List get props => [groupScreen]; +} + +class ChangeFirstSwitchStatusEvent extends TwoGangEvent { + final bool value; + final String deviceId; + const ChangeFirstSwitchStatusEvent({required this.value, this.deviceId = ''}); + @override + List get props => [value, deviceId]; +} + +class ChangeSecondSwitchStatusEvent extends TwoGangEvent { + final bool value; + final String deviceId; + const ChangeSecondSwitchStatusEvent({required this.value, this.deviceId = ''}); + @override + List get props => [value, deviceId]; +} + + +class AllOffEvent extends TwoGangEvent {} + +class AllOnEvent extends TwoGangEvent {} + +class GroupAllOnEvent extends TwoGangEvent {} + +class GroupAllOffEvent extends TwoGangEvent {} + + +// two_gang_event.dart +class ToggleCreateScheduleEvent extends TwoGangEvent {} + + + + +class ChangeSlidingSegment extends TwoGangEvent { + final int value; + const ChangeSlidingSegment({required this.value}); + @override + List get props => [value]; +} + +class GetCounterEvent extends TwoGangEvent { + final String deviceCode; + const GetCounterEvent({required this.deviceCode}); + @override + List get props => [deviceCode]; +} + +class SetCounterValue extends TwoGangEvent { + final Duration duration; + final String deviceCode; + const SetCounterValue({required this.duration, required this.deviceCode}); + @override + List get props => [duration, deviceCode]; +} + +class StartTimer extends TwoGangEvent { + final int duration; + + const StartTimer(this.duration); + + @override + List get props => [duration]; +} + +class TickTimer extends TwoGangEvent { + final int remainingTime; + + const TickTimer(this.remainingTime); + + @override + List get props => [remainingTime]; +} + +class StopTimer extends TwoGangEvent {} + +class OnClose extends TwoGangEvent {} + + +class GetScheduleEvent extends TwoGangEvent {} +class DeleteScheduleEvent extends TwoGangEvent { + final String id; + const DeleteScheduleEvent({required this.id}); + @override + List get props => [id]; +} +class TabChangedEvent extends TwoGangEvent { + final int index; + TabChangedEvent({required this.index}); +} + + + + + diff --git a/lib/features/devices/bloc/two_gang_bloc/two_gang_state.dart b/lib/features/devices/bloc/two_gang_bloc/two_gang_state.dart new file mode 100644 index 0000000..72c8537 --- /dev/null +++ b/lib/features/devices/bloc/two_gang_bloc/two_gang_state.dart @@ -0,0 +1,92 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/devices/model/groupTwoGangModel.dart'; +import 'package:syncrow_app/features/devices/model/two_gang_model.dart'; + + +class TwoGangState extends Equatable { + const TwoGangState(); + + @override + List get props => []; +} + +class InitialState extends TwoGangState {} +class TwoGangSaveSchedule extends TwoGangState {} +class ChangeTimeState extends TwoGangState {} + +class LoadingInitialState extends TwoGangState {} + +class UpdateState extends TwoGangState { + final TwoGangModel twoGangModel; + const UpdateState({required this.twoGangModel}); + + @override + List get props => [TwoGangModel]; +} + +class LoadingNewSate extends TwoGangState { + final TwoGangModel twoGangModel; + const LoadingNewSate({required this.twoGangModel}); + + @override + List get props => [TwoGangModel]; +} + +class UpdateGroupState extends TwoGangState { + final List twoGangList; + final bool allSwitches; + + const UpdateGroupState({required this.twoGangList, required this.allSwitches}); + + @override + List get props => [twoGangList, allSwitches]; +} + +class FailedState extends TwoGangState { + final String error; + + const FailedState({required this.error}); + + @override + List get props => [error]; +} + +class ChangeSlidingSegmentState extends TwoGangState { + final int value; + + const ChangeSlidingSegmentState({required this.value}); + + @override + List get props => [value]; +} + +class UpdateTimerState extends TwoGangState { + final int seconds; + const UpdateTimerState({required this.seconds}); + + @override + List get props => [seconds]; +} + +class TimerRunInProgress extends TwoGangState { + final int remainingTime; + + const TimerRunInProgress(this.remainingTime); + + @override + List get props => [remainingTime]; +} + +class TimerRunComplete extends TwoGangState {} +class IsToggleState extends TwoGangState { + final bool? onOff; + const IsToggleState({this.onOff}); +} + + + +// two_gang_state.dart +class UpdateCreateScheduleState extends TwoGangState { + final bool createSchedule; + UpdateCreateScheduleState(this.createSchedule); +} diff --git a/lib/features/devices/model/device_model.dart b/lib/features/devices/model/device_model.dart index 82fac11..c5ba2ae 100644 --- a/lib/features/devices/model/device_model.dart +++ b/lib/features/devices/model/device_model.dart @@ -58,6 +58,10 @@ class DeviceModel { tempIcon = Assets.assetsIcons3GangSwitch; } else if (type == DeviceType.Gateway) { tempIcon = Assets.assetsIconsGateway; + } else if (type == DeviceType.OneGang) { + tempIcon = Assets.oneGang; + } else if (type == DeviceType.TwoGang) { + tempIcon = Assets.twoGang; } else { tempIcon = Assets.assetsIconsLogo; } diff --git a/lib/features/devices/model/groupTwoGangModel.dart b/lib/features/devices/model/groupTwoGangModel.dart new file mode 100644 index 0000000..f385b4e --- /dev/null +++ b/lib/features/devices/model/groupTwoGangModel.dart @@ -0,0 +1,13 @@ +class GroupTwoGangModel { + final String deviceId; + final String deviceName; + bool firstSwitch; + bool secondSwitch; + + GroupTwoGangModel({ + required this.deviceId, + required this.deviceName, + required this.firstSwitch, + required this.secondSwitch, + }); +} diff --git a/lib/features/devices/model/one_gang_model.dart b/lib/features/devices/model/one_gang_model.dart new file mode 100644 index 0000000..4e73231 --- /dev/null +++ b/lib/features/devices/model/one_gang_model.dart @@ -0,0 +1,30 @@ + +import 'package:syncrow_app/features/devices/model/status_model.dart'; + +class OneGangModel { + bool firstSwitch; + int firstCountDown; + + OneGangModel( + {required this.firstSwitch, + required this.firstCountDown, + }); + + factory OneGangModel.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 OneGangModel( + firstSwitch: _switch, + firstCountDown: _count, + ); + } +} diff --git a/lib/features/devices/model/schedule_model.dart b/lib/features/devices/model/schedule_model.dart new file mode 100644 index 0000000..0261046 --- /dev/null +++ b/lib/features/devices/model/schedule_model.dart @@ -0,0 +1,83 @@ +import 'package:intl/intl.dart'; + +class ScheduleModel { + String category; + bool enable; + ScheduleFunctionData function; + String time; + String scheduleId; + String timezoneId; + List days; + + ScheduleModel({ + required this.category, + required this.enable, + required this.function, + required this.time, + required this.scheduleId, + required this.timezoneId, + required this.days, + }); + + // Factory method to create an instance from JSON + factory ScheduleModel.fromJson(Map json) { + return ScheduleModel( + category: json['category'], + enable: json['enable'], + function: ScheduleFunctionData.fromJson(json['function']), + time:getTimeIn12HourFormat( json['time']), + scheduleId: json['scheduleId'], + timezoneId: json['timezoneId'], + days: List.from(json['days']), + ); + } + + // Method to convert an instance into JSON + Map toJson() { + return { + 'category': category, + 'enable': enable, + 'function': function.toJson(), + 'time': time, + 'scheduleId': scheduleId, + 'timezoneId': timezoneId, + 'days': days, + }; + } +} + +class ScheduleFunctionData { + String code; + bool value; + + ScheduleFunctionData({ + required this.code, + required this.value, + }); + + // Factory method to create an instance from JSON + factory ScheduleFunctionData.fromJson(Map json) { + return ScheduleFunctionData( + code: json['code'], + value: json['value'], + ); + } + + // Method to convert an instance into JSON + Map toJson() { + return { + 'code': code, + 'value': value, + }; + } +} + +String getTimeIn12HourFormat(time) { + // Parse the time string (in HH:mm format) + final DateFormat inputFormat = DateFormat("HH:mm"); + final DateFormat outputFormat = DateFormat("h:mm a"); // 12-hour format with AM/PM + + // Convert the time string + DateTime parsedTime = inputFormat.parse(time); + return outputFormat.format(parsedTime); +} diff --git a/lib/features/devices/model/two_gang_model.dart b/lib/features/devices/model/two_gang_model.dart new file mode 100644 index 0000000..00f88d1 --- /dev/null +++ b/lib/features/devices/model/two_gang_model.dart @@ -0,0 +1,40 @@ +import 'package:syncrow_app/features/devices/model/status_model.dart'; + +class TwoGangModel { + bool firstSwitch; + bool secondSwitch; + int firstCountDown; + int secondCountDown; + + TwoGangModel( + {required this.firstSwitch, + required this.secondSwitch, + required this.firstCountDown, + required this.secondCountDown, + }); + + factory TwoGangModel.fromJson(List jsonList) { + late bool _firstSwitch; + late bool _secondSwitch; + late int _firstCount; + late int _secondCount; + + for (int i = 0; i < jsonList.length; i++) { + if (jsonList[i].code == 'switch_1') { + _firstSwitch = jsonList[i].value ?? false; + } else if (jsonList[i].code == 'switch_2') { + _secondSwitch = jsonList[i].value ?? false; + }else if (jsonList[i].code == 'countdown_1') { + _firstCount = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'countdown_2') { + _secondCount = jsonList[i].value ?? 0; + } + } + return TwoGangModel( + firstSwitch: _firstSwitch, + secondSwitch: _secondSwitch, + firstCountDown: _firstCount, + secondCountDown: _secondCount, + ); + } +} diff --git a/lib/features/devices/view/widgets/devices_view_body.dart b/lib/features/devices/view/widgets/devices_view_body.dart index 9ee89a6..53fa8a3 100644 --- a/lib/features/devices/view/widgets/devices_view_body.dart +++ b/lib/features/devices/view/widgets/devices_view_body.dart @@ -79,8 +79,7 @@ class DevicesViewBody extends StatelessWidget { ), if (HomeCubit.getInstance().selectedSpace != null) if (HomeCubit.getInstance().selectedSpace!.rooms != null) - ...HomeCubit.getInstance().selectedSpace!.rooms!.map( - (room) { + ...HomeCubit.getInstance().selectedSpace!.rooms!.map((room) { return RoomPage( room: room, ); diff --git a/lib/features/devices/view/widgets/one_gang/one_gang_Interface.dart b/lib/features/devices/view/widgets/one_gang/one_gang_Interface.dart new file mode 100644 index 0000000..636a730 --- /dev/null +++ b/lib/features/devices/view/widgets/one_gang/one_gang_Interface.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart'; +import 'package:syncrow_app/features/devices/view/widgets/one_gang/one_gang_screen.dart'; +import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_gang_screen.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/constants.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class OneGangInterface extends StatelessWidget { + const OneGangInterface({super.key, this.gangSwitch}); + + final DeviceModel? gangSwitch; + @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: gangSwitch != null + ? DeviceAppbar( + deviceName: gangSwitch!.name!, + deviceUuid: gangSwitch!.uuid!, + ) + : AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + title: BodyLarge( + text: gangSwitch?.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: OneGangScreen(device: gangSwitch), + ), + ), + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/one_gang/one_gang_screen.dart b/lib/features/devices/view/widgets/one_gang/one_gang_screen.dart new file mode 100644 index 0000000..21aee6d --- /dev/null +++ b/lib/features/devices/view/widgets/one_gang/one_gang_screen.dart @@ -0,0 +1,171 @@ +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_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/one_gang_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/one_gang/one_gang_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_small.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import '../../../bloc/one_gang_bloc/one_gang_event.dart'; + +class OneGangScreen extends StatelessWidget { + const OneGangScreen({super.key, this.device}); + + final DeviceModel? device; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => OneGangBloc( + switchCode: 'switch_1', + oneGangId : device?.uuid ?? '') + ..add(const InitialEvent(groupScreen:false)), + child: BlocBuilder( + builder: (context, state) { + OneGangModel oneGangModel = OneGangModel( + firstSwitch: false, + firstCountDown: 0, + ); + + if (state is LoadingNewSate) { + oneGangModel = state.oneGangModel; + } else if (state is UpdateState) { + oneGangModel = state.oneGangModel; + } + 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: oneGangModel.firstSwitch, + action: () { + BlocProvider.of(context).add( + ChangeFirstSwitchStatusEvent( + value: oneGangModel.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: [ + const SizedBox( + width: 20, + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Card( + elevation: 3, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100), + ), + child: GestureDetector( + 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.access_time, + color: + ColorsManager.primaryColorWithOpacity, + size: 25, + ), + ), + ), + ], + ), + ), + ), + const SizedBox(height: 10), + BodySmall( + text: "Timer", + style: context.bodyMedium.copyWith( + color: ColorsManager.textPrimaryColor, + ), + ), + ], + ), + + ], + ), + ), + Expanded(child: SizedBox()) + ], + ), + ), + ], + ), + ); + }, + ), + ); + + } +} diff --git a/lib/features/devices/view/widgets/one_gang/one_gang_timer_screen.dart b/lib/features/devices/view/widgets/one_gang/one_gang_timer_screen.dart new file mode 100644 index 0000000..605d5d5 --- /dev/null +++ b/lib/features/devices/view/widgets/one_gang/one_gang_timer_screen.dart @@ -0,0 +1,258 @@ +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_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/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) => + OneGangBloc(switchCode: switchCode, oneGangId: device.uuid ?? '') + ..add(GetCounterEvent(deviceCode: deviceCode)) + ..add(GetScheduleEvent()), + 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: 'Schedule', + fontColor: ColorsManager.primaryColor, + fontWeight: FontsManager.bold, + ), + actions: [ + oneGangBloc.createSchedule == true ? + TextButton( + onPressed: () { + oneGangBloc.add(ThreeGangSave()); + }, + child: const Text('Save') + ) : + oneGangBloc.selectedTabIndex==1? IconButton( + onPressed: () { + oneGangBloc.toggleCreateSchedule(); + }, + icon: const Icon(Icons.add), + ):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(oneGangBloc.createSchedule == true){ + oneGangBloc.toggleCreateSchedule(); + } + oneGangBloc.toggleSelectedIndex(0); + }else{ + oneGangBloc.toggleSelectedIndex(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( + 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) { + oneGangBloc.add(SetCounterValue( + deviceCode: deviceCode, + duration: Duration.zero)); + } else if (duration != Duration.zero) { + oneGangBloc.add(SetCounterValue( + deviceCode: deviceCode, + duration: duration)); + } + }, + child: SvgPicture.asset(countNum > 0 + ? Assets.pauseIcon + : Assets.playIcon)), + ], + ), + ), + ), + Column( + mainAxisAlignment:oneGangBloc.listSchedule.isNotEmpty? + MainAxisAlignment.start:MainAxisAlignment.center, + children: [ + SizedBox( + child: oneGangBloc.createSchedule == true ? + CreateSchedule( + onToggleChanged: (bool isOn) { + oneGangBloc.toggleSchedule = isOn; + }, + onDateTimeChanged: (DateTime dateTime) { + oneGangBloc.selectedTime=dateTime; + }, + days: oneGangBloc.days, + selectDays: (List selectedDays) { + oneGangBloc.selectedDays = selectedDays; + }, + ) + : + Padding( + padding: const EdgeInsets.only(top: 10), + child: ScheduleListView( + listSchedule: oneGangBloc.listSchedule, // Pass the schedule list here + onDismissed: (scheduleId) { + oneGangBloc.listSchedule.removeWhere((schedule) => schedule.scheduleId == scheduleId); + oneGangBloc.add(DeleteScheduleEvent(id: scheduleId)); + }, + onToggleSchedule: (scheduleId, isEnabled) { + oneGangBloc.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/room_page_switch.dart b/lib/features/devices/view/widgets/room_page_switch.dart index 980de66..cfb5445 100644 --- a/lib/features/devices/view/widgets/room_page_switch.dart +++ b/lib/features/devices/view/widgets/room_page_switch.dart @@ -11,6 +11,10 @@ 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/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_gang/one_gang_screen.dart'; +import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_gang_Interface.dart'; +import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_gang_screen.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'; import 'package:syncrow_app/features/devices/view/widgets/smart_door/door_interface.dart'; @@ -126,13 +130,24 @@ void showDeviceInterface(DeviceModel device, BuildContext context) { break; case DeviceType.LightBulb: navigateToInterface(LightInterface(light: device), context); + case DeviceType.OneGang: + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + OneGangInterface(gangSwitch: device))); + case DeviceType.TwoGang: + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + TwoGangInterface(gangSwitch: device))); case DeviceType.ThreeGang: Navigator.push( context, PageRouteBuilder( pageBuilder: (context, animation1, animation2) => ThreeGangInterface(gangSwitch: device))); - // navigateToInterface(ThreeGangInterface(gangSwitch: device), context); break; default: } diff --git a/lib/features/devices/view/widgets/rooms_slider.dart b/lib/features/devices/view/widgets/rooms_slider.dart index 02d85f0..a53efb3 100644 --- a/lib/features/devices/view/widgets/rooms_slider.dart +++ b/lib/features/devices/view/widgets/rooms_slider.dart @@ -22,6 +22,7 @@ class RoomsSlider extends StatelessWidget { onPageChanged: (index) { HomeCubit.getInstance().roomSliderPageChanged(index); }, + children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 15), diff --git a/lib/features/devices/view/widgets/three_gang/gang_switch.dart b/lib/features/devices/view/widgets/three_gang/gang_switch.dart index c0bd8f9..8b5dc55 100644 --- a/lib/features/devices/view/widgets/three_gang/gang_switch.dart +++ b/lib/features/devices/view/widgets/three_gang/gang_switch.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; // import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; -// import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart'; -// import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_state.dart'; +// import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/two_gang_bloc.dart'; +// import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/two_gang_state.dart'; import 'package:syncrow_app/features/devices/model/device_model.dart'; import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; diff --git a/lib/features/devices/view/widgets/three_gang/schedule_screen.dart b/lib/features/devices/view/widgets/three_gang/schedule_screen.dart index 1c527e3..8e1388a 100644 --- a/lib/features/devices/view/widgets/three_gang/schedule_screen.dart +++ b/lib/features/devices/view/widgets/three_gang/schedule_screen.dart @@ -63,7 +63,8 @@ class ScheduleScreen extends StatelessWidget { Navigator.push( context, PageRouteBuilder( - pageBuilder: (context, animation1, animation2) => TimerScreen( + pageBuilder: (context, animation1, animation2) => TimerScheduleScreen( + switchCode :"switch_1", device: device, deviceCode: 'countdown_1', ))); @@ -99,10 +100,11 @@ class ScheduleScreen extends StatelessWidget { Navigator.push( context, PageRouteBuilder( - pageBuilder: (context, animation1, animation2) => TimerScreen( - device: device, - deviceCode: 'countdown_2', - ))); + pageBuilder: (context, animation1, animation2) => TimerScheduleScreen( + switchCode :"switch_2", + device: device, + deviceCode: 'countdown_2', + ))); }, child: Container( padding: @@ -135,8 +137,9 @@ class ScheduleScreen extends StatelessWidget { Navigator.push( context, PageRouteBuilder( - pageBuilder: (context, animation1, animation2) => TimerScreen( - device: device, + pageBuilder: (context, animation1, animation2) => TimerScheduleScreen( + switchCode :"switch_3", + device: device, deviceCode: 'countdown_3', ))); }, diff --git a/lib/features/devices/view/widgets/three_gang/three_gang_screen.dart b/lib/features/devices/view/widgets/three_gang/three_gang_screen.dart index 3b42646..b9a8f0c 100644 --- a/lib/features/devices/view/widgets/three_gang/three_gang_screen.dart +++ b/lib/features/devices/view/widgets/three_gang/three_gang_screen.dart @@ -22,7 +22,9 @@ class ThreeGangScreen extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => ThreeGangBloc(threeGangId: device?.uuid ?? '') + create: (context) => ThreeGangBloc( + switchCode: '', + threeGangId: device?.uuid ?? '') ..add(InitialEvent(groupScreen: device != null ? false : true)), child: BlocBuilder( builder: (context, state) { diff --git a/lib/features/devices/view/widgets/three_gang/timer_screen.dart b/lib/features/devices/view/widgets/three_gang/timer_screen.dart index d5e1c8b..d15d4b1 100644 --- a/lib/features/devices/view/widgets/three_gang/timer_screen.dart +++ b/lib/features/devices/view/widgets/three_gang/timer_screen.dart @@ -7,7 +7,10 @@ import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_blo import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_event.dart'; import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_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_container.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'; @@ -15,10 +18,17 @@ 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 TimerScreen extends StatelessWidget { + + +class TimerScheduleScreen extends StatelessWidget { final DeviceModel device; final String deviceCode; - const TimerScreen({required this.device, required this.deviceCode, super.key}); + final String switchCode; + const TimerScheduleScreen( + {required this.device, + required this.deviceCode, + required this.switchCode, + super.key}); @override Widget build(BuildContext context) { @@ -27,150 +37,217 @@ class TimerScreen extends StatelessWidget { statusBarColor: ColorsManager.primaryColor.withOpacity(0.5), statusBarIconBrightness: Brightness.light, ), - child: Scaffold( - backgroundColor: ColorsManager.backgroundColor, - extendBodyBehindAppBar: true, - extendBody: true, - appBar: AppBar( - backgroundColor: Colors.transparent, - centerTitle: true, - title: const BodyLarge( - text: 'Schedule', - fontColor: ColorsManager.primaryColor, - fontWeight: FontsManager.bold, - ), - ), - body: SafeArea( - child: BlocProvider( - create: (context) => ThreeGangBloc(threeGangId: device.uuid ?? '') - ..add(GetCounterEvent(deviceCode: deviceCode)), - child: BlocBuilder( - builder: (context, state) { - Duration duration = Duration.zero; - int countNum = 0; - int tabNum = 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; - } - - if (state is ChangeSlidingSegmentState) { - tabNum = state.value; - } - return PopScope( - canPop: false, - onPopInvoked: (didPop) { - if (!didPop) { - BlocProvider.of(context).add(OnClose()); - Navigator.pop(context); - } - }, - child: Container( - width: MediaQuery.sizeOf(context).width, - height: MediaQuery.sizeOf(context).height, - padding: const EdgeInsets.all(24), - decoration: const BoxDecoration( - image: DecorationImage( - image: AssetImage( - Assets.assetsImagesBackground, - ), - fit: BoxFit.cover, - opacity: 0.4, - ), + child: BlocProvider( + create: (context) => + ThreeGangBloc(switchCode: switchCode, threeGangId: device.uuid ?? '') + ..add(GetCounterEvent(deviceCode: deviceCode)) + ..add(GetScheduleEvent()), + child: BlocBuilder( + builder: (context, state) { + final threeGangBloc = 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) { + threeGangBloc.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, ), - child: state is LoadingInitialState || state is LoadingNewSate - ? const Center( - child: DefaultContainer( - width: 50, height: 50, child: CircularProgressIndicator()), - ) - : Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - width: MediaQuery.sizeOf(context).width, - decoration: const ShapeDecoration( - color: ColorsManager.slidingBlueColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(20)), + actions: [ + threeGangBloc.createSchedule == true ? + TextButton( + onPressed: () { + threeGangBloc.add(ThreeGangSave()); + }, + child: const Text('Save') + ) : + threeGangBloc.selectedTabIndex==1? IconButton( + onPressed: () { + threeGangBloc.toggleCreateSchedule(); + }, + icon: const Icon(Icons.add), + ):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(threeGangBloc.createSchedule == true){ + threeGangBloc.toggleCreateSchedule(); + } + threeGangBloc.toggleSelectedIndex(0); + + }else{ + threeGangBloc.toggleSelectedIndex(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, ), ), - child: CupertinoSlidingSegmentedControl( - thumbColor: ColorsManager.slidingBlueColor, - onValueChanged: (value) { - BlocProvider.of(context) - .add(ChangeSlidingSegment(value: value ?? 0)); - }, - groupValue: - state is ChangeSlidingSegmentState ? state.value : 0, - backgroundColor: Colors.white, - children: { - 0: Container( - padding: const EdgeInsets.symmetric(vertical: 10), - child: BodySmall( - text: 'Countdown', - style: context.bodySmall.copyWith( - color: ColorsManager.blackColor, - fontSize: 12, - fontWeight: FontWeight.w400, - ), - ), - ), - 1: Container( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Text( - 'Schedule', - 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, + ), ), ), - if (tabNum == 0) - countNum > 0 - ? BodyLarge( - text: _formatDuration(countNum), - fontColor: ColorsManager.slidingBlueColor, - fontSize: 40, - ) - : CupertinoTimerPicker( - mode: CupertinoTimerPickerMode.hm, - onTimerDurationChanged: (Duration newDuration) { - duration = newDuration; - }, - ), - if (tabNum == 0) - GestureDetector( - onTap: () { - if (state is LoadingNewSate) { - return; - } - if (countNum > 0) { - BlocProvider.of(context).add( - SetCounterValue( + ), + ], + ), + + ), + Expanded( + child: TabBarView( + 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) { + threeGangBloc.add(SetCounterValue( deviceCode: deviceCode, duration: Duration.zero)); - } else if (duration != Duration.zero) { - BlocProvider.of(context).add( - SetCounterValue( - deviceCode: deviceCode, duration: duration)); - } + } else if (duration != Duration.zero) { + threeGangBloc.add(SetCounterValue( + deviceCode: deviceCode, + duration: duration)); + } + }, + child: SvgPicture.asset(countNum > 0 + ? Assets.pauseIcon + : Assets.playIcon)), + ], + ), + ), + ), + Column( + mainAxisAlignment:threeGangBloc.listSchedule.isNotEmpty? + MainAxisAlignment.start:MainAxisAlignment.center, + children: [ + SizedBox( + child: threeGangBloc.createSchedule == true ? + CreateSchedule( + onToggleChanged: (bool isOn) { + threeGangBloc.toggleSchedule = isOn; }, - child: SvgPicture.asset( - countNum > 0 ? Assets.pauseIcon : Assets.playIcon)) - ], - ))); - }, - ), - ), + onDateTimeChanged: (DateTime dateTime) { + threeGangBloc.selectedTime=dateTime; + }, + days: threeGangBloc.days, + selectDays: (List selectedDays) { + threeGangBloc.selectedDays = selectedDays; + }, + ) + : + Padding( + padding: const EdgeInsets.only(top: 10), + child: ScheduleListView( + listSchedule: threeGangBloc.listSchedule, // Pass the schedule list here + onDismissed: (scheduleId) { + threeGangBloc.listSchedule.removeWhere((schedule) => schedule.scheduleId == scheduleId); + threeGangBloc.add(DeleteScheduleEvent(id: scheduleId)); + }, + onToggleSchedule: (scheduleId, isEnabled) { + threeGangBloc.add(ToggleScheduleEvent( + id: scheduleId, + toggle: isEnabled, + )); + }, + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + )) + ); + }, ), ), ); diff --git a/lib/features/devices/view/widgets/two_gang/two_gang_Interface.dart b/lib/features/devices/view/widgets/two_gang/two_gang_Interface.dart new file mode 100644 index 0000000..029934f --- /dev/null +++ b/lib/features/devices/view/widgets/two_gang/two_gang_Interface.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart'; +import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_gang_screen.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/constants.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class TwoGangInterface extends StatelessWidget { + const TwoGangInterface({super.key, this.gangSwitch}); + + final DeviceModel? gangSwitch; + @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: gangSwitch != null + ? DeviceAppbar( + deviceName: gangSwitch!.name!, + deviceUuid: gangSwitch!.uuid!, + ) + : AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + title: BodyLarge( + text: gangSwitch?.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: TwoGangScreen(device: gangSwitch), + ), + ), + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/two_gang/two_gang_list.dart b/lib/features/devices/view/widgets/two_gang/two_gang_list.dart new file mode 100644 index 0000000..065dd62 --- /dev/null +++ b/lib/features/devices/view/widgets/two_gang/two_gang_list.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_event.dart'; +import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_state.dart'; +import 'package:syncrow_app/features/devices/model/groupTwoGangModel.dart'; +import 'package:syncrow_app/features/devices/model/group_three_gang_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 TwoGangList extends StatelessWidget { + const TwoGangList({super.key, required this.twoGangList, required this.allSwitches}); + + final List twoGangList; + 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: twoGangList.length, + itemBuilder: (context, index) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + BodySmall(text: '${twoGangList[index].deviceName} beside light'), + const SizedBox(height: 5), + DevicesDefaultSwitch( + switchValue: twoGangList[index].firstSwitch, + action: () { + BlocProvider.of(context).add(ChangeFirstSwitchStatusEvent( + value: twoGangList[index].firstSwitch, + deviceId: twoGangList[index].deviceId)); + }, + ), + const SizedBox(height: 10), + BodySmall(text: '${twoGangList[index].deviceName} ceiling light'), + const SizedBox(height: 5), + DevicesDefaultSwitch( + switchValue: twoGangList[index].secondSwitch, + action: () { + BlocProvider.of(context).add(ChangeSecondSwitchStatusEvent( + value: twoGangList[index].secondSwitch, + deviceId: twoGangList[index].deviceId)); + }, + ), + const SizedBox(height: 10), + BodySmall(text: '${twoGangList[index].deviceName} spotlight'), + const SizedBox(height: 5), + ], + ); + }, + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/devices/view/widgets/two_gang/two_gang_screen.dart b/lib/features/devices/view/widgets/two_gang/two_gang_screen.dart new file mode 100644 index 0000000..d332386 --- /dev/null +++ b/lib/features/devices/view/widgets/two_gang/two_gang_screen.dart @@ -0,0 +1,339 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_event.dart'; +import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/groupTwoGangModel.dart'; +import 'package:syncrow_app/features/devices/model/two_gang_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/three_gang/gang_switch.dart'; +import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_gang_list.dart'; +import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_schedule_screen.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.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 TwoGangScreen extends StatelessWidget { + const TwoGangScreen({super.key, this.device, this.switchCode}); + final DeviceModel? device; + final String? switchCode; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => TwoGangBloc( + switchCode: switchCode??'', + twoGangId: device?.uuid ?? '') + ..add(InitialEvent(groupScreen: device != null ? false : true)), + child: BlocBuilder( + builder: (context, state) { + TwoGangModel twoGangModel = TwoGangModel( + firstSwitch: false, + secondSwitch: false, + firstCountDown: 0, + secondCountDown: 0, + ); + + List groupTwoGangModel = []; + bool allSwitchesOn = false; + if (state is LoadingNewSate) { + twoGangModel = state.twoGangModel; + } else if (state is UpdateState) { + twoGangModel = state.twoGangModel; + } else if (state is UpdateGroupState) { + groupTwoGangModel = state.twoGangList; + allSwitchesOn = state.allSwitches; + } + return state is LoadingInitialState + ? const Center( + child: DefaultContainer(width: 50, height: 50, child: CircularProgressIndicator()),) + : device == null + ? TwoGangList( + twoGangList: groupTwoGangModel, + allSwitches: allSwitchesOn, + ) + : 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: twoGangModel.firstSwitch, + action: () { + BlocProvider.of(context).add( + ChangeFirstSwitchStatusEvent( + value: twoGangModel.firstSwitch)); + }, + ), + const SizedBox(height: 20), + const SizedBox( + width: 77, + child: BodySmall( + text: "Cove Light", + fontColor: ColorsManager.textPrimaryColor, + textAlign: TextAlign.center, + ), + ), + ], + ), + Column( + children: [ + GangSwitch( + threeGangSwitch: device!, + value: twoGangModel.secondSwitch, + action: () { + BlocProvider.of(context).add( + ChangeSecondSwitchStatusEvent( + value: twoGangModel.secondSwitch)); + }, + ), + const SizedBox(height: 20), + const SizedBox( + width: 77, + child: BodySmall( + text: "Chandelier", + fontColor: ColorsManager.textPrimaryColor, + textAlign: TextAlign.center, + ), + ), + ], + ), + + ], + ), + ), + Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Card( + elevation: 3, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100), + ), + child: GestureDetector( + onTap: () { + BlocProvider.of(context) + .add(AllOnEvent()); + }, + 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: BodySmall( + text: "On", + style: context.bodyMedium.copyWith( + color: ColorsManager + .primaryColorWithOpacity, + fontWeight: FontWeight.bold), + ), + ), + ), + ], + ), + ), + ), + const SizedBox(height: 10), + BodySmall( + text: "All On", + style: context.bodyMedium.copyWith( + color: ColorsManager.textPrimaryColor, + ), + ), + ], + ), + const SizedBox( + width: 20, + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Card( + elevation: 3, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100), + ), + child: GestureDetector( + onTap: () { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: + (context, animation1, animation2) => + TwoGangScheduleScreen( + device: device!, + ))); + }, + 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.access_time, + color: + ColorsManager.primaryColorWithOpacity, + size: 25, + ), + ), + ), + ], + ), + ), + ), + const SizedBox(height: 10), + BodySmall( + text: "Timer", + style: context.bodyMedium.copyWith( + color: ColorsManager.textPrimaryColor, + ), + ), + ], + ), + const SizedBox( + width: 20, + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Card( + elevation: 3, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100), + ), + child: GestureDetector( + onTap: () { + // DevicesCubit.getInstance().deviceControl( + // DeviceControlModel( + // deviceId: device.uuid, + // code: 'switch_1', + // value: false, + // ), + // device.uuid!, + // ); + // DevicesCubit.getInstance().deviceControl( + // DeviceControlModel( + // deviceId: device.uuid, + // code: 'switch_2', + // value: false, + // ), + // device.uuid!, + // ); + // DevicesCubit.getInstance().deviceControl( + // DeviceControlModel( + // deviceId: device.uuid, + // code: 'switch_3', + // value: false, + // ), + // device.uuid!, + // ); + BlocProvider.of(context) + .add(AllOffEvent()); + }, + 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: BodySmall( + text: "Off", + style: context.bodyMedium.copyWith( + color: ColorsManager + .primaryColorWithOpacity, + fontWeight: FontWeight.bold), + ), + ), + ), + ], + ), + ), + ), + const SizedBox(height: 10), + BodySmall( + text: "All Off", + style: context.bodyMedium.copyWith( + color: ColorsManager.textPrimaryColor, + ), + ), + ], + ), + ], + ), + ), + Expanded(child: SizedBox()) + ], + ), + ), + ], + ), + ); + }, + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/two_gang/two_schedule_screen.dart b/lib/features/devices/view/widgets/two_gang/two_schedule_screen.dart new file mode 100644 index 0000000..020272d --- /dev/null +++ b/lib/features/devices/view/widgets/two_gang/two_schedule_screen.dart @@ -0,0 +1,141 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_timer_screen.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/font_manager.dart'; + +class TwoGangScheduleScreen extends StatelessWidget { + final DeviceModel device; + const TwoGangScheduleScreen({required this.device, super.key}); + + @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: AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + title: const BodyLarge( + text: 'Schedule', + fontColor: ColorsManager.primaryColor, + fontWeight: FontsManager.bold, + ), + ), + body: SafeArea( + child: 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: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 48, + ), + DefaultContainer( + width: MediaQuery.sizeOf(context).width, + child: Column( + children: [ + InkWell( + onTap: () { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => TimerScheduleScreen( + device: device, + deviceCode: 'countdown_1', + switchCode:'switch_1' , + ))); + }, + child: Container( + padding: + const EdgeInsets.only(left: 25, right: 15, top: 20, bottom: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodySmall( + text: "Cove Light", + style: context.bodyMedium.copyWith( + color: ColorsManager.textPrimaryColor, + fontSize: 15, + fontWeight: FontWeight.w400, + ), + ), + const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 18, + ) + ], + ), + ), + ), + const Divider( + color: ColorsManager.dividerColor, + ), + InkWell( + onTap: () { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => TimerScheduleScreen( + device: device, + deviceCode: 'countdown_2', + switchCode:'switch_2' , + ))); + }, + child: Container( + padding: + const EdgeInsets.only(left: 25, right: 15, top: 20, bottom: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodySmall( + text: "Chandelier", + style: context.bodyMedium.copyWith( + color: ColorsManager.textPrimaryColor, + fontSize: 15, + fontWeight: FontWeight.w400, + ), + ), + const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 18, + ) + ], + ), + ), + ), + + ], + )), + ], + ), + ), + ), + )); + } +} diff --git a/lib/features/devices/view/widgets/two_gang/two_timer_screen.dart b/lib/features/devices/view/widgets/two_gang/two_timer_screen.dart new file mode 100644 index 0000000..a86ae8c --- /dev/null +++ b/lib/features/devices/view/widgets/two_gang/two_timer_screen.dart @@ -0,0 +1,259 @@ +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/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_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'; +import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_event.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) => + TwoGangBloc(switchCode: switchCode, twoGangId: device.uuid ?? '') + ..add(GetCounterEvent(deviceCode: deviceCode)) + ..add(GetScheduleEvent()), + child: BlocBuilder( + builder: (context, state) { + final twoGangBloc = 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) { + twoGangBloc.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: [ + twoGangBloc.createSchedule == true ? + TextButton( + onPressed: () { + twoGangBloc.add(TwoGangSave()); + }, + child: const Text('Save') + ) : + twoGangBloc.selectedTabIndex==1? IconButton( + onPressed: () { + twoGangBloc.toggleCreateSchedule(); + }, + icon: const Icon(Icons.add), + ):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) { + print(value); + if(value==0){ + if(twoGangBloc.createSchedule == true){ + twoGangBloc.toggleCreateSchedule(); + } + twoGangBloc.toggleSelectedIndex(0); + + }else{ + twoGangBloc.toggleSelectedIndex(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( + 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) { + twoGangBloc.add(SetCounterValue( + deviceCode: deviceCode, + duration: Duration.zero)); + } else if (duration != Duration.zero) { + twoGangBloc.add(SetCounterValue( + deviceCode: deviceCode, + duration: duration)); + } + }, + child: SvgPicture.asset(countNum > 0 + ? Assets.pauseIcon + : Assets.playIcon)), + ], + ), + ), + ), + Column( + mainAxisAlignment:twoGangBloc.listSchedule.isNotEmpty? + MainAxisAlignment.start:MainAxisAlignment.center, + children: [ + SizedBox( + child: twoGangBloc.createSchedule == true ? + CreateSchedule( + onToggleChanged: (bool isOn) { + twoGangBloc.toggleSchedule = isOn; + }, + onDateTimeChanged: (DateTime dateTime) { + twoGangBloc.selectedTime=dateTime; + }, + days: twoGangBloc.days, + selectDays: (List selectedDays) { + twoGangBloc.selectedDays = selectedDays; + }, + ) + : + Padding( + padding: const EdgeInsets.only(top: 10), + child: ScheduleListView( + listSchedule: twoGangBloc.listSchedule, // Pass the schedule list here + onDismissed: (scheduleId) { + twoGangBloc.listSchedule.removeWhere((schedule) => schedule.scheduleId == scheduleId); + twoGangBloc.add(DeleteScheduleEvent(id: scheduleId)); + }, + onToggleSchedule: (scheduleId, isEnabled) { + twoGangBloc.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/shared_widgets/create_schedule.dart b/lib/features/shared_widgets/create_schedule.dart new file mode 100644 index 0000000..c8b298a --- /dev/null +++ b/lib/features/shared_widgets/create_schedule.dart @@ -0,0 +1,139 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class CreateSchedule extends StatefulWidget { + final List> days; + final void Function(List selectedDays)? selectDays; // Callback for selected days + final Function(DateTime) onDateTimeChanged; + final void Function(bool isOn)? onToggleChanged; // Callback for toggle state + + const CreateSchedule({ + Key? key, + required this.days, + required this.selectDays, + required this.onDateTimeChanged, + this.onToggleChanged, // Optional callback + }) : super(key: key); + + @override + _CreateScheduleState createState() => _CreateScheduleState(); +} + +class _CreateScheduleState extends State { + List selectedDays = []; // List of selected days + bool isOn = true; // Boolean state for the ON/OFF toggle + + @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']); // Check if day is selected + return Padding( + padding: const EdgeInsets.all(8.0), + child: InkWell( + onTap: () { + setState(() { + if (isSelected) { + selectedDays.remove(day['day']); // Deselect day + } else { + selectedDays.add(day['day']!); // Select 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), + InkWell( + onTap: () { + setState(() { + isOn = !isOn; // Toggle the state + if (widget.onToggleChanged != null) { + widget.onToggleChanged!(isOn); // Pass the new toggle state + } + }); + }, + child: DefaultContainer( + height: 60, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + isOn ? 'ON' : 'OFF', // Change text based on state + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + CircleAvatar( + backgroundColor: isOn + ? ColorsManager.secondaryColor.withOpacity(0.6) + : Colors.red.withOpacity(0.6), // Change background color based on state + child: const Icon( + Icons.power_settings_new, + color: ColorsManager.onPrimaryColor, // Change icon color + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/shared_widgets/empty_schedule.dart b/lib/features/shared_widgets/empty_schedule.dart new file mode 100644 index 0000000..42a311a --- /dev/null +++ b/lib/features/shared_widgets/empty_schedule.dart @@ -0,0 +1,51 @@ + + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; + +class EmptySchedule extends StatelessWidget { + const EmptySchedule({super.key}); + @override + Widget build(BuildContext context) { + return SizedBox( + height: 180, + width: 180, + child: Center(child: Column( + children: [ + Center( + child: SvgPicture.asset( + height: 100, + width: 100, + Assets.emptySchedule), + ), + Column( + children: [ + Center( + child: Text( + 'Please add', + style: context.displaySmall + .copyWith( + color: const Color( + 0XFFD5D5D5), + ), + ), + ), + Center( + child: Text( + 'a new schedule', + style: context.displaySmall + .copyWith( + color: const Color( + 0XFFD5D5D5), + ), + ), + ), + ], + ), + ], + ),), + ); + } +} diff --git a/lib/features/shared_widgets/schedule_list.dart b/lib/features/shared_widgets/schedule_list.dart new file mode 100644 index 0000000..0b48f86 --- /dev/null +++ b/lib/features/shared_widgets/schedule_list.dart @@ -0,0 +1,120 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/model/schedule_model.dart'; +import 'package:syncrow_app/features/shared_widgets/default_button.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_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'empty_schedule.dart'; // for SVG icons + + +class ScheduleListView extends StatelessWidget { + final List listSchedule; + final Function(String) onDismissed; + final Function(String, bool) onToggleSchedule; + const ScheduleListView({ + Key? key, + required this.listSchedule, + required this.onDismissed, + required this.onToggleSchedule, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Center( + child: listSchedule.isNotEmpty + ? SizedBox( + child: ListView.builder( + shrinkWrap: true, + itemCount: listSchedule.length, + itemBuilder: (context, index) { + return Dismissible( + key: Key(listSchedule[index].scheduleId), + background: Container( + padding: const EdgeInsets.only(right: 10), + alignment: AlignmentDirectional.centerEnd, + decoration: const ShapeDecoration( + color: Color(0xFFFF0000), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + ), + child: Padding( + padding: const EdgeInsets.only(bottom: 10, right: 10), + child: SvgPicture.asset( + Assets.assetsDeleteIcon, + width: 20, + height: 22, + ), + ), + ), + direction: DismissDirection.endToStart, + onDismissed: (direction) { + + onDismissed(listSchedule[index].scheduleId); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Schedule removed')), + ); + }, + child: InkWell( + onTap: () { + + }, + child: DefaultContainer( + padding: const EdgeInsets.all(20), + height: MediaQuery.of(context).size.height / 6.4, + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + BodyLarge( + text: listSchedule[index].time, + fontWeight: FontWeight.w500, + fontColor: Colors.black, + fontSize: 22, + ), + Text(listSchedule[index].days.join(' ')), + Text('Function ${listSchedule[index].function.value ? "ON" : "OFF"}'), + ], + ), + ), + Expanded( + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: const BodyMedium( + text: '', + fontWeight: FontWeight.normal, + ), + trailing: Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: listSchedule[index].enable, + onChanged: (value) { + onToggleSchedule( + listSchedule[index].scheduleId, + value, + ); + }, + applyTheme: true, + ), + ), + ), + ), + ], + ), + ), + ), + ); + }, + ), + ) + : const EmptySchedule() + ); + } +} diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart index a077576..5370a48 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -1034,4 +1034,11 @@ class Assets { static const String assetsPresenceState = "assets/icons/functions_icons/automation_functions/presence_state.svg"; + //assets/icons/functions_icons/automation_functions/presence_state.svg + + static const String oneGang = "assets/icons/1gang.svg"; + + static const String twoGang = "assets/icons/2gang.svg"; + + static const String emptySchedule = "assets/icons/emptySchedule.svg"; } diff --git a/lib/services/api/api_links_endpoints.dart b/lib/services/api/api_links_endpoints.dart index f8867fc..1c3e20f 100644 --- a/lib/services/api/api_links_endpoints.dart +++ b/lib/services/api/api_links_endpoints.dart @@ -175,4 +175,12 @@ abstract class ApiEndpoints { //multiple-time offline static const String deleteTemporaryPassword = '/door-lock/temporary-password/online/{doorLockUuid}/{passwordId}'; + + + static const String saveSchedule = '/schedule/{deviceUuid}'; + static const String getSchedule = '/schedule/{deviceUuid}?category={category}'; + static const String changeSchedule = '/schedule/enable/{deviceUuid}'; + static const String deleteSchedule = '/schedule/{deviceUuid}/{scheduleId}'; + + } diff --git a/lib/services/api/devices_api.dart b/lib/services/api/devices_api.dart index f347cdd..ca12999 100644 --- a/lib/services/api/devices_api.dart +++ b/lib/services/api/devices_api.dart @@ -1,11 +1,11 @@ import 'dart:async'; +import 'dart:convert'; import 'package:syncrow_app/features/devices/model/device_category_model.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/function_model.dart'; import 'package:syncrow_app/services/api/api_links_endpoints.dart'; import 'package:syncrow_app/services/api/http_service.dart'; - import '../../features/devices/model/create_temporary_password_model.dart'; class DevicesAPI { @@ -270,4 +270,87 @@ class DevicesAPI { ); return response; } + + static Future> postSchedule({ + required String category, + required String deviceId, + required String time, + required String code, + required bool value, + required List days, + + }) async { + print( { + "category": category, + "time": time, + "function":{ + "code": code, + "value":value, + }, + "days": days + }); + final response = await _httpService.post( + path: ApiEndpoints.saveSchedule.replaceAll('{deviceUuid}', deviceId), + showServerMessage: false, + body: + jsonEncode({ + "category": category, + "time": time, + "function":{ + "code": code, + "value":value, + }, + "days": days + },), + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } + + static Future getSchedule({ + required String category, + required String deviceId, + }) async { + final response = await _httpService.get( + path: ApiEndpoints.getSchedule.replaceAll('{deviceUuid}', deviceId).replaceAll('{category}', category), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } + + static Future changeSchedule({ + required String deviceUuid, + required String scheduleId, + required bool enable, + }) async { + final response = await _httpService.put( + path: ApiEndpoints.changeSchedule.replaceAll('{deviceUuid}', deviceUuid), + body: { + "scheduleId": scheduleId, + "enable": enable + }, + expectedResponseModel: (json) { + return json['success']; + }, + ); + return response; + } + + static Future deleteSchedule({ + required String deviceUuid, + required String scheduleId, + }) async { + final response = await _httpService.delete( + path: ApiEndpoints.deleteSchedule.replaceAll('{deviceUuid}', deviceUuid).replaceAll('{scheduleId}', scheduleId), + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } } diff --git a/lib/utils/resource_manager/constants.dart b/lib/utils/resource_manager/constants.dart index 774f726..a417454 100644 --- a/lib/utils/resource_manager/constants.dart +++ b/lib/utils/resource_manager/constants.dart @@ -43,6 +43,8 @@ enum DeviceType { DoorLock, Curtain, Blind, + OneGang, + TwoGang, ThreeGang, Gateway, CeilingSensor, @@ -68,6 +70,8 @@ Map devicesTypesMap = { "DL": DeviceType.DoorLock, "WPS": DeviceType.WallSensor, "3G": DeviceType.ThreeGang, + "2G": DeviceType.TwoGang, + "1G": DeviceType.OneGang, "CUR": DeviceType.Curtain, }; Map> devicesFunctionsMap = { @@ -155,6 +159,33 @@ Map> devicesFunctionsMap = { FunctionModel( code: 'indicator', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})), ], + DeviceType.OneGang:[ + 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})), + ], + DeviceType.TwoGang:[ + FunctionModel( + code: 'switch_1', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({}) + ), + FunctionModel( + code: 'switch_2', 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: 'countdown_2', + type: functionTypesMap['Integer'], + values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1}) + ), + ], + DeviceType.ThreeGang: [ FunctionModel( code: 'switch_1', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),