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/two_gang_bloc/two_gang_bloc.dart b/lib/features/devices/bloc/two_gang_bloc/two_gang_bloc.dart index 09829c7..6a1b3f8 100644 --- a/lib/features/devices/bloc/two_gang_bloc/two_gang_bloc.dart +++ b/lib/features/devices/bloc/two_gang_bloc/two_gang_bloc.dart @@ -7,6 +7,7 @@ import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_state.d 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'; @@ -14,11 +15,11 @@ import 'package:syncrow_app/services/api/devices_api.dart'; class TwoGangBloc extends Bloc { final String twoGangId; TwoGangModel deviceStatus = TwoGangModel( - firstSwitch: false, - secondSwitch: false, - firstCountDown: 0, - secondCountDown: 0, - ); + firstSwitch: false, + secondSwitch: false, + firstCountDown: 0, + secondCountDown: 0, + ); Timer? _timer; // Timer? _firstSwitchTimer; // Timer? _secondSwitchTimer; @@ -28,6 +29,20 @@ class TwoGangBloc extends Bloc { List devicesList = []; List groupTwoGangList = []; bool allSwitchesOn = true; + List selectedDays = []; + + bool createSchedule = false; + List listSchedule = [ + ScheduleModel( + id: '1', + category: 'category', + enable: true, + function: FunctionModel(code: 'code', value: true), + time: 'time', + timerId: 'timerId', + timezoneId: 'timezoneId', + days: ['S']) + ]; TwoGangBloc({required this.twoGangId}) : super(InitialState()) { on(_fetchTwoGangStatus); @@ -43,9 +58,20 @@ class TwoGangBloc extends Bloc { on(_onClose); on(_groupAllOn); on(_groupAllOff); + on(toggleDaySelection); } - void _fetchTwoGangStatus(InitialEvent event, Emitter emit) async { + DateTime? selectedTime; + + void toggleCreateSchedule() { + emit(LoadingInitialState()); + createSchedule = !createSchedule; + emit(UpdateCreateScheduleState(createSchedule)); + emit(ChangeSlidingSegmentState(value: 1)); + } + + void _fetchTwoGangStatus( + InitialEvent event, Emitter emit) async { emit(LoadingInitialState()); try { twoGangGroup = event.groupScreen; @@ -57,7 +83,8 @@ class TwoGangBloc extends Bloc { HomeCubit.getInstance().selectedSpace?.id ?? '', '2G'); for (int i = 0; i < devicesList.length; i++) { - var response = await DevicesAPI.getDeviceStatus(devicesList[i].uuid ?? ''); + var response = + await DevicesAPI.getDeviceStatus(devicesList[i].uuid ?? ''); List statusModelList = []; for (var status in response['status']) { statusModelList.add(StatusModel.fromJson(status)); @@ -65,23 +92,23 @@ class TwoGangBloc extends Bloc { deviceStatus = TwoGangModel.fromJson(statusModelList); groupTwoGangList.add(GroupTwoGangModel( - deviceId: devicesList[i].uuid ?? '', - deviceName: devicesList[i].name ?? '', - firstSwitch: deviceStatus.firstSwitch, - secondSwitch: deviceStatus.secondSwitch, - ) - ); + 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 ) { + if (!element.firstSwitch || !element.secondSwitch) { allSwitchesOn = false; } return true; }); } - emit(UpdateGroupState(twoGangList: groupTwoGangList, allSwitches: allSwitchesOn)); + emit(UpdateGroupState( + twoGangList: groupTwoGangList, allSwitches: allSwitchesOn)); } else { var response = await DevicesAPI.getDeviceStatus(twoGangId); List statusModelList = []; @@ -100,18 +127,21 @@ class TwoGangBloc extends Bloc { _listenToChanges() { try { - DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$twoGangId'); + 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; + Map usersMap = + event.snapshot.value as Map; List statusList = []; usersMap['status'].forEach((element) { - statusList.add(StatusModel(code: element['code'], value: element['value'])); + statusList + .add(StatusModel(code: element['code'], value: element['value'])); }); deviceStatus = TwoGangModel.fromJson(statusList); @@ -126,7 +156,8 @@ class TwoGangBloc extends Bloc { emit(UpdateState(twoGangModel: deviceStatus)); } - void _changeFirstSwitch(ChangeFirstSwitchStatusEvent event, Emitter emit) async { + void _changeFirstSwitch( + ChangeFirstSwitchStatusEvent event, Emitter emit) async { emit(LoadingNewSate(twoGangModel: deviceStatus)); try { deviceStatus.firstSwitch = !event.value; @@ -189,13 +220,16 @@ class TwoGangBloc extends Bloc { final response = await Future.wait([ DevicesAPI.controlDevice( DeviceControlModel( - deviceId: twoGangId, code: 'switch_1', value: deviceStatus.firstSwitch), + deviceId: twoGangId, + code: 'switch_1', + value: deviceStatus.firstSwitch), twoGangId), DevicesAPI.controlDevice( DeviceControlModel( - deviceId: twoGangId, code: 'switch_2', value: deviceStatus.secondSwitch), + deviceId: twoGangId, + code: 'switch_2', + value: deviceStatus.secondSwitch), twoGangId), - ]); if (response.every((element) => !element['success'])) { @@ -216,16 +250,17 @@ class TwoGangBloc extends Bloc { emit(UpdateState(twoGangModel: deviceStatus)); final response = await Future.wait([ DevicesAPI.controlDevice( - DeviceControlModel( - deviceId: twoGangId, code: 'switch_1', - value: deviceStatus.firstSwitch), + DeviceControlModel( + deviceId: twoGangId, + code: 'switch_1', + value: deviceStatus.firstSwitch), twoGangId), DevicesAPI.controlDevice( - DeviceControlModel( - deviceId: twoGangId, - code: 'switch_2', - value: deviceStatus.secondSwitch), - twoGangId), + DeviceControlModel( + deviceId: twoGangId, + code: 'switch_2', + value: deviceStatus.secondSwitch), + twoGangId), ]); if (response.every((element) => !element['success'])) { await Future.delayed(const Duration(milliseconds: 500)); @@ -243,7 +278,6 @@ class TwoGangBloc extends Bloc { for (int i = 0; i < groupTwoGangList.length; i++) { groupTwoGangList[i].firstSwitch = true; groupTwoGangList[i].secondSwitch = true; - } emit(UpdateGroupState(twoGangList: groupTwoGangList, allSwitches: true)); @@ -251,11 +285,15 @@ class TwoGangBloc extends Bloc { final response = await Future.wait([ DevicesAPI.controlDevice( DeviceControlModel( - deviceId: groupTwoGangList[i].deviceId, code: 'switch_1', value: true), + deviceId: groupTwoGangList[i].deviceId, + code: 'switch_1', + value: true), groupTwoGangList[i].deviceId), DevicesAPI.controlDevice( DeviceControlModel( - deviceId: groupTwoGangList[i].deviceId, code: 'switch_2', value: true), + deviceId: groupTwoGangList[i].deviceId, + code: 'switch_2', + value: true), groupTwoGangList[i].deviceId), ]); @@ -284,13 +322,16 @@ class TwoGangBloc extends Bloc { final response = await Future.wait([ DevicesAPI.controlDevice( DeviceControlModel( - deviceId: groupTwoGangList[i].deviceId, code: 'switch_1', value: false), + deviceId: groupTwoGangList[i].deviceId, + code: 'switch_1', + value: false), groupTwoGangList[i].deviceId), DevicesAPI.controlDevice( DeviceControlModel( - deviceId: groupTwoGangList[i].deviceId, code: 'switch_2', value: false), + deviceId: groupTwoGangList[i].deviceId, + code: 'switch_2', + value: false), groupTwoGangList[i].deviceId), - ]); if (response.every((element) => !element['success'])) { @@ -305,17 +346,20 @@ class TwoGangBloc extends Bloc { } } - void _changeSliding(ChangeSlidingSegment event, Emitter emit) async { + void _changeSliding( + ChangeSlidingSegment event, Emitter emit) async { emit(ChangeSlidingSegmentState(value: event.value)); } - void _setCounterValue(SetCounterValue event, Emitter emit) async { + void _setCounterValue( + SetCounterValue event, Emitter emit) async { emit(LoadingNewSate(twoGangModel: deviceStatus)); int seconds = 0; try { seconds = event.duration.inSeconds; final response = await DevicesAPI.controlDevice( - DeviceControlModel(deviceId: twoGangId, code: event.deviceCode, value: seconds), + DeviceControlModel( + deviceId: twoGangId, code: event.deviceCode, value: seconds), twoGangId); if (response['success'] ?? false) { @@ -340,7 +384,8 @@ class TwoGangBloc extends Bloc { } } - void _getCounterValue(GetCounterEvent event, Emitter emit) async { + void _getCounterValue( + GetCounterEvent event, Emitter emit) async { emit(LoadingInitialState()); try { var response = await DevicesAPI.getDeviceStatus(twoGangId); @@ -387,4 +432,38 @@ class TwoGangBloc 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)); + } + + saveSchedule() async { + final response = await DevicesAPI.postSchedule( + category: 'switch_1', + deviceId: twoGangId, + time: 'selectedTime', + code: 'switch_1', + value: true, + days: selectedDays); + } } 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 index 6be514c..9076a03 100644 --- a/lib/features/devices/bloc/two_gang_bloc/two_gang_event.dart +++ b/lib/features/devices/bloc/two_gang_bloc/two_gang_event.dart @@ -10,6 +10,14 @@ abstract class TwoGangEvent extends Equatable { class LoadingEvent extends TwoGangEvent {} class TwoGangUpdated 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; @@ -43,6 +51,13 @@ 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}); 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 index c967a36..43d7c69 100644 --- a/lib/features/devices/bloc/two_gang_bloc/two_gang_state.dart +++ b/lib/features/devices/bloc/two_gang_bloc/two_gang_state.dart @@ -11,6 +11,7 @@ class TwoGangState extends Equatable { } class InitialState extends TwoGangState {} +class ChangeTimeState extends TwoGangState {} class LoadingInitialState extends TwoGangState {} @@ -76,3 +77,11 @@ class TimerRunInProgress extends TwoGangState { } class TimerRunComplete extends TwoGangState {} + + + +// two_gang_state.dart +class UpdateCreateScheduleState extends TwoGangState { + final bool createSchedule; + UpdateCreateScheduleState(this.createSchedule); +} diff --git a/lib/features/devices/model/schedule_model.dart b/lib/features/devices/model/schedule_model.dart new file mode 100644 index 0000000..220fd8c --- /dev/null +++ b/lib/features/devices/model/schedule_model.dart @@ -0,0 +1,74 @@ +class FunctionModel { + final String code; + final bool value; + + FunctionModel({ + required this.code, + required this.value, + }); + + // Convert a JSON object to a FunctionModel instance + factory FunctionModel.fromJson(Map json) { + return FunctionModel( + code: json['code'], + value: json['value'], + ); + } + + // Convert a FunctionModel instance to a JSON object + Map toJson() { + return { + 'code': code, + 'value': value, + }; + } +} + +class ScheduleModel { + final String id; + final String category; + final bool enable; + final FunctionModel function; + final String time; + final String timerId; + final String timezoneId; + final List days; + + ScheduleModel({ + required this.id, + required this.category, + required this.enable, + required this.function, + required this.time, + required this.timerId, + required this.timezoneId, + required this.days, + }); + + // Convert a JSON object to a Schedule instance + factory ScheduleModel.fromJson(Map json) { + return ScheduleModel( + id: json['id'], + category: json['category'], + enable: json['enable'], + function: FunctionModel.fromJson(json['function']), + time: json['time'], + timerId: json['timerId'], + timezoneId: json['timezoneId'], + days: List.from(json['days']), + ); + } + + // Convert a Schedule instance to a JSON object + Map toJson() { + return { + 'category': category, + 'enable': enable, + 'function': function.toJson(), + 'time': time, + 'timerId': timerId, + 'timezoneId': timezoneId, + 'days': days, + }; + } +} \ No newline at end of file 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 index 72c5f80..0f61c40 100644 --- a/lib/features/devices/view/widgets/two_gang/two_timer_screen.dart +++ b/lib/features/devices/view/widgets/two_gang/two_timer_screen.dart @@ -6,8 +6,11 @@ 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_container.dart'; +import 'package:syncrow_app/features/shared_widgets/empty_schedule.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/utils/context_extension.dart'; @@ -15,11 +18,11 @@ 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 TwoTimerScreen extends StatelessWidget { final DeviceModel device; final String deviceCode; - const TwoTimerScreen({required this.device, required this.deviceCode, super.key}); + const TwoTimerScreen( + {required this.device, required this.deviceCode, super.key}); @override Widget build(BuildContext context) { @@ -28,151 +31,248 @@ class TwoTimerScreen 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) => TwoGangBloc(twoGangId: 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; - } + child: BlocProvider( + create: (context) => TwoGangBloc(twoGangId: device.uuid ?? '') + ..add(GetCounterEvent(deviceCode: deviceCode)), + child: BlocBuilder( + builder: (context, state) { + final twoGangBloc = BlocProvider.of(context); - 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, + 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; + } else if (state is ChangeSlidingSegmentState) { + tabNum = state.value; + } + return 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, + ), + actions: [ + if (tabNum == 1) + twoGangBloc.createSchedule == true + ? TextButton(onPressed: () {}, child: const Text('Save')) + : IconButton( + onPressed: () { + twoGangBloc.toggleCreateSchedule(); + }, + icon: const Icon(Icons.add), ), - ), - 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)), + ], + ), + body: SafeArea( + child: 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: 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)), + ), + ), + 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, + ), ), ), - 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, ), - 1: 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( - deviceCode: deviceCode, - duration: Duration.zero)); - } else if (duration != Duration.zero) { - BlocProvider.of(context).add( - SetCounterValue( - deviceCode: deviceCode, duration: duration)); - } + }, + ), + ), + if (tabNum == 0) + countNum > 0 + ? BodyLarge( + text: _formatDuration(countNum), + fontColor: + ColorsManager.slidingBlueColor, + fontSize: 40, + ) + : CupertinoTimerPicker( + mode: CupertinoTimerPickerMode.hm, + onTimerDurationChanged: + (Duration newDuration) { + duration = newDuration; }, - child: SvgPicture.asset( - countNum > 0 ? Assets.pauseIcon : Assets.playIcon) - ) - ], - ))); - }, - ), - ), + ), + if (tabNum == 0) + GestureDetector( + onTap: () { + if (state is LoadingNewSate) { + return; + } + if (countNum > 0) { + BlocProvider.of(context) + .add(SetCounterValue( + deviceCode: deviceCode, + duration: Duration.zero)); + } else if (duration != Duration.zero) { + BlocProvider.of(context) + .add(SetCounterValue( + deviceCode: deviceCode, + duration: duration)); + } + }, + child: SvgPicture.asset(countNum > 0 + ? Assets.pauseIcon + : Assets.playIcon)), + if (tabNum == 1) + SizedBox( + height: + MediaQuery.of(context).size.height / 2, + child: twoGangBloc.createSchedule == true + ? CreateSchedule( + onToggleChanged: (bool isOn) { + print( + 'Toggle is now: ${isOn ? "ON" : "OFF"}'); + }, + onDateTimeChanged: + (DateTime dateTime) { + print('Selected Time: $dateTime'); + }, + days: twoGangBloc.days, + selectDays: + (List selectedDays) { + twoGangBloc.selectedDays = + selectedDays; + print( + 'Selected Days: ${twoGangBloc.selectedDays}'); + }, + ) + : Center( + child: twoGangBloc.listSchedule.isNotEmpty? + Container( + height: MediaQuery.of(context).size.height / 2, + child: ListView.builder( + itemCount: twoGangBloc.listSchedule.length, + itemBuilder: (context, index) { + return + + Dismissible( + key: Key( twoGangBloc.listSchedule[index].id), + child: InkWell( + onTap: () {}, + child: + DefaultContainer( + padding: EdgeInsets.all(20), + height: MediaQuery.of(context).size.height / 8, + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(twoGangBloc.listSchedule[index].time), + Text(twoGangBloc.listSchedule[index].days.toString()), + Text(twoGangBloc.listSchedule[index].enable.toString()) + ], + ), + ), + Expanded(child:ListTile( + contentPadding: EdgeInsets.zero, + leading: const BodyMedium( + text: '', + fontWeight: FontWeight.normal, + ), + trailing: Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: true, + onChanged: (value) { + // smartDoorBloc.add(ToggleRepeatEvent()); + }, + applyTheme: true, + )), + ),) + ], + )))); + },), + ): + EmptySchedule()), + ) + ], + ), + ), + ), + ), + ); + }, ), ), ); diff --git a/lib/features/shared_widgets/create_schedule.dart b/lib/features/shared_widgets/create_schedule.dart new file mode 100644 index 0000000..9148114 --- /dev/null +++ b/lib/features/shared_widgets/create_schedule.dart @@ -0,0 +1,140 @@ +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 = false; // 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: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: isOn ? Colors.green : Colors.red, // Change color based on state + ), + ), + CircleAvatar( + backgroundColor: isOn + ? ColorsManager.secondaryColor.withOpacity(0.6) + : Colors.red.withOpacity(0.6), // Change background color based on state + child: Icon( + Icons.power_settings_new, + color: isOn ? Colors.green : Colors.red, // 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..fa56b38 --- /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 Center(child: Column( + mainAxisAlignment: + MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.center, + 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/generated/assets.dart b/lib/generated/assets.dart index 80d9bf8..509443d 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -1035,4 +1035,6 @@ class Assets { 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/devices_api.dart b/lib/services/api/devices_api.dart index f347cdd..2e32b4b 100644 --- a/lib/services/api/devices_api.dart +++ b/lib/services/api/devices_api.dart @@ -270,4 +270,33 @@ 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 { + final response = await _httpService.post( + path: ApiEndpoints.deleteTemporaryPassword.replaceAll('{doorLockUuid}', deviceId), + showServerMessage: false, + body: + { + "category": category, + "time": time, + "function":{ + "code": code, + "value":value, + }, + "days": days + }, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } }