From f99744338c02a7b5986ae75b7099c7650450a3f9 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 22 Sep 2024 14:17:52 +0300 Subject: [PATCH] connecting apis and changes the table design to match figma --- assets/icons/empty_records.svg | 15 + .../water_heater/bloc/water_heater_bloc.dart | 141 ++++- .../water_heater/bloc/water_heater_event.dart | 60 +- .../water_heater/bloc/water_heater_state.dart | 38 +- .../water_heater/models/schedule_entry.dart | 19 + .../water_heater/models/send_schedule.dart | 93 +++ .../water_heater/widgets/schedual_view.dart | 528 +++++++++--------- lib/services/devices_mang_api.dart | 60 +- lib/utils/constants/api_const.dart | 6 + lib/utils/constants/assets.dart | 3 + lib/utils/format_date_time.dart | 15 + 11 files changed, 674 insertions(+), 304 deletions(-) create mode 100644 assets/icons/empty_records.svg create mode 100644 lib/pages/device_managment/water_heater/models/schedule_entry.dart create mode 100644 lib/pages/device_managment/water_heater/models/send_schedule.dart diff --git a/assets/icons/empty_records.svg b/assets/icons/empty_records.svg new file mode 100644 index 00000000..662a3e47 --- /dev/null +++ b/assets/icons/empty_records.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart index fcf52732..07f73569 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart @@ -5,8 +5,11 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/send_schedule.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; +import 'package:syncrow_web/utils/format_date_time.dart'; part 'water_heater_event.dart'; part 'water_heater_state.dart'; @@ -21,11 +24,63 @@ class WaterHeaterBloc extends Bloc { on(_onAddSchedule); on(_onDeleteSchedule); on(_onUpdateSchedule); + on(_initializeAddSchedule); + on(_updateSelectedTime); + on(_updateSelectedDay); + on(_updateFunctionOn); } late WaterHeaterStatusModel deviceStatus; Timer? _countdownTimer; + FutureOr _initializeAddSchedule( + InitializeAddScheduleEvent event, + Emitter emit, + ) { + final currentState = state as WaterHeaterDeviceStatusLoaded; + if (event.isEditing) { + emit(currentState.copyWith( + selectedTime: event.selectedTime, + selectedDays: event.selectedDays ?? List.filled(7, false), + functionOn: event.functionOn ?? false, + isEditing: event.isEditing, + )); + } else { + emit(currentState.copyWith( + selectedTime: null, + selectedDays: List.filled(7, false), + functionOn: false, + isEditing: false, + )); + } + } + + FutureOr _updateSelectedTime( + UpdateSelectedTimeEvent event, + Emitter emit, + ) { + final currentState = state as WaterHeaterDeviceStatusLoaded; + emit(currentState.copyWith(selectedTime: event.selectedTime)); + } + + FutureOr _updateSelectedDay( + UpdateSelectedDayEvent event, + Emitter emit, + ) { + final currentState = state as WaterHeaterDeviceStatusLoaded; + final updatedDays = List.from(currentState.selectedDays); + updatedDays[event.index] = event.value; + emit(currentState.copyWith(selectedDays: updatedDays)); + } + + FutureOr _updateFunctionOn( + UpdateFunctionOnEvent event, + Emitter emit, + ) { + final currentState = state as WaterHeaterDeviceStatusLoaded; + emit(currentState.copyWith(functionOn: event.isOn)); + } + FutureOr _updateScheduleEvent( UpdateScheduleEvent event, Emitter emit, @@ -301,21 +356,50 @@ class WaterHeaterBloc extends Bloc { FutureOr _onAddSchedule( AddScheduleEvent event, Emitter emit, - ) { + ) async { if (state is WaterHeaterDeviceStatusLoaded) { final currentState = state as WaterHeaterDeviceStatusLoaded; - final newSchedule = ScheduleEntry( - selectedDays: event.selectedDays, - time: event.time, - functionOn: event.functionOn, - ); - final updatedSchedules = List.from(currentState.schedules) - ..add(newSchedule); - emit(currentState.copyWith(schedules: updatedSchedules)); + SendSchedule sendSchedule = SendSchedule( + category: event.category, + time: formatTimeOfDayToISO(event.time), + function: Status(code: 'switch_1', value: event.functionOn), + days: _getSelectedDaysString(event.selectedDays), + ); + + bool success = await DevicesManagementApi() + .addScheduleRecord(sendSchedule, currentState.status.uuid); + + if (success) { + final newSchedule = ScheduleEntry( + selectedDays: event.selectedDays, + time: event.time, + functionOn: event.functionOn, + category: event.category, + ); + + final updatedSchedules = + List.from(currentState.schedules)..add(newSchedule); + + emit(currentState.copyWith(schedules: updatedSchedules)); + } else { + emit(const WaterHeaterFailedState( + error: 'Failed to add schedule. Please try again.')); + } } } + List _getSelectedDaysString(List selectedDays) { + final days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + List selectedDaysStr = []; + for (int i = 0; i < selectedDays.length; i++) { + if (selectedDays[i]) { + selectedDaysStr.add(days[i]); + } + } + return selectedDaysStr; + } + FutureOr _onDeleteSchedule( DeleteScheduleEvent event, Emitter emit, @@ -332,17 +416,42 @@ class WaterHeaterBloc extends Bloc { FutureOr _onUpdateSchedule( UpdateScheduleEntryEvent event, Emitter emit, - ) { + ) async { if (state is WaterHeaterDeviceStatusLoaded) { final currentState = state as WaterHeaterDeviceStatusLoaded; - final updatedSchedules = List.from(currentState.schedules); - updatedSchedules[event.index] = ScheduleEntry( - selectedDays: event.selectedDays, - time: event.time, - functionOn: event.functionOn, + + // Get the current schedule ID or UUID (assuming it's stored in the schedules) + String scheduleId = + ""; // Retrieve the actual schedule ID based on your model + + // Call the API to update the schedule + bool success = await DevicesManagementApi().updateScheduleRecord( + enable: event.functionOn, + uuid: event.deviceId, + scheduleId: scheduleId, ); - emit(currentState.copyWith(schedules: updatedSchedules)); + if (success) { + final updatedSchedules = + List.from(currentState.schedules); + + final updatedScheduleIndex = updatedSchedules.indexWhere((schedule) { + return schedule.category == event.category; + }); + + if (updatedScheduleIndex != -1) { + updatedSchedules[updatedScheduleIndex] = ScheduleEntry( + category: event.category, + selectedDays: updatedSchedules[updatedScheduleIndex].selectedDays, + time: updatedSchedules[updatedScheduleIndex].time, + functionOn: event.functionOn, + ); + emit(currentState.copyWith(schedules: updatedSchedules)); + } + } else { + emit(const WaterHeaterFailedState( + error: 'Failed to update schedule. Please try again.')); + } } } diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart index 360f4a4f..0c9cd794 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart @@ -67,15 +67,17 @@ final class AddScheduleEvent extends WaterHeaterEvent { final List selectedDays; final TimeOfDay time; final bool functionOn; + final String category; const AddScheduleEvent({ required this.selectedDays, required this.time, required this.functionOn, + required this.category, }); @override - List get props => [selectedDays, time, functionOn]; + List get props => [selectedDays, time, functionOn, category]; } final class DeleteScheduleEvent extends WaterHeaterEvent { @@ -88,18 +90,60 @@ final class DeleteScheduleEvent extends WaterHeaterEvent { } final class UpdateScheduleEntryEvent extends WaterHeaterEvent { - final int index; - final List selectedDays; - final TimeOfDay time; final bool functionOn; + final String category; + final String deviceId; const UpdateScheduleEntryEvent({ - required this.index, - required this.selectedDays, - required this.time, required this.functionOn, + required this.category, + required this.deviceId, }); @override - List get props => [index, selectedDays, time, functionOn]; + List get props => [category, functionOn, deviceId]; +} + +class InitializeAddScheduleEvent extends WaterHeaterEvent { + final TimeOfDay? selectedTime; + final List? selectedDays; + final bool? functionOn; + final bool isEditing; + final int? index; + + const InitializeAddScheduleEvent({ + this.selectedTime, + this.selectedDays, + this.functionOn, + this.isEditing = false, + this.index, + }); +} + +class UpdateSelectedTimeEvent extends WaterHeaterEvent { + final TimeOfDay selectedTime; + + const UpdateSelectedTimeEvent(this.selectedTime); + + @override + List get props => [selectedTime]; +} + +class UpdateSelectedDayEvent extends WaterHeaterEvent { + final int index; + final bool value; + + const UpdateSelectedDayEvent(this.index, this.value); + + @override + List get props => [index, value]; +} + +class UpdateFunctionOnEvent extends WaterHeaterEvent { + final bool isOn; + + const UpdateFunctionOnEvent(this.isOn); + + @override + List get props => [isOn]; } diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart index 95c68f24..1054104f 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart @@ -13,7 +13,7 @@ final class WaterHeaterInitial extends WaterHeaterState {} final class WaterHeaterLoadingState extends WaterHeaterState {} -final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { +class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { final WaterHeaterStatusModel status; final ScheduleModes? scheduleMode; final int? hours; @@ -21,6 +21,10 @@ final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { final bool? isActive; final Duration? countdownRemaining; final List schedules; + final List selectedDays; + final TimeOfDay? selectedTime; + final bool functionOn; + final bool isEditing; const WaterHeaterDeviceStatusLoaded( this.status, { @@ -30,6 +34,10 @@ final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { this.isActive, this.countdownRemaining, this.schedules = const [], + this.selectedDays = const [false, false, false, false, false, false, false], + this.selectedTime, + this.functionOn = false, + this.isEditing = false, }); @override @@ -41,6 +49,10 @@ final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { isActive, countdownRemaining, schedules, + selectedDays, + selectedTime, + functionOn, + isEditing ]; WaterHeaterDeviceStatusLoaded copyWith({ @@ -51,6 +63,10 @@ final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { bool? isActive, Duration? countdownRemaining, List? schedules, + List? selectedDays, + TimeOfDay? selectedTime, + bool? functionOn, + bool? isEditing, }) { return WaterHeaterDeviceStatusLoaded( status ?? this.status, @@ -60,26 +76,14 @@ final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { isActive: isActive ?? this.isActive, countdownRemaining: countdownRemaining ?? this.countdownRemaining, schedules: schedules ?? this.schedules, + selectedDays: selectedDays ?? this.selectedDays, + selectedTime: selectedTime ?? this.selectedTime, + functionOn: functionOn ?? this.functionOn, + isEditing: isEditing ?? this.isEditing, ); } } -class ScheduleEntry { - final List selectedDays; - final TimeOfDay time; - final bool functionOn; - - ScheduleEntry({ - required this.selectedDays, - required this.time, - required this.functionOn, - }); - - @override - String toString() => - 'ScheduleEntry(selectedDays: $selectedDays, time: $time, functionOn: $functionOn)'; -} - final class WaterHeaterFailedState extends WaterHeaterState { final String error; diff --git a/lib/pages/device_managment/water_heater/models/schedule_entry.dart b/lib/pages/device_managment/water_heater/models/schedule_entry.dart new file mode 100644 index 00000000..2d02b617 --- /dev/null +++ b/lib/pages/device_managment/water_heater/models/schedule_entry.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class ScheduleEntry { + final List selectedDays; + final TimeOfDay time; + final bool functionOn; + final String category; + + ScheduleEntry({ + required this.selectedDays, + required this.time, + required this.functionOn, + required this.category, + }); + + @override + String toString() => + 'ScheduleEntry(selectedDays: $selectedDays, time: $time, functionOn: $functionOn)'; +} \ No newline at end of file diff --git a/lib/pages/device_managment/water_heater/models/send_schedule.dart b/lib/pages/device_managment/water_heater/models/send_schedule.dart new file mode 100644 index 00000000..650f3a04 --- /dev/null +++ b/lib/pages/device_managment/water_heater/models/send_schedule.dart @@ -0,0 +1,93 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +/* +{ + "category": "kg", + "time": "2024-09-22T10:31:54Z", + "function": { + "code": "switch_1", + "value": true + }, + "days": [ + "Sun" + ] +} +*/ + +class SendSchedule { + final String category; + final String time; + final Status function; + final List days; + + SendSchedule({ + required this.category, + required this.time, + required this.function, + required this.days, + }); + + SendSchedule copyWith({ + String? category, + String? time, + Status? function, + List? days, + }) { + return SendSchedule( + category: category ?? this.category, + time: time ?? this.time, + function: function ?? this.function, + days: days ?? this.days, + ); + } + + Map toMap() { + return { + 'category': category, + 'time': time, + 'function': function.toMap(), + 'days': days, + }; + } + + factory SendSchedule.fromMap(Map map) { + return SendSchedule( + category: map['category'] ?? '', + time: map['time'] ?? '', + function: Status.fromMap(map['function']), + days: List.from(map['days']), + ); + } + + String toJson() => json.encode(toMap()); + + factory SendSchedule.fromJson(String source) => + SendSchedule.fromMap(json.decode(source)); + + @override + String toString() { + return 'SendSchedule(category: $category, time: $time, function: $function, days: $days)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is SendSchedule && + other.category == category && + other.time == time && + other.function == function && + listEquals(other.days, days); + } + + @override + int get hashCode { + return category.hashCode ^ + time.hashCode ^ + function.hashCode ^ + days.hashCode; + } +} diff --git a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart index 125a9483..7ef86bac 100644 --- a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart +++ b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart @@ -1,9 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class BuildScheduleView extends StatefulWidget { @@ -171,7 +174,8 @@ class _BuildScheduleViewState extends State { padding: 2, backgroundColor: ColorsManager.graysColor, borderRadius: 15, - onPressed: () => _showAddScheduleDialog(context), + onPressed: () => + _showAddScheduleDialog(context, schedule: null, index: null), child: Row( children: [ const Icon(Icons.add, color: ColorsManager.primaryColor), @@ -192,52 +196,92 @@ class _BuildScheduleViewState extends State { } Widget _buildScheduleTable(WaterHeaterDeviceStatusLoaded state) { - return Table( - border: TableBorder.all(color: Colors.grey), + return Column( children: [ - TableRow( + Table( + border: TableBorder.all( + color: ColorsManager.graysColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(20), topRight: Radius.circular(20)), + ), children: [ - _buildTableHeader('Active'), - _buildTableHeader('Days'), - _buildTableHeader('Time'), - _buildTableHeader('Function'), - _buildTableHeader('Action'), - ], - ), - if (state.schedules.isEmpty) - TableRow( - children: [ - Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - 'No schedules added yet', - style: context.textTheme.bodySmall!.copyWith( - fontSize: 13, - color: ColorsManager.grayColor, - ), - ), + TableRow( + decoration: const BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), ), ), - const SizedBox(), - const SizedBox(), - const SizedBox(), - const SizedBox(), - ], + children: [ + _buildTableHeader('Active'), + _buildTableHeader('Days'), + _buildTableHeader('Time'), + _buildTableHeader('Function'), + _buildTableHeader('Action'), + ], + ), + ], + ), + Container( + height: 200, + decoration: BoxDecoration( + border: Border.all(color: ColorsManager.graysColor), + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(20), + bottomRight: Radius.circular(20)), ), - for (int i = 0; i < state.schedules.length; i++) - _buildScheduleRow(state.schedules[i], i, context), + child: state.schedules.isEmpty + ? _buildEmptyState(context) + : _buildTableBody(state), + ), ], ); } + Widget _buildEmptyState(BuildContext context) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset(Assets.emptyRecords, width: 40, height: 40), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'No schedules added yet', + style: context.textTheme.bodySmall!.copyWith( + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), + ), + ], + ), + ); + } + + Widget _buildTableBody(WaterHeaterDeviceStatusLoaded state) { + return SingleChildScrollView( + child: Table( + border: TableBorder.all(color: ColorsManager.graysColor), + defaultVerticalAlignment: TableCellVerticalAlignment.middle, + children: [ + for (int i = 0; i < state.schedules.length; i++) + _buildScheduleRow(state.schedules[i], i, context), + ], + ), + ); + } + TableRow _buildScheduleRow( ScheduleEntry schedule, int index, BuildContext context) { return TableRow( children: [ Center( child: schedule.functionOn - ? const Icon(Icons.radio_button_checked) + ? const Icon(Icons.radio_button_checked, + color: ColorsManager.blueColor) : const Icon(Icons.radio_button_unchecked)), Center(child: Text(_getSelectedDays(schedule.selectedDays))), Center(child: Text(schedule.time.format(context))), @@ -247,9 +291,10 @@ class _BuildScheduleViewState extends State { runAlignment: WrapAlignment.center, children: [ TextButton( + style: TextButton.styleFrom(padding: EdgeInsets.zero), onPressed: () { - _showAddScheduleDialog(context); - // _showEditScheduleDialog(context, schedule, index); + _showAddScheduleDialog(context, + schedule: schedule, index: index); }, child: Text( 'Edit', @@ -258,6 +303,7 @@ class _BuildScheduleViewState extends State { ), ), TextButton( + style: TextButton.styleFrom(padding: EdgeInsets.zero), onPressed: () { context .read() @@ -287,245 +333,165 @@ class _BuildScheduleViewState extends State { return selectedDaysStr.join(', '); } - void _showEditScheduleDialog( - BuildContext context, ScheduleEntry schedule, int index) { - List selectedDays = List.from(schedule.selectedDays); - TimeOfDay selectedTime = schedule.time; - bool isOn = schedule.functionOn; - - showDialog( - context: context, - builder: (ctx) { - return StatefulBuilder( - builder: (context, setState) { - return AlertDialog( - title: const Text('Edit Schedule'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ElevatedButton( - onPressed: () async { - TimeOfDay? time = await showTimePicker( - context: context, - initialTime: selectedTime, - ); - if (time != null) { - setState(() { - selectedTime = time; - }); - } - }, - child: Text(selectedTime.format(context)), - ), - ], - ), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text('Cancel'), - ), - TextButton( - onPressed: () { - context.read().add( - UpdateScheduleEntryEvent( - index: index, - selectedDays: selectedDays, - time: selectedTime, - functionOn: isOn, - ), - ); - Navigator.pop(context); - }, - child: const Text('Save'), - ), - ], - ); - }, - ); - }, - ); - } - Widget _buildTableHeader(String label) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - label, - style: const TextStyle(fontWeight: FontWeight.bold), + return TableCell( + child: Padding( + padding: const EdgeInsets.all(12), + child: Text( + label, + style: context.textTheme.bodySmall!.copyWith( + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), ), ); } - void _showAddScheduleDialog( - BuildContext context) { + void _showAddScheduleDialog(BuildContext context, + {ScheduleEntry? schedule, int? index}) { + final bloc = context.read(); + + if (schedule != null) { + bloc.add(InitializeAddScheduleEvent( + selectedTime: schedule.time, + selectedDays: schedule.selectedDays, + functionOn: schedule.functionOn, + isEditing: true, + index: index, + )); + } else { + bloc.add( + const InitializeAddScheduleEvent( + selectedDays: [false, false, false, false, false, false, false], + functionOn: false, + isEditing: false, + index: null, + selectedTime: null, + ), + ); + } + showDialog( context: context, builder: (ctx) { - List selectedDays = [ - false, - false, - false, - false, - false, - false, - false - ]; - - TimeOfDay? selectedTime; - bool isOn = false; return BlocProvider.value( - value: BlocProvider.of(context), - child: StatefulBuilder( - builder: (ctx, setState) { - return AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20)), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const SizedBox(), - Text( - 'Scheduling', - style: context.textTheme.titleLarge!.copyWith( - color: ColorsManager.dialogBlueTitle, - fontWeight: FontWeight.bold, + value: bloc, + child: BlocBuilder( + builder: (context, state) { + if (state is WaterHeaterDeviceStatusLoaded) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox(), + Text( + 'Scheduling', + style: context.textTheme.titleLarge!.copyWith( + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(), + ], + ), + const SizedBox(height: 24), + SizedBox( + width: 150, + height: 40, + child: DefaultButton( + padding: 8, + backgroundColor: ColorsManager.boxColor, + borderRadius: 15, + onPressed: () async { + TimeOfDay? time = await showTimePicker( + context: context, + initialTime: + state.selectedTime ?? TimeOfDay.now(), + ); + if (time != null) { + bloc.add(UpdateSelectedTimeEvent(time)); + } + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + state.selectedTime == null + ? 'Time' + : state.selectedTime!.format(context), + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.grayColor, + ), + ), + const Icon( + Icons.access_time, + color: ColorsManager.grayColor, + size: 18, + ), + ], ), ), - const SizedBox(), - ], - ), - const SizedBox( - height: 24, + ), + const SizedBox(height: 16), + _buildDayCheckboxes(context, state.selectedDays), + const SizedBox(height: 16), + _buildFunctionSwitch(context, state.functionOn), + ], + ), + actions: [ + SizedBox( + width: 200, + child: DefaultButton( + height: 40, + onPressed: () { + Navigator.pop(context); + }, + backgroundColor: ColorsManager.boxColor, + child: Text( + 'Cancel', + style: context.textTheme.bodyMedium, + ), + ), ), SizedBox( - width: 150, - height: 40, + width: 200, child: DefaultButton( - padding: 8, - backgroundColor: ColorsManager.boxColor, - borderRadius: 15, - onPressed: () async { - TimeOfDay? time = await showTimePicker( - context: context, - initialTime: TimeOfDay.now(), - ); - if (time != null) { - setState(() { - selectedTime = time; - }); + height: 40, + onPressed: () { + if (state.selectedTime != null) { + if (state.isEditing && index != null) { + bloc.add(UpdateScheduleEntryEvent( + deviceId: state.status.uuid, + category: 'kg', + functionOn: state.functionOn, + )); + } else { + bloc.add(AddScheduleEvent( + category: 'kg', + time: state.selectedTime!, + selectedDays: state.selectedDays, + functionOn: state.functionOn, + )); + } + Navigator.pop(context); } }, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - selectedTime == null - ? 'Time' - : selectedTime!.format(context), - style: context.textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor, - ), - ), - const Icon( - Icons.access_time, - color: ColorsManager.grayColor, - size: 18, - ), - ], - ), + backgroundColor: ColorsManager.primaryColor, + child: const Text('Save'), ), ), - const SizedBox(height: 16), - Row( - children: [ - _buildDayCheckbox(setState, 'Mon.', 0, selectedDays), - _buildDayCheckbox(setState, 'Tue.', 1, selectedDays), - _buildDayCheckbox(setState, 'Wed.', 2, selectedDays), - _buildDayCheckbox(setState, 'Thu.', 3, selectedDays), - _buildDayCheckbox(setState, 'Fri.', 4, selectedDays), - _buildDayCheckbox(setState, 'Sat.', 5, selectedDays), - _buildDayCheckbox(setState, 'Sun.', 6, selectedDays), - ], - ), - const SizedBox(height: 16), - Row( - children: [ - Text( - 'Function:', - style: context.textTheme.bodySmall! - .copyWith(color: ColorsManager.grayColor), - ), - const SizedBox( - width: 10, - ), - Radio( - value: true, - groupValue: isOn, - onChanged: (bool? value) { - setState(() { - isOn = value!; - }); - }, - ), - const Text('On'), - const SizedBox( - width: 10, - ), - Radio( - value: false, - groupValue: isOn, - onChanged: (bool? value) { - setState(() { - isOn = value!; - }); - }, - ), - const Text('Off'), - ], - ), ], - ), - actions: [ - SizedBox( - width: 200, - child: DefaultButton( - height: 40, - onPressed: () { - Navigator.pop(context); - }, - backgroundColor: ColorsManager.boxColor, - child: Text( - 'Cancel', - style: context.textTheme.bodyMedium, - ), - ), - ), - SizedBox( - width: 200, - child: DefaultButton( - height: 40, - onPressed: () { - if (selectedTime != null) { - context.read().add(AddScheduleEvent( - time: selectedTime!, - selectedDays: selectedDays, - functionOn: isOn, - )); - Navigator.pop(context); - } - }, - backgroundColor: ColorsManager.primaryColor, - child: const Text('Save'), - ), - ), - ], - ); + ); + } + return const SizedBox(); }, ), ); @@ -533,19 +499,57 @@ class _BuildScheduleViewState extends State { ); } - Widget _buildDayCheckbox(void Function(void Function()) setState, String day, - int index, List selectedDays) { + Widget _buildDayCheckboxes(BuildContext context, List selectedDays) { + return Row( + children: List.generate(7, (index) { + final dayLabels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + return Row( + children: [ + Checkbox( + value: selectedDays[index], + onChanged: (bool? value) { + context + .read() + .add(UpdateSelectedDayEvent(index, value!)); + }, + ), + Text(dayLabels[index]), + ], + ); + }), + ); + } + + Widget _buildFunctionSwitch(BuildContext context, bool isOn) { return Row( children: [ - Checkbox( - value: selectedDays[index], + Text( + 'Function:', + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.grayColor), + ), + const SizedBox(width: 10), + Radio( + value: true, + groupValue: isOn, onChanged: (bool? value) { - setState(() { - selectedDays[index] = value!; - }); + context + .read() + .add(const UpdateFunctionOnEvent(true)); }, ), - Text(day), + const Text('On'), + const SizedBox(width: 10), + Radio( + value: false, + groupValue: isOn, + onChanged: (bool? value) { + context + .read() + .add(const UpdateFunctionOnEvent(false)); + }, + ), + const Text('Off'), ], ); } diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 4be915a2..4445d5c1 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/send_schedule.dart'; import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; @@ -79,7 +80,7 @@ class DevicesManagementApi { body: body, showServerMessage: true, expectedResponseModel: (json) { - return (json['successResults'] as List).isNotEmpty ; + return (json['successResults'] as List).isNotEmpty; }, ); @@ -175,4 +176,61 @@ class DevicesManagementApi { ); } } + + Future addScheduleRecord(SendSchedule sendSchedule, String uuid) async { + try { + final response = await HTTPService().post( + path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), + body: sendSchedule.toMap(), + showServerMessage: true, + expectedResponseModel: (json) { + return json['success'] ?? false; + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return false; + } + } + + Future updateScheduleRecord( + {required bool enable, + required String uuid, + required String scheduleId}) async { + try { + final response = await HTTPService().put( + path: ApiEndpoints.scheduleByDeviceId + .replaceAll('{deviceUuid}', uuid) + .replaceAll('{scheduleUuid}', scheduleId), + body: { + 'scheduleId': scheduleId, + 'enable': enable, + }, + expectedResponseModel: (json) { + return json['success'] ?? false; + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return false; + } + } + + Future deleteScheduleRecord(String uuid) async { + try { + final response = await HTTPService().delete( + path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), + showServerMessage: true, + expectedResponseModel: (json) { + return json['success'] ?? false; + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return false; + } + } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 3138c502..0c17e2bc 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -38,4 +38,10 @@ abstract class ApiEndpoints { static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}'; static const String getDeviceLogsByDate = '/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}'; + + static const String scheduleByDeviceId = '/schedule/{deviceUuid}'; + static const String deleteScheduleByDeviceId = + '/schedule/{deviceUuid}/{scheduleUuid}'; + static const String updateScheduleByDeviceId = + '/schedule/enable/{deviceUuid}'; } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 3c22e031..c334d2d1 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -154,4 +154,7 @@ class Assets { static const String mainDoorReports = 'assets/icons/main_door_reports.svg'; //assets/icons/main_door.svg static const String mainDoor = 'assets/icons/main_door.svg'; + + //assets/icons/empty_records.svg + static const String emptyRecords = 'assets/icons/empty_records.svg'; } diff --git a/lib/utils/format_date_time.dart b/lib/utils/format_date_time.dart index 5c089a2c..e214b46c 100644 --- a/lib/utils/format_date_time.dart +++ b/lib/utils/format_date_time.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; String formatDateTime(DateTime? dateTime) { @@ -9,3 +10,17 @@ String formatDateTime(DateTime? dateTime) { return '${dateFormatter.format(dateTime)} ${timeFormatter.format(dateTime)}'; } + +String formatTimeOfDayToISO(TimeOfDay time, {DateTime? currentDate}) { + final now = currentDate ?? DateTime.now(); + + final dateTime = DateTime( + now.year, + now.month, + now.day, + time.hour, + time.minute, + ); + + return dateTime.toUtc().toIso8601String(); +} \ No newline at end of file