mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 15:17:31 +00:00
refactor schedule view and cleaning
This commit is contained in:
@ -5,7 +5,6 @@ import 'package:bloc/bloc.dart';
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/material.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/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/schedule_model.dart';
|
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.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/services/devices_mang_api.dart';
|
||||||
@ -21,17 +20,20 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
on<UpdateScheduleEvent>(_updateScheduleEvent);
|
on<UpdateScheduleEvent>(_updateScheduleEvent);
|
||||||
on<StopScheduleEvent>(_stopScheduleEvent);
|
on<StopScheduleEvent>(_stopScheduleEvent);
|
||||||
on<DecrementCountdownEvent>(_onDecrementCountdown);
|
on<DecrementCountdownEvent>(_onDecrementCountdown);
|
||||||
on<AddScheduleEvent>(_onAddSchedule);
|
|
||||||
on<DeleteScheduleEvent>(_onDeleteSchedule);
|
|
||||||
on<UpdateScheduleEntryEvent>(_onUpdateSchedule);
|
|
||||||
on<InitializeAddScheduleEvent>(_initializeAddSchedule);
|
on<InitializeAddScheduleEvent>(_initializeAddSchedule);
|
||||||
on<UpdateSelectedTimeEvent>(_updateSelectedTime);
|
on<UpdateSelectedTimeEvent>(_updateSelectedTime);
|
||||||
on<UpdateSelectedDayEvent>(_updateSelectedDay);
|
on<UpdateSelectedDayEvent>(_updateSelectedDay);
|
||||||
on<UpdateFunctionOnEvent>(_updateFunctionOn);
|
on<UpdateFunctionOnEvent>(_updateFunctionOn);
|
||||||
|
|
||||||
|
on<GetSchedulesEvent>(_getSchedule);
|
||||||
|
on<AddScheduleEvent>(_onAddSchedule);
|
||||||
|
on<DeleteScheduleEvent>(_onDeleteSchedule);
|
||||||
|
on<UpdateScheduleEntryEvent>(_onUpdateSchedule);
|
||||||
}
|
}
|
||||||
|
|
||||||
late WaterHeaterStatusModel deviceStatus;
|
late WaterHeaterStatusModel deviceStatus;
|
||||||
Timer? _countdownTimer;
|
Timer? _countdownTimer;
|
||||||
|
// Timer? _inchingTimer;
|
||||||
|
|
||||||
FutureOr<void> _initializeAddSchedule(
|
FutureOr<void> _initializeAddSchedule(
|
||||||
InitializeAddScheduleEvent event,
|
InitializeAddScheduleEvent event,
|
||||||
@ -87,22 +89,37 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
) async {
|
) async {
|
||||||
final currentState = state;
|
final currentState = state;
|
||||||
if (currentState is WaterHeaterDeviceStatusLoaded) {
|
if (currentState is WaterHeaterDeviceStatusLoaded) {
|
||||||
final countdownRemaining =
|
if (event.scheduleMode == ScheduleModes.schedule) {
|
||||||
// currentState.isActive == true
|
emit(currentState.copyWith(
|
||||||
// ? currentState.countdownRemaining
|
scheduleMode: ScheduleModes.schedule,
|
||||||
// :
|
));
|
||||||
Duration(hours: event.hours, minutes: event.minutes);
|
}
|
||||||
|
if (event.scheduleMode == ScheduleModes.countdown) {
|
||||||
|
final countdownRemaining =
|
||||||
|
Duration(hours: event.hours, minutes: event.minutes);
|
||||||
|
|
||||||
emit(currentState.copyWith(
|
emit(currentState.copyWith(
|
||||||
scheduleMode: event.scheduleMode,
|
scheduleMode: ScheduleModes.countdown,
|
||||||
hours: countdownRemaining.inHours,
|
countdownHours: countdownRemaining.inHours,
|
||||||
minutes: countdownRemaining.inMinutes % 60,
|
countdownMinutes: countdownRemaining.inMinutes % 60,
|
||||||
isActive: currentState.isActive,
|
isCountdownActive: currentState.isCountdownActive,
|
||||||
countdownRemaining: countdownRemaining,
|
countdownRemaining: countdownRemaining,
|
||||||
));
|
));
|
||||||
|
|
||||||
if (!currentState.isActive! && countdownRemaining > Duration.zero) {
|
if (!currentState.isCountdownActive! &&
|
||||||
_startCountdown(emit, countdownRemaining);
|
countdownRemaining > Duration.zero) {
|
||||||
|
_startCountdownTimer(emit, countdownRemaining);
|
||||||
|
}
|
||||||
|
} else if (event.scheduleMode == ScheduleModes.inching) {
|
||||||
|
final inchingDuration =
|
||||||
|
Duration(hours: event.hours, minutes: event.minutes);
|
||||||
|
|
||||||
|
emit(currentState.copyWith(
|
||||||
|
scheduleMode: ScheduleModes.inching,
|
||||||
|
inchingHours: inchingDuration.inHours,
|
||||||
|
inchingMinutes: inchingDuration.inMinutes % 60,
|
||||||
|
isInchingActive: currentState.isInchingActive,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,28 +147,39 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
emit: emit,
|
emit: emit,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (success &&
|
if (success) {
|
||||||
(event.code == "countdown_1" || event.code == "switch_inching")) {
|
if (event.code == "countdown_1") {
|
||||||
final countdownDuration = Duration(seconds: event.value);
|
final countdownDuration = Duration(seconds: event.value);
|
||||||
|
|
||||||
emit(currentState.copyWith(
|
|
||||||
status: deviceStatus,
|
|
||||||
scheduleMode: deviceStatus.scheduleMode,
|
|
||||||
hours: countdownDuration.inHours,
|
|
||||||
minutes: (countdownDuration.inMinutes % 60),
|
|
||||||
isActive: true,
|
|
||||||
countdownRemaining: countdownDuration,
|
|
||||||
));
|
|
||||||
if (countdownDuration.inSeconds > 0) {
|
|
||||||
_startCountdown(emit, countdownDuration);
|
|
||||||
} else {
|
|
||||||
_countdownTimer?.cancel();
|
|
||||||
emit(currentState.copyWith(
|
emit(currentState.copyWith(
|
||||||
hours: 0,
|
countdownHours: countdownDuration.inHours,
|
||||||
minutes: 0,
|
countdownMinutes: countdownDuration.inMinutes % 60,
|
||||||
isActive: false,
|
countdownRemaining: countdownDuration,
|
||||||
countdownRemaining: Duration.zero,
|
isCountdownActive: true,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
if (countdownDuration.inSeconds > 0) {
|
||||||
|
_startCountdownTimer(emit, countdownDuration);
|
||||||
|
} else {
|
||||||
|
_countdownTimer?.cancel();
|
||||||
|
emit(currentState.copyWith(
|
||||||
|
countdownHours: 0,
|
||||||
|
countdownMinutes: 0,
|
||||||
|
countdownRemaining: Duration.zero,
|
||||||
|
isCountdownActive: false,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if (event.code == "switch_inching") {
|
||||||
|
final inchingDuration = Duration(seconds: event.value);
|
||||||
|
//if (inchingDuration.inSeconds > 0) {
|
||||||
|
// _startInchingTimer(emit, inchingDuration);
|
||||||
|
// } else {
|
||||||
|
emit(currentState.copyWith(
|
||||||
|
inchingHours: inchingDuration.inHours,
|
||||||
|
inchingMinutes: inchingDuration.inMinutes % 60,
|
||||||
|
isInchingActive: true,
|
||||||
|
));
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,28 +191,30 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
) async {
|
) async {
|
||||||
if (state is WaterHeaterDeviceStatusLoaded) {
|
if (state is WaterHeaterDeviceStatusLoaded) {
|
||||||
final currentState = state as WaterHeaterDeviceStatusLoaded;
|
final currentState = state as WaterHeaterDeviceStatusLoaded;
|
||||||
|
final isCountDown = currentState.scheduleMode == ScheduleModes.countdown;
|
||||||
|
|
||||||
_countdownTimer?.cancel();
|
_countdownTimer?.cancel();
|
||||||
|
|
||||||
deviceStatus = deviceStatus.copyWith(
|
if (isCountDown) {
|
||||||
countdownHours: 0,
|
emit(currentState.copyWith(
|
||||||
countdownMinutes: 0,
|
countdownHours: 0,
|
||||||
scheduleMode: ScheduleModes.countdown,
|
countdownMinutes: 0,
|
||||||
);
|
countdownRemaining: Duration.zero,
|
||||||
|
isCountdownActive: false,
|
||||||
emit(currentState.copyWith(
|
));
|
||||||
status: deviceStatus,
|
} else if (currentState.scheduleMode == ScheduleModes.inching) {
|
||||||
scheduleMode: ScheduleModes.countdown,
|
emit(currentState.copyWith(
|
||||||
hours: 0,
|
inchingHours: 0,
|
||||||
minutes: 0,
|
inchingMinutes: 0,
|
||||||
isActive: false,
|
isInchingActive: false,
|
||||||
countdownRemaining: Duration.zero,
|
));
|
||||||
));
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final status = await DevicesManagementApi().deviceControl(
|
final status = await DevicesManagementApi().deviceControl(
|
||||||
event.deviceId,
|
event.deviceId,
|
||||||
Status(code: 'countdown_1', value: 0),
|
Status(
|
||||||
|
code: isCountDown ? 'countdown_1' : 'switch_inching', value: 0),
|
||||||
);
|
);
|
||||||
if (!status) {
|
if (!status) {
|
||||||
emit(const WaterHeaterFailedState(error: 'Failed to stop schedule.'));
|
emit(const WaterHeaterFailedState(error: 'Failed to stop schedule.'));
|
||||||
@ -207,30 +237,66 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
deviceStatus =
|
deviceStatus =
|
||||||
WaterHeaterStatusModel.fromJson(event.deviceId, status.status);
|
WaterHeaterStatusModel.fromJson(event.deviceId, status.status);
|
||||||
|
|
||||||
if (deviceStatus.countdownHours > 0 ||
|
if (deviceStatus.scheduleMode == ScheduleModes.countdown) {
|
||||||
deviceStatus.countdownMinutes > 0) {
|
final countdownRemaining = Duration(
|
||||||
final remainingDuration = Duration(
|
|
||||||
hours: deviceStatus.countdownHours,
|
hours: deviceStatus.countdownHours,
|
||||||
minutes: deviceStatus.countdownMinutes,
|
minutes: deviceStatus.countdownMinutes,
|
||||||
);
|
);
|
||||||
|
|
||||||
emit(WaterHeaterDeviceStatusLoaded(
|
if (countdownRemaining > Duration.zero) {
|
||||||
deviceStatus,
|
emit(WaterHeaterDeviceStatusLoaded(
|
||||||
scheduleMode: deviceStatus.scheduleMode,
|
deviceStatus,
|
||||||
hours: deviceStatus.countdownHours,
|
scheduleMode: ScheduleModes.countdown,
|
||||||
minutes: deviceStatus.countdownMinutes,
|
countdownHours: deviceStatus.countdownHours,
|
||||||
isActive: true,
|
countdownMinutes: deviceStatus.countdownMinutes,
|
||||||
countdownRemaining: remainingDuration,
|
isCountdownActive: true,
|
||||||
));
|
countdownRemaining: countdownRemaining,
|
||||||
|
));
|
||||||
|
_startCountdownTimer(emit, countdownRemaining);
|
||||||
|
} else {
|
||||||
|
emit(WaterHeaterDeviceStatusLoaded(
|
||||||
|
deviceStatus,
|
||||||
|
scheduleMode: ScheduleModes.countdown,
|
||||||
|
countdownHours: 0,
|
||||||
|
countdownMinutes: 0,
|
||||||
|
isCountdownActive: false,
|
||||||
|
countdownRemaining: Duration.zero,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if (deviceStatus.scheduleMode == ScheduleModes.inching) {
|
||||||
|
final inchingDuration = Duration(
|
||||||
|
hours: deviceStatus.inchingHours,
|
||||||
|
minutes: deviceStatus.inchingMinutes,
|
||||||
|
);
|
||||||
|
|
||||||
_startCountdown(emit, remainingDuration);
|
if (inchingDuration > Duration.zero) {
|
||||||
|
emit(WaterHeaterDeviceStatusLoaded(
|
||||||
|
deviceStatus,
|
||||||
|
scheduleMode: ScheduleModes.inching,
|
||||||
|
inchingHours: deviceStatus.inchingHours,
|
||||||
|
inchingMinutes: deviceStatus.inchingMinutes,
|
||||||
|
isInchingActive: true,
|
||||||
|
));
|
||||||
|
//_startInchingTimer(emit, inchingDuration);
|
||||||
|
} else {
|
||||||
|
emit(WaterHeaterDeviceStatusLoaded(
|
||||||
|
deviceStatus,
|
||||||
|
scheduleMode: ScheduleModes.inching,
|
||||||
|
inchingHours: 0,
|
||||||
|
inchingMinutes: 0,
|
||||||
|
isInchingActive: false,
|
||||||
|
));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
emit(WaterHeaterDeviceStatusLoaded(
|
emit(WaterHeaterDeviceStatusLoaded(
|
||||||
deviceStatus,
|
deviceStatus,
|
||||||
scheduleMode: deviceStatus.scheduleMode,
|
scheduleMode: deviceStatus.scheduleMode,
|
||||||
hours: 0,
|
countdownHours: 0,
|
||||||
minutes: 0,
|
countdownMinutes: 0,
|
||||||
isActive: false,
|
inchingHours: 0,
|
||||||
|
inchingMinutes: 0,
|
||||||
|
isCountdownActive: false,
|
||||||
|
isInchingActive: false,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -238,6 +304,28 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _startCountdownTimer(
|
||||||
|
Emitter<WaterHeaterState> emit,
|
||||||
|
Duration countdownRemaining,
|
||||||
|
) {
|
||||||
|
_countdownTimer?.cancel();
|
||||||
|
|
||||||
|
_countdownTimer = Timer.periodic(const Duration(minutes: 1), (timer) {
|
||||||
|
add(DecrementCountdownEvent());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// void _startInchingTimer(
|
||||||
|
// Emitter<WaterHeaterState> emit,
|
||||||
|
// Duration inchingDuration,
|
||||||
|
// ) {
|
||||||
|
// _inchingTimer?.cancel();
|
||||||
|
|
||||||
|
// _inchingTimer = Timer.periodic(const Duration(minutes: 1), (timer) {
|
||||||
|
// add(DecrementInchingEvent());
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
_onDecrementCountdown(
|
_onDecrementCountdown(
|
||||||
DecrementCountdownEvent event,
|
DecrementCountdownEvent event,
|
||||||
Emitter<WaterHeaterState> emit,
|
Emitter<WaterHeaterState> emit,
|
||||||
@ -253,9 +341,9 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
if (newRemaining <= Duration.zero) {
|
if (newRemaining <= Duration.zero) {
|
||||||
_countdownTimer?.cancel();
|
_countdownTimer?.cancel();
|
||||||
emit(currentState.copyWith(
|
emit(currentState.copyWith(
|
||||||
hours: 0,
|
countdownHours: 0,
|
||||||
minutes: 0,
|
countdownMinutes: 0,
|
||||||
isActive: false,
|
isCountdownActive: false,
|
||||||
countdownRemaining: Duration.zero,
|
countdownRemaining: Duration.zero,
|
||||||
));
|
));
|
||||||
return;
|
return;
|
||||||
@ -267,22 +355,44 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
int newMinutes = (totalSeconds % 3600) ~/ 60;
|
int newMinutes = (totalSeconds % 3600) ~/ 60;
|
||||||
|
|
||||||
emit(currentState.copyWith(
|
emit(currentState.copyWith(
|
||||||
hours: newHours,
|
countdownHours: newHours,
|
||||||
minutes: newMinutes,
|
countdownMinutes: newMinutes,
|
||||||
countdownRemaining: newRemaining,
|
countdownRemaining: newRemaining,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _startCountdown(
|
// FutureOr<void> _onDecrementInching(
|
||||||
Emitter<WaterHeaterState> emit, Duration countdownRemaining) {
|
// DecrementInchingEvent event,
|
||||||
_countdownTimer?.cancel();
|
// Emitter<WaterHeaterState> emit,
|
||||||
|
// ) {
|
||||||
|
// if (state is WaterHeaterDeviceStatusLoaded) {
|
||||||
|
// final currentState = state as WaterHeaterDeviceStatusLoaded;
|
||||||
|
|
||||||
_countdownTimer = Timer.periodic(const Duration(minutes: 1), (timer) {
|
// if (currentState.inchingHours > 0 || currentState.inchingMinutes > 0) {
|
||||||
add(DecrementCountdownEvent());
|
// final newRemaining = Duration(
|
||||||
});
|
// hours: currentState.inchingHours,
|
||||||
}
|
// minutes: currentState.inchingMinutes,
|
||||||
|
// ) -
|
||||||
|
// const Duration(minutes: 1);
|
||||||
|
|
||||||
|
// if (newRemaining <= Duration.zero) {
|
||||||
|
// _inchingTimer?.cancel();
|
||||||
|
// emit(currentState.copyWith(
|
||||||
|
// inchingHours: 0,
|
||||||
|
// inchingMinutes: 0,
|
||||||
|
// isInchingActive: false,
|
||||||
|
// ));
|
||||||
|
// } else {
|
||||||
|
// emit(currentState.copyWith(
|
||||||
|
// inchingHours: newRemaining.inHours,
|
||||||
|
// inchingMinutes: newRemaining.inMinutes % 60,
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
Future<bool> _runDebounce({
|
Future<bool> _runDebounce({
|
||||||
required String deviceId,
|
required String deviceId,
|
||||||
@ -353,6 +463,36 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_countdownTimer?.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureOr<void> _getSchedule(
|
||||||
|
GetSchedulesEvent event, Emitter<WaterHeaterState> emit) async {
|
||||||
|
emit(ScheduleLoadingState());
|
||||||
|
|
||||||
|
try {
|
||||||
|
// List<ScheduleModel> schedules = await DevicesManagementApi()
|
||||||
|
// .getDeviceSchedules(deviceStatus.uuid, event.category);
|
||||||
|
|
||||||
|
List<ScheduleModel> schedules = const [];
|
||||||
|
|
||||||
|
emit(WaterHeaterDeviceStatusLoaded(
|
||||||
|
deviceStatus,
|
||||||
|
schedules: schedules,
|
||||||
|
scheduleMode: ScheduleModes.schedule,
|
||||||
|
));
|
||||||
|
} catch (e) {
|
||||||
|
//(const WaterHeaterFailedState(error: 'Failed to fetch schedules.'));
|
||||||
|
emit(WaterHeaterDeviceStatusLoaded(
|
||||||
|
deviceStatus,
|
||||||
|
schedules: const [],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FutureOr<void> _onAddSchedule(
|
FutureOr<void> _onAddSchedule(
|
||||||
AddScheduleEvent event,
|
AddScheduleEvent event,
|
||||||
Emitter<WaterHeaterState> emit,
|
Emitter<WaterHeaterState> emit,
|
||||||
@ -360,59 +500,30 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
if (state is WaterHeaterDeviceStatusLoaded) {
|
if (state is WaterHeaterDeviceStatusLoaded) {
|
||||||
final currentState = state as WaterHeaterDeviceStatusLoaded;
|
final currentState = state as WaterHeaterDeviceStatusLoaded;
|
||||||
|
|
||||||
ScheduleModel sendSchedule = ScheduleModel(
|
ScheduleModel newSchedule = ScheduleModel(
|
||||||
category: event.category,
|
category: event.category,
|
||||||
time: formatTimeOfDayToISO(event.time),
|
time: formatTimeOfDayToISO(event.time),
|
||||||
function: Status(code: 'switch_1', value: event.functionOn),
|
function: Status(code: 'switch_1', value: event.functionOn),
|
||||||
days: _getSelectedDaysString(event.selectedDays),
|
days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// emit(ScheduleLoadingState());
|
||||||
|
|
||||||
bool success = await DevicesManagementApi()
|
bool success = await DevicesManagementApi()
|
||||||
.addScheduleRecord(sendSchedule, currentState.status.uuid);
|
.addScheduleRecord(newSchedule, currentState.status.uuid);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
final newSchedule = ScheduleEntry(
|
|
||||||
selectedDays: event.selectedDays,
|
|
||||||
time: event.time,
|
|
||||||
functionOn: event.functionOn,
|
|
||||||
category: event.category,
|
|
||||||
);
|
|
||||||
|
|
||||||
final updatedSchedules =
|
final updatedSchedules =
|
||||||
List<ScheduleEntry>.from(currentState.schedules)..add(newSchedule);
|
List<ScheduleModel>.from(currentState.schedules)..add(newSchedule);
|
||||||
|
|
||||||
emit(currentState.copyWith(schedules: updatedSchedules));
|
emit(currentState.copyWith(schedules: updatedSchedules));
|
||||||
} else {
|
} else {
|
||||||
emit(const WaterHeaterFailedState(
|
emit(currentState);
|
||||||
error: 'Failed to add schedule. Please try again.'));
|
//emit(const WaterHeaterFailedState(error: 'Failed to add schedule.'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> _getSelectedDaysString(List<bool> selectedDays) {
|
|
||||||
final days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
||||||
List<String> selectedDaysStr = [];
|
|
||||||
for (int i = 0; i < selectedDays.length; i++) {
|
|
||||||
if (selectedDays[i]) {
|
|
||||||
selectedDaysStr.add(days[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return selectedDaysStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
FutureOr<void> _onDeleteSchedule(
|
|
||||||
DeleteScheduleEvent event,
|
|
||||||
Emitter<WaterHeaterState> emit,
|
|
||||||
) {
|
|
||||||
if (state is WaterHeaterDeviceStatusLoaded) {
|
|
||||||
final currentState = state as WaterHeaterDeviceStatusLoaded;
|
|
||||||
final updatedSchedules = List<ScheduleEntry>.from(currentState.schedules)
|
|
||||||
..removeAt(event.index);
|
|
||||||
|
|
||||||
emit(currentState.copyWith(schedules: updatedSchedules));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FutureOr<void> _onUpdateSchedule(
|
FutureOr<void> _onUpdateSchedule(
|
||||||
UpdateScheduleEntryEvent event,
|
UpdateScheduleEntryEvent event,
|
||||||
Emitter<WaterHeaterState> emit,
|
Emitter<WaterHeaterState> emit,
|
||||||
@ -420,44 +531,53 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
if (state is WaterHeaterDeviceStatusLoaded) {
|
if (state is WaterHeaterDeviceStatusLoaded) {
|
||||||
final currentState = state as WaterHeaterDeviceStatusLoaded;
|
final currentState = state as WaterHeaterDeviceStatusLoaded;
|
||||||
|
|
||||||
// Get the current schedule ID or UUID (assuming it's stored in the schedules)
|
ScheduleModel updatedSchedule = currentState.schedules[event.index]
|
||||||
String scheduleId =
|
.copyWith(
|
||||||
""; // Retrieve the actual schedule ID based on your model
|
function: Status(code: 'switch_1', value: event.functionOn));
|
||||||
|
|
||||||
|
// emit(ScheduleLoadingState());
|
||||||
|
|
||||||
// Call the API to update the schedule
|
|
||||||
bool success = await DevicesManagementApi().updateScheduleRecord(
|
bool success = await DevicesManagementApi().updateScheduleRecord(
|
||||||
enable: event.functionOn,
|
enable: event.functionOn,
|
||||||
uuid: event.deviceId,
|
uuid: currentState.status.uuid,
|
||||||
scheduleId: scheduleId,
|
scheduleId: event.scheduleId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
final updatedSchedules =
|
final updatedSchedules =
|
||||||
List<ScheduleEntry>.from(currentState.schedules);
|
List<ScheduleModel>.from(currentState.schedules)
|
||||||
|
..[event.index] = updatedSchedule;
|
||||||
|
|
||||||
final updatedScheduleIndex = updatedSchedules.indexWhere((schedule) {
|
emit(currentState.copyWith(schedules: updatedSchedules));
|
||||||
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 {
|
} else {
|
||||||
emit(const WaterHeaterFailedState(
|
emit(currentState);
|
||||||
error: 'Failed to update schedule. Please try again.'));
|
// emit(const WaterHeaterFailedState(error: 'Failed to update schedule.'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
FutureOr<void> _onDeleteSchedule(
|
||||||
Future<void> close() {
|
DeleteScheduleEvent event,
|
||||||
_countdownTimer?.cancel();
|
Emitter<WaterHeaterState> emit,
|
||||||
return super.close();
|
) async {
|
||||||
|
if (state is WaterHeaterDeviceStatusLoaded) {
|
||||||
|
final currentState = state as WaterHeaterDeviceStatusLoaded;
|
||||||
|
|
||||||
|
// emit(ScheduleLoadingState());
|
||||||
|
|
||||||
|
bool success = await DevicesManagementApi()
|
||||||
|
.deleteScheduleRecord(currentState.status.uuid, event.scheduleId);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
final updatedSchedules =
|
||||||
|
List<ScheduleModel>.from(currentState.schedules)
|
||||||
|
..removeAt(event.index);
|
||||||
|
|
||||||
|
emit(currentState.copyWith(schedules: updatedSchedules));
|
||||||
|
} else {
|
||||||
|
emit(currentState);
|
||||||
|
// emit(const WaterHeaterFailedState(error: 'Failed to delete schedule.'));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,26 +82,44 @@ final class AddScheduleEvent extends WaterHeaterEvent {
|
|||||||
|
|
||||||
final class DeleteScheduleEvent extends WaterHeaterEvent {
|
final class DeleteScheduleEvent extends WaterHeaterEvent {
|
||||||
final int index;
|
final int index;
|
||||||
|
final String scheduleId;
|
||||||
|
|
||||||
const DeleteScheduleEvent(this.index);
|
const DeleteScheduleEvent({required this.index, required this.scheduleId});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [index];
|
List<Object?> get props => [index, scheduleId];
|
||||||
}
|
}
|
||||||
|
|
||||||
final class UpdateScheduleEntryEvent extends WaterHeaterEvent {
|
final class UpdateScheduleEntryEvent extends WaterHeaterEvent {
|
||||||
final bool functionOn;
|
final bool functionOn;
|
||||||
final String category;
|
final String category;
|
||||||
final String deviceId;
|
final String deviceId;
|
||||||
|
final int index;
|
||||||
|
final String scheduleId;
|
||||||
|
|
||||||
const UpdateScheduleEntryEvent({
|
const UpdateScheduleEntryEvent({
|
||||||
required this.functionOn,
|
required this.functionOn,
|
||||||
required this.category,
|
required this.category,
|
||||||
required this.deviceId,
|
required this.deviceId,
|
||||||
|
required this.scheduleId,
|
||||||
|
required this.index,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [category, functionOn, deviceId];
|
List<Object?> get props => [category, functionOn, deviceId, scheduleId, index];
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetSchedulesEvent extends WaterHeaterEvent {
|
||||||
|
final String uuid;
|
||||||
|
final String category;
|
||||||
|
|
||||||
|
const GetSchedulesEvent({
|
||||||
|
required this.uuid,
|
||||||
|
required this.category,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [uuid, category];
|
||||||
}
|
}
|
||||||
|
|
||||||
class InitializeAddScheduleEvent extends WaterHeaterEvent {
|
class InitializeAddScheduleEvent extends WaterHeaterEvent {
|
||||||
|
@ -13,14 +13,24 @@ final class WaterHeaterInitial extends WaterHeaterState {}
|
|||||||
|
|
||||||
final class WaterHeaterLoadingState extends WaterHeaterState {}
|
final class WaterHeaterLoadingState extends WaterHeaterState {}
|
||||||
|
|
||||||
|
final class ScheduleLoadingState extends WaterHeaterState {}
|
||||||
|
|
||||||
class WaterHeaterDeviceStatusLoaded extends WaterHeaterState {
|
class WaterHeaterDeviceStatusLoaded extends WaterHeaterState {
|
||||||
final WaterHeaterStatusModel status;
|
final WaterHeaterStatusModel status;
|
||||||
final ScheduleModes? scheduleMode;
|
final ScheduleModes? scheduleMode;
|
||||||
final int? hours;
|
|
||||||
final int? minutes;
|
// Countdown-specific
|
||||||
final bool? isActive;
|
final int? countdownHours;
|
||||||
|
final int? countdownMinutes;
|
||||||
final Duration? countdownRemaining;
|
final Duration? countdownRemaining;
|
||||||
final List<ScheduleEntry> schedules;
|
final bool? isCountdownActive;
|
||||||
|
|
||||||
|
// Inching-specific
|
||||||
|
final int? inchingHours;
|
||||||
|
final int? inchingMinutes;
|
||||||
|
final bool? isInchingActive;
|
||||||
|
|
||||||
|
final List<ScheduleModel> schedules;
|
||||||
final List<bool> selectedDays;
|
final List<bool> selectedDays;
|
||||||
final TimeOfDay? selectedTime;
|
final TimeOfDay? selectedTime;
|
||||||
final bool functionOn;
|
final bool functionOn;
|
||||||
@ -29,10 +39,13 @@ class WaterHeaterDeviceStatusLoaded extends WaterHeaterState {
|
|||||||
const WaterHeaterDeviceStatusLoaded(
|
const WaterHeaterDeviceStatusLoaded(
|
||||||
this.status, {
|
this.status, {
|
||||||
this.scheduleMode,
|
this.scheduleMode,
|
||||||
this.hours,
|
this.countdownHours,
|
||||||
this.minutes,
|
this.countdownMinutes,
|
||||||
this.isActive,
|
|
||||||
this.countdownRemaining,
|
this.countdownRemaining,
|
||||||
|
this.isCountdownActive,
|
||||||
|
this.inchingHours,
|
||||||
|
this.inchingMinutes,
|
||||||
|
this.isInchingActive,
|
||||||
this.schedules = const [],
|
this.schedules = const [],
|
||||||
this.selectedDays = const [false, false, false, false, false, false, false],
|
this.selectedDays = const [false, false, false, false, false, false, false],
|
||||||
this.selectedTime,
|
this.selectedTime,
|
||||||
@ -44,10 +57,13 @@ class WaterHeaterDeviceStatusLoaded extends WaterHeaterState {
|
|||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
status,
|
status,
|
||||||
scheduleMode,
|
scheduleMode,
|
||||||
hours,
|
countdownHours,
|
||||||
minutes,
|
countdownMinutes,
|
||||||
isActive,
|
|
||||||
countdownRemaining,
|
countdownRemaining,
|
||||||
|
isCountdownActive,
|
||||||
|
inchingHours,
|
||||||
|
inchingMinutes,
|
||||||
|
isInchingActive,
|
||||||
schedules,
|
schedules,
|
||||||
selectedDays,
|
selectedDays,
|
||||||
selectedTime,
|
selectedTime,
|
||||||
@ -58,11 +74,14 @@ class WaterHeaterDeviceStatusLoaded extends WaterHeaterState {
|
|||||||
WaterHeaterDeviceStatusLoaded copyWith({
|
WaterHeaterDeviceStatusLoaded copyWith({
|
||||||
WaterHeaterStatusModel? status,
|
WaterHeaterStatusModel? status,
|
||||||
ScheduleModes? scheduleMode,
|
ScheduleModes? scheduleMode,
|
||||||
int? hours,
|
int? countdownHours,
|
||||||
int? minutes,
|
int? countdownMinutes,
|
||||||
bool? isActive,
|
|
||||||
Duration? countdownRemaining,
|
Duration? countdownRemaining,
|
||||||
List<ScheduleEntry>? schedules,
|
bool? isCountdownActive,
|
||||||
|
int? inchingHours,
|
||||||
|
int? inchingMinutes,
|
||||||
|
bool? isInchingActive,
|
||||||
|
List<ScheduleModel>? schedules,
|
||||||
List<bool>? selectedDays,
|
List<bool>? selectedDays,
|
||||||
TimeOfDay? selectedTime,
|
TimeOfDay? selectedTime,
|
||||||
bool? functionOn,
|
bool? functionOn,
|
||||||
@ -71,10 +90,13 @@ class WaterHeaterDeviceStatusLoaded extends WaterHeaterState {
|
|||||||
return WaterHeaterDeviceStatusLoaded(
|
return WaterHeaterDeviceStatusLoaded(
|
||||||
status ?? this.status,
|
status ?? this.status,
|
||||||
scheduleMode: scheduleMode ?? this.scheduleMode,
|
scheduleMode: scheduleMode ?? this.scheduleMode,
|
||||||
hours: hours ?? this.hours,
|
countdownHours: countdownHours ?? this.countdownHours,
|
||||||
minutes: minutes ?? this.minutes,
|
countdownMinutes: countdownMinutes ?? this.countdownMinutes,
|
||||||
isActive: isActive ?? this.isActive,
|
|
||||||
countdownRemaining: countdownRemaining ?? this.countdownRemaining,
|
countdownRemaining: countdownRemaining ?? this.countdownRemaining,
|
||||||
|
isCountdownActive: isCountdownActive ?? this.isCountdownActive,
|
||||||
|
inchingHours: inchingHours ?? this.inchingHours,
|
||||||
|
inchingMinutes: inchingMinutes ?? this.inchingMinutes,
|
||||||
|
isInchingActive: isInchingActive ?? this.isInchingActive,
|
||||||
schedules: schedules ?? this.schedules,
|
schedules: schedules ?? this.schedules,
|
||||||
selectedDays: selectedDays ?? this.selectedDays,
|
selectedDays: selectedDays ?? this.selectedDays,
|
||||||
selectedTime: selectedTime ?? this.selectedTime,
|
selectedTime: selectedTime ?? this.selectedTime,
|
||||||
|
@ -0,0 +1,246 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.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_model.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||||
|
|
||||||
|
class ScheduleDialogHelper {
|
||||||
|
static void showAddScheduleDialog(BuildContext context,
|
||||||
|
{ScheduleModel? schedule, int? index, bool? isEdit}) {
|
||||||
|
final bloc = context.read<WaterHeaterBloc>();
|
||||||
|
|
||||||
|
if (schedule != null) {
|
||||||
|
final time = _convertStringToTimeOfDay(schedule.time);
|
||||||
|
final selectedDays = _convertDaysStringToBooleans(schedule.days);
|
||||||
|
|
||||||
|
bloc.add(InitializeAddScheduleEvent(
|
||||||
|
selectedTime: time,
|
||||||
|
selectedDays: selectedDays,
|
||||||
|
functionOn: schedule.function.value,
|
||||||
|
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) {
|
||||||
|
return BlocProvider.value(
|
||||||
|
value: bloc,
|
||||||
|
child: BlocBuilder<WaterHeaterBloc, WaterHeaterState>(
|
||||||
|
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: isEdit == true
|
||||||
|
? null
|
||||||
|
: () 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(height: 16),
|
||||||
|
_buildDayCheckboxes(context, state.selectedDays,
|
||||||
|
isEdit: isEdit),
|
||||||
|
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: 200,
|
||||||
|
child: DefaultButton(
|
||||||
|
height: 40,
|
||||||
|
onPressed: () {
|
||||||
|
if (state.selectedTime != null) {
|
||||||
|
if (state.isEditing && index != null) {
|
||||||
|
bloc.add(UpdateScheduleEntryEvent(
|
||||||
|
index: index,
|
||||||
|
deviceId: state.status.uuid,
|
||||||
|
category: 'kg',
|
||||||
|
functionOn: state.functionOn,
|
||||||
|
scheduleId: state.schedules[index].scheduleId,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
bloc.add(AddScheduleEvent(
|
||||||
|
category: 'kg',
|
||||||
|
time: state.selectedTime!,
|
||||||
|
selectedDays: state.selectedDays,
|
||||||
|
functionOn: state.functionOn,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor: ColorsManager.primaryColor,
|
||||||
|
child: const Text('Save'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const SizedBox();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TimeOfDay _convertStringToTimeOfDay(String timeString) {
|
||||||
|
final DateTime dateTime = DateTime.parse(timeString);
|
||||||
|
return TimeOfDay(hour: dateTime.hour, minute: dateTime.minute);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<bool> _convertDaysStringToBooleans(List<String> selectedDays) {
|
||||||
|
final daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||||||
|
List<bool> daysBoolean = List.filled(7, false);
|
||||||
|
|
||||||
|
for (int i = 0; i < daysOfWeek.length; i++) {
|
||||||
|
if (selectedDays.contains(daysOfWeek[i])) {
|
||||||
|
daysBoolean[i] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return daysBoolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Widget _buildDayCheckboxes(
|
||||||
|
BuildContext context, List<bool> selectedDays,
|
||||||
|
{bool? isEdit}) {
|
||||||
|
final dayLabels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
children: List.generate(7, (index) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Checkbox(
|
||||||
|
value: selectedDays[index],
|
||||||
|
onChanged: isEdit == true
|
||||||
|
? null
|
||||||
|
: (bool? value) {
|
||||||
|
context
|
||||||
|
.read<WaterHeaterBloc>()
|
||||||
|
.add(UpdateSelectedDayEvent(index, value!));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Text(dayLabels[index]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Widget _buildFunctionSwitch(BuildContext context, bool isOn) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Function:',
|
||||||
|
style: context.textTheme.bodySmall!
|
||||||
|
.copyWith(color: ColorsManager.grayColor),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Radio<bool>(
|
||||||
|
value: true,
|
||||||
|
groupValue: isOn,
|
||||||
|
onChanged: (bool? value) {
|
||||||
|
context
|
||||||
|
.read<WaterHeaterBloc>()
|
||||||
|
.add(const UpdateFunctionOnEvent(true));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Text('On'),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Radio<bool>(
|
||||||
|
value: false,
|
||||||
|
groupValue: isOn,
|
||||||
|
onChanged: (bool? value) {
|
||||||
|
context
|
||||||
|
.read<WaterHeaterBloc>()
|
||||||
|
.add(const UpdateFunctionOnEvent(false));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Text('Off'),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +1,19 @@
|
|||||||
import 'package:flutter/material.dart';
|
// import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class ScheduleEntry {
|
// class ScheduleEntry {
|
||||||
final List<bool> selectedDays;
|
// final List<bool> selectedDays;
|
||||||
final TimeOfDay time;
|
// final TimeOfDay time;
|
||||||
final bool functionOn;
|
// final bool functionOn;
|
||||||
final String category;
|
// final String category;
|
||||||
|
|
||||||
ScheduleEntry({
|
// ScheduleEntry({
|
||||||
required this.selectedDays,
|
// required this.selectedDays,
|
||||||
required this.time,
|
// required this.time,
|
||||||
required this.functionOn,
|
// required this.functionOn,
|
||||||
required this.category,
|
// required this.category,
|
||||||
});
|
// });
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
String toString() =>
|
// String toString() =>
|
||||||
'ScheduleEntry(selectedDays: $selectedDays, time: $time, functionOn: $functionOn)';
|
// 'ScheduleEntry(selectedDays: $selectedDays, time: $time, functionOn: $functionOn)';
|
||||||
}
|
// }
|
@ -1,49 +1,27 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
/*
|
|
||||||
{
|
|
||||||
"category": "kg",
|
|
||||||
"time": "2024-09-22T10:31:54Z",
|
|
||||||
"function": {
|
|
||||||
"code": "switch_1",
|
|
||||||
"value": true
|
|
||||||
},
|
|
||||||
"days": [
|
|
||||||
"Sun"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
class ScheduleModel {
|
class ScheduleModel {
|
||||||
|
final String scheduleId;
|
||||||
final String category;
|
final String category;
|
||||||
final String time;
|
final String time;
|
||||||
final Status function;
|
final Status function;
|
||||||
final List<String> days;
|
final List<String> days;
|
||||||
|
final TimeOfDay? timeOfDay;
|
||||||
|
final List<bool>? selectedDays;
|
||||||
|
|
||||||
ScheduleModel({
|
ScheduleModel({
|
||||||
required this.category,
|
required this.category,
|
||||||
required this.time,
|
required this.time,
|
||||||
required this.function,
|
required this.function,
|
||||||
required this.days,
|
required this.days,
|
||||||
|
this.timeOfDay,
|
||||||
|
this.selectedDays,
|
||||||
|
this.scheduleId = '',
|
||||||
});
|
});
|
||||||
|
|
||||||
ScheduleModel copyWith({
|
|
||||||
String? category,
|
|
||||||
String? time,
|
|
||||||
Status? function,
|
|
||||||
List<String>? days,
|
|
||||||
}) {
|
|
||||||
return ScheduleModel(
|
|
||||||
category: category ?? this.category,
|
|
||||||
time: time ?? this.time,
|
|
||||||
function: function ?? this.function,
|
|
||||||
days: days ?? this.days,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap() {
|
Map<String, dynamic> toMap() {
|
||||||
return {
|
return {
|
||||||
'category': category,
|
'category': category,
|
||||||
@ -55,10 +33,15 @@ class ScheduleModel {
|
|||||||
|
|
||||||
factory ScheduleModel.fromMap(Map<String, dynamic> map) {
|
factory ScheduleModel.fromMap(Map<String, dynamic> map) {
|
||||||
return ScheduleModel(
|
return ScheduleModel(
|
||||||
|
scheduleId: map['scheduleId'] ?? '',
|
||||||
category: map['category'] ?? '',
|
category: map['category'] ?? '',
|
||||||
time: map['time'] ?? '',
|
time: map['time'] ?? '',
|
||||||
function: Status.fromMap(map['function']),
|
function: Status.fromMap(map['function']),
|
||||||
days: List<String>.from(map['days']),
|
days: List<String>.from(map['days']),
|
||||||
|
timeOfDay:
|
||||||
|
parseTimeOfDay(map['time']),
|
||||||
|
selectedDays:
|
||||||
|
parseSelectedDays(map['days']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,9 +50,54 @@ class ScheduleModel {
|
|||||||
factory ScheduleModel.fromJson(String source) =>
|
factory ScheduleModel.fromJson(String source) =>
|
||||||
ScheduleModel.fromMap(json.decode(source));
|
ScheduleModel.fromMap(json.decode(source));
|
||||||
|
|
||||||
|
ScheduleModel copyWith({
|
||||||
|
String? category,
|
||||||
|
String? time,
|
||||||
|
Status? function,
|
||||||
|
List<String>? days,
|
||||||
|
TimeOfDay? timeOfDay,
|
||||||
|
List<bool>? selectedDays,
|
||||||
|
String? scheduleId,
|
||||||
|
}) {
|
||||||
|
return ScheduleModel(
|
||||||
|
category: category ?? this.category,
|
||||||
|
time: time ?? this.time,
|
||||||
|
function: function ?? this.function,
|
||||||
|
days: days ?? this.days,
|
||||||
|
timeOfDay: timeOfDay ?? this.timeOfDay,
|
||||||
|
selectedDays: selectedDays ?? this.selectedDays,
|
||||||
|
scheduleId: scheduleId ?? this.scheduleId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TimeOfDay? parseTimeOfDay(String isoTime) {
|
||||||
|
try {
|
||||||
|
final dateTime = DateTime.parse(isoTime);
|
||||||
|
return TimeOfDay(hour: dateTime.hour, minute: dateTime.minute);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<bool> parseSelectedDays(List<String> days) {
|
||||||
|
const allDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||||
|
return allDays.map((day) => days.contains(day)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> convertSelectedDaysToStrings(List<bool> selectedDays) {
|
||||||
|
const allDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||||
|
List<String> result = [];
|
||||||
|
for (int i = 0; i < selectedDays.length; i++) {
|
||||||
|
if (selectedDays[i]) {
|
||||||
|
result.add(allDays[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SendSchedule(category: $category, time: $time, function: $function, days: $days)';
|
return 'ScheduleModel(category: $category, time: $time, function: $function, days: $days, timeOfDay: $timeOfDay, selectedDays: $selectedDays)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -80,7 +108,9 @@ class ScheduleModel {
|
|||||||
other.category == category &&
|
other.category == category &&
|
||||||
other.time == time &&
|
other.time == time &&
|
||||||
other.function == function &&
|
other.function == function &&
|
||||||
listEquals(other.days, days);
|
listEquals(other.days, days) &&
|
||||||
|
timeOfDay == other.timeOfDay &&
|
||||||
|
listEquals(other.selectedDays, selectedDays);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -88,6 +118,8 @@ class ScheduleModel {
|
|||||||
return category.hashCode ^
|
return category.hashCode ^
|
||||||
time.hashCode ^
|
time.hashCode ^
|
||||||
function.hashCode ^
|
function.hashCode ^
|
||||||
days.hashCode;
|
days.hashCode ^
|
||||||
|
timeOfDay.hashCode ^
|
||||||
|
selectedDays.hashCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.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_model.dart';
|
||||||
|
|
||||||
enum ScheduleModes { countdown, schedule, circulate, inching }
|
enum ScheduleModes { countdown, schedule, circulate, inching }
|
||||||
|
|
||||||
@ -8,11 +9,14 @@ class WaterHeaterStatusModel extends Equatable {
|
|||||||
final bool heaterSwitch;
|
final bool heaterSwitch;
|
||||||
final int countdownHours;
|
final int countdownHours;
|
||||||
final int countdownMinutes;
|
final int countdownMinutes;
|
||||||
|
final int inchingHours;
|
||||||
|
final int inchingMinutes;
|
||||||
final ScheduleModes scheduleMode;
|
final ScheduleModes scheduleMode;
|
||||||
final String relayStatus;
|
final String relayStatus;
|
||||||
final String cycleTiming;
|
final String cycleTiming;
|
||||||
|
final List<ScheduleModel> schedules;
|
||||||
|
|
||||||
WaterHeaterStatusModel({
|
const WaterHeaterStatusModel({
|
||||||
required this.uuid,
|
required this.uuid,
|
||||||
required this.heaterSwitch,
|
required this.heaterSwitch,
|
||||||
required this.countdownHours,
|
required this.countdownHours,
|
||||||
@ -20,6 +24,9 @@ class WaterHeaterStatusModel extends Equatable {
|
|||||||
required this.relayStatus,
|
required this.relayStatus,
|
||||||
required this.cycleTiming,
|
required this.cycleTiming,
|
||||||
required this.scheduleMode,
|
required this.scheduleMode,
|
||||||
|
required this.schedules,
|
||||||
|
this.inchingHours = 0,
|
||||||
|
this.inchingMinutes = 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory WaterHeaterStatusModel.fromJson(String id, List<Status> jsonList) {
|
factory WaterHeaterStatusModel.fromJson(String id, List<Status> jsonList) {
|
||||||
@ -60,6 +67,7 @@ class WaterHeaterStatusModel extends Equatable {
|
|||||||
relayStatus: relayStatus,
|
relayStatus: relayStatus,
|
||||||
cycleTiming: cycleTiming,
|
cycleTiming: cycleTiming,
|
||||||
scheduleMode: scheduleMode,
|
scheduleMode: scheduleMode,
|
||||||
|
schedules: const [],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,6 +79,7 @@ class WaterHeaterStatusModel extends Equatable {
|
|||||||
String? relayStatus,
|
String? relayStatus,
|
||||||
String? cycleTiming,
|
String? cycleTiming,
|
||||||
ScheduleModes? scheduleMode,
|
ScheduleModes? scheduleMode,
|
||||||
|
List<ScheduleModel>? schedules,
|
||||||
}) {
|
}) {
|
||||||
return WaterHeaterStatusModel(
|
return WaterHeaterStatusModel(
|
||||||
uuid: uuid ?? this.uuid,
|
uuid: uuid ?? this.uuid,
|
||||||
@ -80,6 +89,7 @@ class WaterHeaterStatusModel extends Equatable {
|
|||||||
relayStatus: relayStatus ?? this.relayStatus,
|
relayStatus: relayStatus ?? this.relayStatus,
|
||||||
cycleTiming: cycleTiming ?? this.cycleTiming,
|
cycleTiming: cycleTiming ?? this.cycleTiming,
|
||||||
scheduleMode: scheduleMode ?? this.scheduleMode,
|
scheduleMode: scheduleMode ?? this.scheduleMode,
|
||||||
|
schedules: schedules ?? this.schedules,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,16 +29,12 @@ class WaterHeaterDeviceControl extends StatelessWidget
|
|||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
} else if (state is WaterHeaterDeviceStatusLoaded) {
|
} else if (state is WaterHeaterDeviceStatusLoaded) {
|
||||||
return _buildStatusControls(context, state.status);
|
return _buildStatusControls(context, state.status);
|
||||||
}
|
} else if (state is WaterHeaterFailedState ||
|
||||||
// else if (state is WaterHeaterScheduleViewState) {
|
|
||||||
// final status = context.read<WaterHeaterBloc>().deviceStatus;
|
|
||||||
// return _buildStatusControls(context, status);
|
|
||||||
// }
|
|
||||||
else if (state is WaterHeaterFailedState ||
|
|
||||||
state is WaterHeaterBatchFailedState) {
|
state is WaterHeaterBatchFailedState) {
|
||||||
return const Center(child: Text('Error fetching status'));
|
return const Center(child: Text('Error fetching status'));
|
||||||
} else {
|
} else {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const SizedBox(
|
||||||
|
height: 200, child: Center(child: SizedBox()));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.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/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class CountdownModeButtons extends StatelessWidget {
|
||||||
|
final bool isActive;
|
||||||
|
final String deviceId;
|
||||||
|
final int hours;
|
||||||
|
final int minutes;
|
||||||
|
|
||||||
|
const CountdownModeButtons({
|
||||||
|
super.key,
|
||||||
|
required this.isActive,
|
||||||
|
required this.deviceId,
|
||||||
|
required this.hours,
|
||||||
|
required this.minutes,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: DefaultButton(
|
||||||
|
height: 40,
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
backgroundColor: ColorsManager.boxColor,
|
||||||
|
child: Text('Cancel', style: context.textTheme.bodyMedium),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
Expanded(
|
||||||
|
child: isActive
|
||||||
|
? DefaultButton(
|
||||||
|
height: 40,
|
||||||
|
onPressed: () {
|
||||||
|
context
|
||||||
|
.read<WaterHeaterBloc>()
|
||||||
|
.add(StopScheduleEvent(deviceId));
|
||||||
|
context.read<WaterHeaterBloc>().add(
|
||||||
|
ToggleWaterHeaterEvent(
|
||||||
|
deviceId: deviceId,
|
||||||
|
code: 'countdown_1',
|
||||||
|
value: 0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
child: const Text('Stop'),
|
||||||
|
)
|
||||||
|
: DefaultButton(
|
||||||
|
height: 40,
|
||||||
|
onPressed: () {
|
||||||
|
context.read<WaterHeaterBloc>().add(
|
||||||
|
ToggleWaterHeaterEvent(
|
||||||
|
deviceId: deviceId,
|
||||||
|
code: 'countdown_1',
|
||||||
|
value: Duration(hours: hours, minutes: minutes)
|
||||||
|
.inSeconds,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
backgroundColor: ColorsManager.primaryColor,
|
||||||
|
child: const Text('Save'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,152 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.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/water_heater_status_model.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class CountdownInchingView extends StatelessWidget {
|
||||||
|
final WaterHeaterDeviceStatusLoaded state;
|
||||||
|
|
||||||
|
const CountdownInchingView({
|
||||||
|
super.key,
|
||||||
|
required this.state,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final isCountDown =
|
||||||
|
state.scheduleMode?.name == ScheduleModes.countdown.name;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
isCountDown ? 'Countdown:' : 'Inching:',
|
||||||
|
style: context.textTheme.bodySmall!.copyWith(
|
||||||
|
fontSize: 13,
|
||||||
|
color: ColorsManager.grayColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Visibility(
|
||||||
|
visible: !isCountDown,
|
||||||
|
child: const Text(
|
||||||
|
'Once enabled this feature, each time the device is turned on, it will automatically turn off after a preset time.'),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_hourMinutesWheel(context, state),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Row _hourMinutesWheel(
|
||||||
|
BuildContext context, WaterHeaterDeviceStatusLoaded state) {
|
||||||
|
final isCountDown =
|
||||||
|
state.scheduleMode?.name == ScheduleModes.countdown.name;
|
||||||
|
late bool isActive;
|
||||||
|
if (isCountDown &&
|
||||||
|
state.countdownRemaining != null &&
|
||||||
|
state.isCountdownActive == true) {
|
||||||
|
isActive = true;
|
||||||
|
} else if (!isCountDown &&
|
||||||
|
state.countdownRemaining != null &&
|
||||||
|
state.isInchingActive == true) {
|
||||||
|
isActive = true;
|
||||||
|
} else {
|
||||||
|
isActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildPickerColumn(
|
||||||
|
context,
|
||||||
|
'h',
|
||||||
|
isCountDown
|
||||||
|
? (state.countdownHours ?? 0)
|
||||||
|
: (state.inchingHours ?? 0),
|
||||||
|
24, (value) {
|
||||||
|
context.read<WaterHeaterBloc>().add(UpdateScheduleEvent(
|
||||||
|
scheduleMode: state.scheduleMode ?? ScheduleModes.countdown,
|
||||||
|
hours: value,
|
||||||
|
minutes: isCountDown
|
||||||
|
? (state.countdownMinutes ?? 0)
|
||||||
|
: (state.inchingMinutes ?? 0),
|
||||||
|
));
|
||||||
|
}, isActive: isActive),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
_buildPickerColumn(
|
||||||
|
context,
|
||||||
|
'm',
|
||||||
|
isCountDown
|
||||||
|
? (state.countdownMinutes ?? 0)
|
||||||
|
: (state.inchingMinutes ?? 0),
|
||||||
|
60, (value) {
|
||||||
|
context.read<WaterHeaterBloc>().add(UpdateScheduleEvent(
|
||||||
|
scheduleMode: state.scheduleMode ?? ScheduleModes.countdown,
|
||||||
|
hours: isCountDown
|
||||||
|
? (state.countdownHours ?? 0)
|
||||||
|
: (state.inchingHours ?? 0),
|
||||||
|
minutes: value,
|
||||||
|
));
|
||||||
|
}, isActive: isActive),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPickerColumn(
|
||||||
|
BuildContext context,
|
||||||
|
String label,
|
||||||
|
int initialValue,
|
||||||
|
int itemCount,
|
||||||
|
ValueChanged<int> onSelected, {
|
||||||
|
required bool isActive,
|
||||||
|
}) {
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 40,
|
||||||
|
width: 80,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.boxColor,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: ListWheelScrollView.useDelegate(
|
||||||
|
key: ValueKey('$label-$initialValue'),
|
||||||
|
controller: FixedExtentScrollController(
|
||||||
|
initialItem: initialValue,
|
||||||
|
),
|
||||||
|
itemExtent: 40.0,
|
||||||
|
physics: const FixedExtentScrollPhysics(),
|
||||||
|
onSelectedItemChanged: onSelected,
|
||||||
|
childDelegate: ListWheelChildBuilderDelegate(
|
||||||
|
builder: (context, index) {
|
||||||
|
return Center(
|
||||||
|
child: Text(
|
||||||
|
index.toString().padLeft(2, '0'),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
color: isActive ? ColorsManager.grayColor : Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
childCount: itemCount,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorsManager.grayColor,
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.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/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class InchingModeButtons extends StatelessWidget {
|
||||||
|
final bool isActive;
|
||||||
|
final String deviceId;
|
||||||
|
final int hours;
|
||||||
|
final int minutes;
|
||||||
|
|
||||||
|
const InchingModeButtons({
|
||||||
|
Key? key,
|
||||||
|
required this.isActive,
|
||||||
|
required this.deviceId,
|
||||||
|
required this.hours,
|
||||||
|
required this.minutes,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: DefaultButton(
|
||||||
|
height: 40,
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
backgroundColor: ColorsManager.boxColor,
|
||||||
|
child: Text('Cancel', style: context.textTheme.bodyMedium),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
Expanded(
|
||||||
|
child: isActive
|
||||||
|
? DefaultButton(
|
||||||
|
height: 40,
|
||||||
|
onPressed: () {
|
||||||
|
context
|
||||||
|
.read<WaterHeaterBloc>()
|
||||||
|
.add(StopScheduleEvent(deviceId));
|
||||||
|
context.read<WaterHeaterBloc>().add(
|
||||||
|
ToggleWaterHeaterEvent(
|
||||||
|
deviceId: deviceId,
|
||||||
|
code: 'switch_inching',
|
||||||
|
value: 0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
child: const Text('Stop'),
|
||||||
|
)
|
||||||
|
: DefaultButton(
|
||||||
|
height: 40,
|
||||||
|
onPressed: () {
|
||||||
|
context.read<WaterHeaterBloc>().add(
|
||||||
|
ToggleWaterHeaterEvent(
|
||||||
|
deviceId: deviceId,
|
||||||
|
code: 'switch_inching',
|
||||||
|
value: Duration(hours: hours, minutes: minutes)
|
||||||
|
.inSeconds,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
backgroundColor: ColorsManager.primaryColor,
|
||||||
|
child: const Text('Save'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,15 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.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/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/helper/add_schedule_dialog_helper.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.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/pages/device_managment/water_heater/widgets/count_down_button.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/pages/device_managment/water_heater/widgets/count_down_inching_view.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/pages/device_managment/water_heater/widgets/inching_mode_buttons.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/water_heater/widgets/schedule_header.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/water_heater/widgets/schedule_managment_ui.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/water_heater/widgets/schedule_mode_buttons.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/water_heater/widgets/schedule_mode_selector.dart';
|
||||||
|
|
||||||
class BuildScheduleView extends StatefulWidget {
|
class BuildScheduleView extends StatefulWidget {
|
||||||
const BuildScheduleView({super.key, required this.status});
|
const BuildScheduleView({super.key, required this.status});
|
||||||
@ -15,7 +17,7 @@ class BuildScheduleView extends StatefulWidget {
|
|||||||
final WaterHeaterStatusModel status;
|
final WaterHeaterStatusModel status;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_BuildScheduleViewState createState() => _BuildScheduleViewState();
|
State<BuildScheduleView> createState() => _BuildScheduleViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BuildScheduleViewState extends State<BuildScheduleView> {
|
class _BuildScheduleViewState extends State<BuildScheduleView> {
|
||||||
@ -42,20 +44,50 @@ class _BuildScheduleViewState extends State<BuildScheduleView> {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_scheduleHeader(context),
|
const ScheduleHeader(),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
_buildScheduleModeSelector(context, state),
|
ScheduleModeSelector(state: state),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
if (state.scheduleMode == ScheduleModes.schedule)
|
if (state.scheduleMode == ScheduleModes.schedule)
|
||||||
_buildScheduleManagementUI(state),
|
ScheduleManagementUI(
|
||||||
|
state: state,
|
||||||
|
onAddSchedule: () =>
|
||||||
|
ScheduleDialogHelper.showAddScheduleDialog(
|
||||||
|
context,
|
||||||
|
schedule: null,
|
||||||
|
index: null,
|
||||||
|
isEdit: false
|
||||||
|
),
|
||||||
|
),
|
||||||
if (state.scheduleMode == ScheduleModes.countdown ||
|
if (state.scheduleMode == ScheduleModes.countdown ||
|
||||||
state.scheduleMode == ScheduleModes.inching)
|
state.scheduleMode == ScheduleModes.inching)
|
||||||
..._buildCountDownAngInchingView(context, state),
|
CountdownInchingView(state: state),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
_buildSaveStopCancelButtons(context, state),
|
if (state.scheduleMode == ScheduleModes.countdown)
|
||||||
|
CountdownModeButtons(
|
||||||
|
isActive: state.isCountdownActive ?? false,
|
||||||
|
deviceId: widget.status.uuid,
|
||||||
|
hours: state.countdownHours ?? 0,
|
||||||
|
minutes: state.countdownMinutes ?? 0,
|
||||||
|
),
|
||||||
|
if (state.scheduleMode == ScheduleModes.inching)
|
||||||
|
InchingModeButtons(
|
||||||
|
isActive: state.isInchingActive ?? false,
|
||||||
|
deviceId: widget.status.uuid,
|
||||||
|
hours: state.inchingHours ?? 0,
|
||||||
|
minutes: state.inchingMinutes ?? 0,
|
||||||
|
),
|
||||||
|
if (state.scheduleMode != ScheduleModes.countdown &&
|
||||||
|
state.scheduleMode != ScheduleModes.inching)
|
||||||
|
ScheduleModeButtons(
|
||||||
|
onSave: () {},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (state is WaterHeaterLoadingState) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -65,673 +97,4 @@ class _BuildScheduleViewState extends State<BuildScheduleView> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Row _scheduleHeader(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
const SizedBox(),
|
|
||||||
Text(
|
|
||||||
'Scheduling',
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 22,
|
|
||||||
color: ColorsManager.dialogBlueTitle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
width: 25,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.transparent,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(
|
|
||||||
color: Colors.grey,
|
|
||||||
width: 1.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: IconButton(
|
|
||||||
padding: EdgeInsets.all(1),
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.close,
|
|
||||||
color: Colors.grey,
|
|
||||||
size: 18,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildScheduleModeSelector(
|
|
||||||
BuildContext context, WaterHeaterDeviceStatusLoaded state) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Type:',
|
|
||||||
style: context.textTheme.bodySmall!.copyWith(
|
|
||||||
fontSize: 13,
|
|
||||||
color: ColorsManager.grayColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
_buildRadioTile(
|
|
||||||
context, 'Countdown', ScheduleModes.countdown, state),
|
|
||||||
_buildRadioTile(context, 'Schedule', ScheduleModes.schedule, state),
|
|
||||||
_buildRadioTile(
|
|
||||||
context, 'Circulate', ScheduleModes.circulate, state),
|
|
||||||
_buildRadioTile(context, 'Inching', ScheduleModes.inching, state),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildRadioTile(BuildContext context, String label, ScheduleModes mode,
|
|
||||||
WaterHeaterDeviceStatusLoaded state) {
|
|
||||||
return Flexible(
|
|
||||||
child: ListTile(
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
title: Text(
|
|
||||||
label,
|
|
||||||
style: context.textTheme.bodySmall!.copyWith(
|
|
||||||
fontSize: 13,
|
|
||||||
color: ColorsManager.blackColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
leading: Radio<ScheduleModes>(
|
|
||||||
value: mode,
|
|
||||||
groupValue: state.scheduleMode,
|
|
||||||
onChanged: (ScheduleModes? value) {
|
|
||||||
if (value != null) {
|
|
||||||
context.read<WaterHeaterBloc>().add(UpdateScheduleEvent(
|
|
||||||
scheduleMode: value,
|
|
||||||
hours: state.hours ?? 0,
|
|
||||||
minutes: state.minutes ?? 0,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildScheduleManagementUI(WaterHeaterDeviceStatusLoaded state) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: 170,
|
|
||||||
height: 40,
|
|
||||||
child: DefaultButton(
|
|
||||||
borderColor: ColorsManager.boxColor,
|
|
||||||
padding: 2,
|
|
||||||
backgroundColor: ColorsManager.graysColor,
|
|
||||||
borderRadius: 15,
|
|
||||||
onPressed: () =>
|
|
||||||
_showAddScheduleDialog(context, schedule: null, index: null),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.add, color: ColorsManager.primaryColor),
|
|
||||||
Text(
|
|
||||||
' Add new schedule',
|
|
||||||
style: context.textTheme.bodySmall!.copyWith(
|
|
||||||
color: ColorsManager.blackColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
_buildScheduleTable(state),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildScheduleTable(WaterHeaterDeviceStatusLoaded state) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
Table(
|
|
||||||
border: TableBorder.all(
|
|
||||||
color: ColorsManager.graysColor,
|
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(20), topRight: Radius.circular(20)),
|
|
||||||
),
|
|
||||||
children: [
|
|
||||||
TableRow(
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: ColorsManager.boxColor,
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(20),
|
|
||||||
topRight: Radius.circular(20),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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)),
|
|
||||||
),
|
|
||||||
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,
|
|
||||||
color: ColorsManager.blueColor)
|
|
||||||
: const Icon(Icons.radio_button_unchecked)),
|
|
||||||
Center(child: Text(_getSelectedDays(schedule.selectedDays))),
|
|
||||||
Center(child: Text(schedule.time.format(context))),
|
|
||||||
Center(child: Text(schedule.functionOn ? 'On' : 'Off')),
|
|
||||||
Center(
|
|
||||||
child: Wrap(
|
|
||||||
runAlignment: WrapAlignment.center,
|
|
||||||
children: [
|
|
||||||
TextButton(
|
|
||||||
style: TextButton.styleFrom(padding: EdgeInsets.zero),
|
|
||||||
onPressed: () {
|
|
||||||
_showAddScheduleDialog(context,
|
|
||||||
schedule: schedule, index: index);
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'Edit',
|
|
||||||
style: context.textTheme.bodySmall!
|
|
||||||
.copyWith(color: ColorsManager.blueColor),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
style: TextButton.styleFrom(padding: EdgeInsets.zero),
|
|
||||||
onPressed: () {
|
|
||||||
context
|
|
||||||
.read<WaterHeaterBloc>()
|
|
||||||
.add(DeleteScheduleEvent(index));
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'Delete',
|
|
||||||
style: context.textTheme.bodySmall!
|
|
||||||
.copyWith(color: ColorsManager.blueColor),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getSelectedDays(List<bool> selectedDays) {
|
|
||||||
final days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
|
||||||
List<String> selectedDaysStr = [];
|
|
||||||
for (int i = 0; i < selectedDays.length; i++) {
|
|
||||||
if (selectedDays[i]) {
|
|
||||||
selectedDaysStr.add(days[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return selectedDaysStr.join(', ');
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildTableHeader(String label) {
|
|
||||||
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,
|
|
||||||
{ScheduleEntry? schedule, int? index}) {
|
|
||||||
final bloc = context.read<WaterHeaterBloc>();
|
|
||||||
|
|
||||||
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) {
|
|
||||||
return BlocProvider.value(
|
|
||||||
value: bloc,
|
|
||||||
child: BlocBuilder<WaterHeaterBloc, WaterHeaterState>(
|
|
||||||
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(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: 200,
|
|
||||||
child: DefaultButton(
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
backgroundColor: ColorsManager.primaryColor,
|
|
||||||
child: const Text('Save'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return const SizedBox();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildDayCheckboxes(BuildContext context, List<bool> 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<WaterHeaterBloc>()
|
|
||||||
.add(UpdateSelectedDayEvent(index, value!));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Text(dayLabels[index]),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildFunctionSwitch(BuildContext context, bool isOn) {
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Function:',
|
|
||||||
style: context.textTheme.bodySmall!
|
|
||||||
.copyWith(color: ColorsManager.grayColor),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Radio<bool>(
|
|
||||||
value: true,
|
|
||||||
groupValue: isOn,
|
|
||||||
onChanged: (bool? value) {
|
|
||||||
context
|
|
||||||
.read<WaterHeaterBloc>()
|
|
||||||
.add(const UpdateFunctionOnEvent(true));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const Text('On'),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Radio<bool>(
|
|
||||||
value: false,
|
|
||||||
groupValue: isOn,
|
|
||||||
onChanged: (bool? value) {
|
|
||||||
context
|
|
||||||
.read<WaterHeaterBloc>()
|
|
||||||
.add(const UpdateFunctionOnEvent(false));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const Text('Off'),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Center _buildSaveStopCancelButtons(
|
|
||||||
BuildContext context, WaterHeaterDeviceStatusLoaded state) {
|
|
||||||
return Center(
|
|
||||||
child: SizedBox(
|
|
||||||
width: 400,
|
|
||||||
height: 50,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: DefaultButton(
|
|
||||||
height: 40,
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
backgroundColor: ColorsManager.boxColor,
|
|
||||||
child: Text(
|
|
||||||
'Cancel',
|
|
||||||
style: context.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 20),
|
|
||||||
Expanded(
|
|
||||||
child:
|
|
||||||
(state.countdownRemaining != null && state.isActive == true)
|
|
||||||
? DefaultButton(
|
|
||||||
height: 40,
|
|
||||||
onPressed: () {
|
|
||||||
late String code;
|
|
||||||
if (state.scheduleMode == ScheduleModes.countdown) {
|
|
||||||
code = 'countdown_1';
|
|
||||||
} else if (state.scheduleMode ==
|
|
||||||
ScheduleModes.inching) {
|
|
||||||
code = 'switch_inching';
|
|
||||||
}
|
|
||||||
context
|
|
||||||
.read<WaterHeaterBloc>()
|
|
||||||
.add(StopScheduleEvent(widget.status.uuid));
|
|
||||||
context.read<WaterHeaterBloc>().add(
|
|
||||||
ToggleWaterHeaterEvent(
|
|
||||||
deviceId: widget.status.uuid,
|
|
||||||
code: code,
|
|
||||||
value: 0,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
child: const Text('Stop'),
|
|
||||||
)
|
|
||||||
: DefaultButton(
|
|
||||||
height: 40,
|
|
||||||
onPressed: () {
|
|
||||||
late String code;
|
|
||||||
if (state.scheduleMode == ScheduleModes.countdown) {
|
|
||||||
code = 'countdown_1';
|
|
||||||
} else if (state.scheduleMode ==
|
|
||||||
ScheduleModes.inching) {
|
|
||||||
code = 'switch_inching';
|
|
||||||
}
|
|
||||||
context.read<WaterHeaterBloc>().add(
|
|
||||||
ToggleWaterHeaterEvent(
|
|
||||||
deviceId: widget.status.uuid,
|
|
||||||
code: code,
|
|
||||||
value: Duration(
|
|
||||||
hours: state.hours ?? 0,
|
|
||||||
minutes: state.minutes ?? 0)
|
|
||||||
.inSeconds,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
backgroundColor: ColorsManager.primaryColor,
|
|
||||||
child: const Text('Save'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> _buildCountDownAngInchingView(
|
|
||||||
BuildContext context, WaterHeaterDeviceStatusLoaded state) {
|
|
||||||
final isCountDown =
|
|
||||||
state.scheduleMode?.name == ScheduleModes.countdown.name;
|
|
||||||
return [
|
|
||||||
Text(
|
|
||||||
isCountDown ? 'Countdown:' : 'Inching:',
|
|
||||||
style: context.textTheme.bodySmall!.copyWith(
|
|
||||||
fontSize: 13,
|
|
||||||
color: ColorsManager.grayColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Visibility(
|
|
||||||
visible: !isCountDown,
|
|
||||||
child: const Text(
|
|
||||||
'Once enabled this feature, each time the device is turned on, it will automatically turn of after a period time as pre-set.'),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_hourMinutesWheel(state, context)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
Row _hourMinutesWheel(
|
|
||||||
WaterHeaterDeviceStatusLoaded state, BuildContext context) {
|
|
||||||
final isActive =
|
|
||||||
(state.countdownRemaining != null && state.isActive == true);
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_buildPickerColumn(context, 'h', state.hours ?? 0, 24, (value) {
|
|
||||||
context.read<WaterHeaterBloc>().add(UpdateScheduleEvent(
|
|
||||||
scheduleMode: state.scheduleMode ?? ScheduleModes.countdown,
|
|
||||||
hours: value,
|
|
||||||
minutes: state.minutes ?? 0,
|
|
||||||
));
|
|
||||||
}, isActive: isActive),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
_buildPickerColumn(context, 'm', state.minutes ?? 0, 60, (value) {
|
|
||||||
context.read<WaterHeaterBloc>().add(UpdateScheduleEvent(
|
|
||||||
scheduleMode: state.scheduleMode ?? ScheduleModes.countdown,
|
|
||||||
hours: state.hours ?? 0,
|
|
||||||
minutes: value,
|
|
||||||
));
|
|
||||||
}, isActive: isActive),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildPickerColumn(BuildContext context, String label,
|
|
||||||
int initialValue, int itemCount, ValueChanged<int> onSelected,
|
|
||||||
{required bool isActive}) {
|
|
||||||
return Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
height: 40,
|
|
||||||
width: 80,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: ColorsManager.boxColor,
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: ListWheelScrollView.useDelegate(
|
|
||||||
key: ValueKey('$label-$initialValue'),
|
|
||||||
controller: FixedExtentScrollController(
|
|
||||||
initialItem: initialValue,
|
|
||||||
),
|
|
||||||
itemExtent: 40.0,
|
|
||||||
physics: const FixedExtentScrollPhysics(),
|
|
||||||
onSelectedItemChanged: onSelected,
|
|
||||||
childDelegate: ListWheelChildBuilderDelegate(
|
|
||||||
builder: (context, index) {
|
|
||||||
return Center(
|
|
||||||
child: Text(
|
|
||||||
index.toString().padLeft(2, '0'),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
color: isActive ? ColorsManager.grayColor : Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
childCount: itemCount,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: ColorsManager.grayColor,
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class ScheduleHeader extends StatelessWidget {
|
||||||
|
const ScheduleHeader({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const SizedBox(),
|
||||||
|
Text(
|
||||||
|
'Scheduling',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 22,
|
||||||
|
color: ColorsManager.dialogBlueTitle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 25,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.transparent,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.grey,
|
||||||
|
width: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: IconButton(
|
||||||
|
padding: const EdgeInsets.all(1),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
color: Colors.grey,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
import 'package:flutter/material.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/widgets/schedule_table.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class ScheduleManagementUI extends StatelessWidget {
|
||||||
|
final WaterHeaterDeviceStatusLoaded state;
|
||||||
|
final Function onAddSchedule;
|
||||||
|
|
||||||
|
const ScheduleManagementUI({
|
||||||
|
super.key,
|
||||||
|
required this.state,
|
||||||
|
required this.onAddSchedule,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 170,
|
||||||
|
height: 40,
|
||||||
|
child: DefaultButton(
|
||||||
|
borderColor: ColorsManager.boxColor,
|
||||||
|
padding: 2,
|
||||||
|
backgroundColor: ColorsManager.graysColor,
|
||||||
|
borderRadius: 15,
|
||||||
|
onPressed: () => onAddSchedule(),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.add, color: ColorsManager.primaryColor),
|
||||||
|
Text(
|
||||||
|
' Add new schedule',
|
||||||
|
style: context.textTheme.bodySmall!.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
ScheduleTableWidget(state: state),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class ScheduleModeButtons extends StatelessWidget {
|
||||||
|
final VoidCallback onSave;
|
||||||
|
|
||||||
|
const ScheduleModeButtons({
|
||||||
|
super.key,
|
||||||
|
required this.onSave,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: DefaultButton(
|
||||||
|
height: 40,
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
backgroundColor: ColorsManager.boxColor,
|
||||||
|
child: Text(
|
||||||
|
'Cancel',
|
||||||
|
style: context.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
Expanded(
|
||||||
|
child: DefaultButton(
|
||||||
|
height: 40,
|
||||||
|
onPressed: onSave,
|
||||||
|
backgroundColor: ColorsManager.primaryColor,
|
||||||
|
child: const Text('Save'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
|
||||||
|
|
||||||
|
class ScheduleModeSelector extends StatelessWidget {
|
||||||
|
final WaterHeaterDeviceStatusLoaded state;
|
||||||
|
|
||||||
|
const ScheduleModeSelector({super.key, required this.state});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Type:',
|
||||||
|
style: context.textTheme.bodySmall!.copyWith(
|
||||||
|
fontSize: 13,
|
||||||
|
color: ColorsManager.grayColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
_buildRadioTile(
|
||||||
|
context, 'Countdown', ScheduleModes.countdown, state),
|
||||||
|
_buildRadioTile(context, 'Schedule', ScheduleModes.schedule, state),
|
||||||
|
_buildRadioTile(
|
||||||
|
context, 'Circulate', ScheduleModes.circulate, state),
|
||||||
|
_buildRadioTile(context, 'Inching', ScheduleModes.inching, state),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRadioTile(BuildContext context, String label, ScheduleModes mode,
|
||||||
|
WaterHeaterDeviceStatusLoaded state) {
|
||||||
|
return Flexible(
|
||||||
|
child: ListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
title: Text(
|
||||||
|
label,
|
||||||
|
style: context.textTheme.bodySmall!.copyWith(
|
||||||
|
fontSize: 13,
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leading: Radio<ScheduleModes>(
|
||||||
|
value: mode,
|
||||||
|
groupValue: state.scheduleMode,
|
||||||
|
onChanged: (ScheduleModes? value) {
|
||||||
|
if (value != null) {
|
||||||
|
if (value == ScheduleModes.countdown) {
|
||||||
|
context.read<WaterHeaterBloc>().add(UpdateScheduleEvent(
|
||||||
|
scheduleMode: value,
|
||||||
|
hours: state.countdownHours ?? 0,
|
||||||
|
minutes: state.countdownMinutes ?? 0,
|
||||||
|
));
|
||||||
|
} else if (value == ScheduleModes.inching) {
|
||||||
|
context.read<WaterHeaterBloc>().add(UpdateScheduleEvent(
|
||||||
|
scheduleMode: value,
|
||||||
|
hours: state.inchingHours ?? 0,
|
||||||
|
minutes: state.inchingMinutes ?? 0,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == ScheduleModes.schedule) {
|
||||||
|
context.read<WaterHeaterBloc>().add(
|
||||||
|
GetSchedulesEvent(
|
||||||
|
category: 'kg',
|
||||||
|
uuid: state.status.uuid,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart';
|
||||||
|
import 'package:syncrow_web/utils/format_date_time.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class ScheduleRowWidget extends StatelessWidget {
|
||||||
|
final ScheduleModel schedule;
|
||||||
|
final int index;
|
||||||
|
final Function onEdit;
|
||||||
|
final Function onDelete;
|
||||||
|
|
||||||
|
const ScheduleRowWidget({
|
||||||
|
super.key,
|
||||||
|
required this.schedule,
|
||||||
|
required this.index,
|
||||||
|
required this.onEdit,
|
||||||
|
required this.onDelete,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Table(
|
||||||
|
border: TableBorder.all(color: ColorsManager.graysColor),
|
||||||
|
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
||||||
|
children: [
|
||||||
|
TableRow(
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: schedule.function.value
|
||||||
|
? const Icon(Icons.radio_button_checked,
|
||||||
|
color: ColorsManager.blueColor)
|
||||||
|
: const Icon(Icons.radio_button_unchecked),
|
||||||
|
),
|
||||||
|
Center(child: Text(_getSelectedDays(schedule.selectedDays ?? []))),
|
||||||
|
Center(child: Text(formatIsoStringToTime(schedule.time))),
|
||||||
|
Center(child: Text(schedule.function.value ? 'On' : 'Off')),
|
||||||
|
Center(
|
||||||
|
child: Wrap(
|
||||||
|
runAlignment: WrapAlignment.center,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(padding: EdgeInsets.zero),
|
||||||
|
onPressed: () => onEdit(),
|
||||||
|
child: const Text(
|
||||||
|
'Edit',
|
||||||
|
style: TextStyle(color: ColorsManager.blueColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(padding: EdgeInsets.zero),
|
||||||
|
onPressed: () => onDelete(),
|
||||||
|
child: const Text(
|
||||||
|
'Delete',
|
||||||
|
style: TextStyle(color: ColorsManager.blueColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getSelectedDays(List<bool> selectedDays) {
|
||||||
|
final days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||||||
|
List<String> selectedDaysStr = [];
|
||||||
|
for (int i = 0; i < selectedDays.length; i++) {
|
||||||
|
if (selectedDays[i]) {
|
||||||
|
selectedDaysStr.add(days[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selectedDaysStr.join(', ');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,198 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.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_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';
|
||||||
|
import 'package:syncrow_web/utils/format_date_time.dart';
|
||||||
|
|
||||||
|
import '../helper/add_schedule_dialog_helper.dart';
|
||||||
|
|
||||||
|
class ScheduleTableWidget extends StatelessWidget {
|
||||||
|
final WaterHeaterDeviceStatusLoaded state;
|
||||||
|
|
||||||
|
const ScheduleTableWidget({
|
||||||
|
super.key,
|
||||||
|
required this.state,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Table(
|
||||||
|
border: TableBorder.all(
|
||||||
|
color: ColorsManager.graysColor,
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(20), topRight: Radius.circular(20)),
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
TableRow(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: ColorsManager.boxColor,
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(20),
|
||||||
|
topRight: Radius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
_buildTableHeader('Active'),
|
||||||
|
_buildTableHeader('Days'),
|
||||||
|
_buildTableHeader('Time'),
|
||||||
|
_buildTableHeader('Function'),
|
||||||
|
_buildTableHeader('Action'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
BlocBuilder<WaterHeaterBloc, WaterHeaterState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state is ScheduleLoadingState) {
|
||||||
|
return const SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: Center(child: CircularProgressIndicator()));
|
||||||
|
}
|
||||||
|
if (state is WaterHeaterDeviceStatusLoaded &&
|
||||||
|
state.schedules.isEmpty) {
|
||||||
|
return _buildEmptyState(context);
|
||||||
|
} else if (state is WaterHeaterDeviceStatusLoaded) {
|
||||||
|
return Container(
|
||||||
|
height: 200,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: ColorsManager.graysColor),
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
bottomLeft: Radius.circular(20),
|
||||||
|
bottomRight: Radius.circular(20)),
|
||||||
|
),
|
||||||
|
child: _buildTableBody(state, context));
|
||||||
|
}
|
||||||
|
return const SizedBox();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEmptyState(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
height: 200,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: ColorsManager.graysColor),
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)),
|
||||||
|
),
|
||||||
|
child: 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, BuildContext context) {
|
||||||
|
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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTableHeader(String label) {
|
||||||
|
return TableCell(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: ColorsManager.grayColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TableRow _buildScheduleRow(
|
||||||
|
ScheduleModel schedule, int index, BuildContext context) {
|
||||||
|
return TableRow(
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: schedule.function.value
|
||||||
|
? const Icon(Icons.radio_button_checked,
|
||||||
|
color: ColorsManager.blueColor)
|
||||||
|
: const Icon(Icons.radio_button_unchecked)),
|
||||||
|
Center(
|
||||||
|
child: Text(_getSelectedDays(
|
||||||
|
ScheduleModel.parseSelectedDays(schedule.days)))),
|
||||||
|
Center(child: Text(formatIsoStringToTime(schedule.time))),
|
||||||
|
Center(child: Text(schedule.function.value ? 'On' : 'Off')),
|
||||||
|
Center(
|
||||||
|
child: Wrap(
|
||||||
|
runAlignment: WrapAlignment.center,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(padding: EdgeInsets.zero),
|
||||||
|
onPressed: () {
|
||||||
|
ScheduleDialogHelper.showAddScheduleDialog(context,
|
||||||
|
schedule: schedule, index: index, isEdit: true);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'Edit',
|
||||||
|
style: context.textTheme.bodySmall!
|
||||||
|
.copyWith(color: ColorsManager.blueColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(padding: EdgeInsets.zero),
|
||||||
|
onPressed: () {
|
||||||
|
context.read<WaterHeaterBloc>().add(DeleteScheduleEvent(
|
||||||
|
index: index,
|
||||||
|
scheduleId: schedule.scheduleId,
|
||||||
|
));
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'Delete',
|
||||||
|
style: context.textTheme.bodySmall!
|
||||||
|
.copyWith(color: ColorsManager.blueColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getSelectedDays(List<bool> selectedDays) {
|
||||||
|
final days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||||||
|
List<String> selectedDaysStr = [];
|
||||||
|
for (int i = 0; i < selectedDays.length; i++) {
|
||||||
|
if (selectedDays[i]) {
|
||||||
|
selectedDaysStr.add(days[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selectedDaysStr.join(', ');
|
||||||
|
}
|
||||||
|
}
|
@ -195,6 +195,29 @@ class DevicesManagementApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<ScheduleModel>> getDeviceSchedules(
|
||||||
|
String uuid, String category) async {
|
||||||
|
try {
|
||||||
|
final response = await HTTPService().get(
|
||||||
|
path: ApiEndpoints.scheduleByDeviceId
|
||||||
|
.replaceAll('{deviceUuid}', uuid)
|
||||||
|
.replaceAll('{category}', category),
|
||||||
|
showServerMessage: true,
|
||||||
|
expectedResponseModel: (json) {
|
||||||
|
List<ScheduleModel> schedules = [];
|
||||||
|
for (var schedule in json['schedules']) {
|
||||||
|
schedules.add(ScheduleModel.fromJson(schedule));
|
||||||
|
}
|
||||||
|
return schedules;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error fetching $e');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> updateScheduleRecord(
|
Future<bool> updateScheduleRecord(
|
||||||
{required bool enable,
|
{required bool enable,
|
||||||
required String uuid,
|
required String uuid,
|
||||||
|
@ -40,6 +40,8 @@ abstract class ApiEndpoints {
|
|||||||
'/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}';
|
'/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}';
|
||||||
|
|
||||||
static const String scheduleByDeviceId = '/schedule/{deviceUuid}';
|
static const String scheduleByDeviceId = '/schedule/{deviceUuid}';
|
||||||
|
static const String getScheduleByDeviceId =
|
||||||
|
'/schedule/{deviceUuid}?category={category}';
|
||||||
static const String deleteScheduleByDeviceId =
|
static const String deleteScheduleByDeviceId =
|
||||||
'/schedule/{deviceUuid}/{scheduleUuid}';
|
'/schedule/{deviceUuid}/{scheduleUuid}';
|
||||||
static const String updateScheduleByDeviceId =
|
static const String updateScheduleByDeviceId =
|
||||||
|
@ -24,3 +24,8 @@ String formatTimeOfDayToISO(TimeOfDay time, {DateTime? currentDate}) {
|
|||||||
|
|
||||||
return dateTime.toUtc().toIso8601String();
|
return dateTime.toUtc().toIso8601String();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String formatIsoStringToTime(String isoString) {
|
||||||
|
final dateTime = DateTime.parse(isoString);
|
||||||
|
return DateFormat('hh:mm a').format(dateTime);
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user