Refactor schedule components and update imports for garage door and water heater modules

This commit is contained in:
mohammad
2025-06-18 16:27:50 +03:00
parent 5b3152e833
commit db513f916f
30 changed files with 1603 additions and 722 deletions

View File

@ -0,0 +1,557 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
import 'package:syncrow_web/services/control_device_service.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
part 'schedule_event.dart';
part 'schedule_state.dart';
class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
final String deviceId;
ScheduleBloc({
required this.deviceId,
}) : super(ScheduleInitial()) {
on<ScheduleInitializeAddEvent>(_initializeAddSchedule);
on<ScheduleUpdateSelectedTimeEvent>(_updateSelectedTime);
on<ScheduleUpdateSelectedDayEvent>(_updateSelectedDay);
on<ScheduleUpdateFunctionOnEvent>(_updateFunctionOn);
on<ScheduleGetEvent>(_getSchedule);
on<ScheduleAddEvent>(_onAddSchedule);
on<ScheduleEditEvent>(_onEditSchedule);
on<ScheduleUpdateEntryEvent>(_onUpdateSchedule);
on<UpdateScheduleModeEvent>(_onUpdateScheduleMode);
on<UpdateCountdownTimeEvent>(_onUpdateCountdownTime);
on<UpdateInchingTimeEvent>(_onUpdateInchingTime);
on<StartScheduleEvent>(_onStartScheduleEvent);
on<StopScheduleEvent>(_onStopScheduleEvent);
on<ScheduleDecrementCountdownEvent>(_onDecrementCountdown);
on<ScheduleFetchStatusEvent>(_fetchStatus);
on<ScheduleDeleteEvent>(_onDeleteSchedule);
}
Timer? _countdownTimer;
Duration countdownRemaining = Duration.zero;
void _onStopScheduleEvent(
StopScheduleEvent event,
Emitter<ScheduleState> emit,
) {
if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded;
_countdownTimer?.cancel();
if (event.mode == ScheduleModes.countdown) {
emit(currentState.copyWith(
countdownHours: 0,
countdownMinutes: 0,
isCountdownActive: false,
countdownRemaining: Duration.zero,
));
} else if (event.mode == ScheduleModes.inching) {
emit(currentState.copyWith(
inchingHours: 0,
inchingMinutes: 0,
isInchingActive: false,
countdownRemaining: Duration.zero,
));
}
}
}
void _onUpdateScheduleMode(
UpdateScheduleModeEvent event,
Emitter<ScheduleState> emit,
) {
if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded;
emit(currentState.copyWith(
scheduleMode: event.scheduleMode,
countdownRemaining: Duration.zero,
));
}
}
void _onUpdateCountdownTime(
UpdateCountdownTimeEvent event,
Emitter<ScheduleState> emit,
) {
if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded;
emit(currentState.copyWith(
countdownHours: event.hours,
countdownMinutes: event.minutes,
inchingHours: 0,
inchingMinutes: 0,
countdownRemaining: Duration.zero,
));
}
}
void _onUpdateInchingTime(
UpdateInchingTimeEvent event,
Emitter<ScheduleState> emit,
) {
if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded;
emit(currentState.copyWith(
inchingHours: event.hours,
inchingMinutes: event.minutes,
countdownRemaining: Duration.zero,
));
}
}
void _initializeAddSchedule(
ScheduleInitializeAddEvent event,
Emitter<ScheduleState> emit,
) {
if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded;
emit(currentState.copyWith(
selectedTime: event.selectedTime,
selectedDays: event.selectedDays ?? List.filled(7, false),
functionOn: event.functionOn ?? false,
isEditing: event.isEditing,
scheduleMode: event.scheduleMode,
countdownRemaining: Duration.zero,
));
} else {
emit(ScheduleLoaded(
schedules: const [],
selectedTime: event.selectedTime,
selectedDays: event.selectedDays ?? List.filled(7, false),
functionOn: event.functionOn ?? false,
isEditing: event.isEditing,
deviceId: deviceId,
scheduleMode: event.scheduleMode,
countdownHours: 0,
countdownMinutes: 0,
inchingHours: 0,
inchingMinutes: 0,
isCountdownActive: false,
isInchingActive: false,
));
}
}
void _updateSelectedTime(
ScheduleUpdateSelectedTimeEvent event,
Emitter<ScheduleState> emit,
) {
if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded;
emit(currentState.copyWith(
selectedTime: event.selectedTime,
countdownHours: 0,
countdownMinutes: 0,
inchingHours: 0,
inchingMinutes: 0,
countdownRemaining: Duration.zero,
));
}
}
void _updateSelectedDay(
ScheduleUpdateSelectedDayEvent event,
Emitter<ScheduleState> emit,
) {
if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded;
final updatedDays = List<bool>.from(currentState.selectedDays);
updatedDays[event.index] = event.value;
emit(currentState.copyWith(
selectedDays: updatedDays,
countdownHours: 0,
countdownMinutes: 0,
inchingHours: 0,
inchingMinutes: 0,
countdownRemaining: Duration.zero,
));
}
}
void _updateFunctionOn(
ScheduleUpdateFunctionOnEvent event,
Emitter<ScheduleState> emit,
) {
if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded;
emit(currentState.copyWith(
functionOn: event.isOn,
countdownHours: 0,
countdownMinutes: 0,
inchingHours: 0,
inchingMinutes: 0,
countdownRemaining: Duration.zero,
));
}
}
Future<void> _getSchedule(
ScheduleGetEvent event,
Emitter<ScheduleState> emit,
) async {
try {
emit(ScheduleLoading());
final schedules = await DevicesManagementApi().getDeviceSchedules(
deviceId,
event.category,
);
if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded;
emit(currentState.copyWith(
schedules: schedules,
selectedTime: null,
selectedDays: List.filled(7, false),
functionOn: false,
isEditing: false,
countdownRemaining: Duration.zero,
));
} else {
emit(ScheduleLoaded(
schedules: schedules,
selectedTime: null,
selectedDays: List.filled(7, false),
functionOn: false,
isEditing: false,
deviceId: deviceId,
scheduleMode: ScheduleModes.schedule,
countdownHours: 0,
countdownMinutes: 0,
inchingHours: 0,
inchingMinutes: 0,
isCountdownActive: false,
isInchingActive: false,
));
}
} catch (e) {
emit(ScheduleError('Failed to load schedules: $e'));
}
}
Future<void> _onAddSchedule(
ScheduleAddEvent event,
Emitter<ScheduleState> emit,
) async {
try {
if (state is ScheduleLoaded) {
final newSchedule = ScheduleEntry(
category: event.category,
time: event.time,
function: Status(code: 'switch_1', value: event.functionOn),
days: event.selectedDays);
final success = await DevicesManagementApi().addScheduleRecord(
newSchedule,
deviceId,
);
if (success) {
add(const ScheduleGetEvent(category: 'switch_1'));
} else {
emit(const ScheduleError('Failed to add schedule'));
}
}
} catch (e) {
emit(ScheduleError('Failed to add schedule: $e'));
}
}
Future<void> _onEditSchedule(
ScheduleEditEvent event,
Emitter<ScheduleState> emit,
) async {
try {
if (state is ScheduleLoaded) {
final updatedSchedule = ScheduleEntry(
scheduleId: event.scheduleId,
category: event.category,
time: event.time,
function: Status(code: 'switch_1', value: event.functionOn),
days: event.selectedDays,
);
final success = await DevicesManagementApi().editScheduleRecord(
deviceId,
updatedSchedule,
);
if (success) {
add(const ScheduleGetEvent(category: 'switch_1'));
} else {
emit(const ScheduleError('Failed to update schedule'));
}
}
} catch (e) {
emit(ScheduleError('Failed to update schedule: $e'));
}
}
Future<void> _onUpdateSchedule(
ScheduleUpdateEntryEvent event,
Emitter<ScheduleState> emit,
) async {
try {
if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded;
final updatedSchedules = currentState.schedules.map((schedule) {
if (schedule.scheduleId == event.scheduleId) {
return schedule.copyWith(
function: Status(code: 'switch_1', value: event.functionOn),
);
}
return schedule;
}).toList();
final success = await DevicesManagementApi().updateScheduleRecord(
enable: event.enable,
uuid: deviceId,
scheduleId: event.scheduleId,
);
if (success) {
emit(currentState.copyWith(
schedules: updatedSchedules,
countdownHours: 0,
countdownMinutes: 0,
inchingHours: 0,
inchingMinutes: 0,
countdownRemaining: Duration.zero,
));
} else {
emit(const ScheduleError('Failed to update schedule status'));
}
}
} catch (e) {
emit(ScheduleError('Failed to update schedule: $e'));
}
}
Future<void> _onDeleteSchedule(
ScheduleDeleteEvent event,
Emitter<ScheduleState> emit,
) async {
try {
if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded;
final success = await DevicesManagementApi().deleteScheduleRecord(
deviceId,
event.scheduleId,
);
if (success) {
final updatedSchedules = currentState.schedules
.where((s) => s.scheduleId != event.scheduleId)
.toList();
emit(currentState.copyWith(
schedules: updatedSchedules,
countdownHours: 0,
countdownMinutes: 0,
inchingHours: 0,
inchingMinutes: 0,
countdownRemaining: Duration.zero,
));
} else {
emit(const ScheduleError('Failed to delete schedule'));
}
}
} catch (e) {
emit(ScheduleError('Failed to delete schedule: $e'));
}
}
Duration? _currentCountdown;
Future<void> _onStartScheduleEvent(
StartScheduleEvent event,
Emitter<ScheduleState> emit,
) async {
if (state is ScheduleLoaded) {
final totalSeconds =
Duration(hours: event.hours, minutes: event.minutes).inSeconds;
final code = event.mode == ScheduleModes.countdown
? 'countdown_1'
: 'switch_inching';
final currentState = state as ScheduleLoaded;
final duration = Duration(seconds: totalSeconds);
_currentCountdown = duration;
emit(currentState.copyWith(
countdownRemaining: duration,
schedules: currentState.schedules.map((schedule) {
if (schedule.function.code == code) {
return schedule.copyWith(
function: Status(code: code, value: totalSeconds),
);
}
return schedule;
}).toList(),
countdownHours: event.mode == ScheduleModes.countdown ? event.hours : 0,
));
final success = await RemoteControlDeviceService().controlDevice(
deviceUuid: deviceId,
status: Status(
code: code,
value: totalSeconds,
),
);
if (success) {
if (code == 'countdown_1') {
final countdownDuration = Duration(seconds: totalSeconds);
emit(
currentState.copyWith(
countdownHours: countdownDuration.inHours,
countdownMinutes: countdownDuration.inMinutes % 60,
countdownRemaining: countdownDuration,
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 (code == 'switch_inching') {
final inchingDuration = Duration(seconds: totalSeconds);
emit(
currentState.copyWith(
inchingHours: inchingDuration.inHours,
inchingMinutes: inchingDuration.inMinutes % 60,
isInchingActive: true,
countdownRemaining: inchingDuration,
),
);
}
}
}
}
void _startCountdownTimer(
Emitter<ScheduleState> emit,
Duration duration,
) {
_countdownTimer?.cancel();
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (_currentCountdown != null && _currentCountdown! > Duration.zero) {
_currentCountdown = _currentCountdown! - const Duration(seconds: 1);
countdownRemaining = _currentCountdown!;
add(const ScheduleDecrementCountdownEvent());
} else {
timer.cancel();
add(StopScheduleEvent(
mode: _currentCountdown == null
? ScheduleModes.countdown
: ScheduleModes.inching,
deviceId: deviceId,
));
}
});
}
void _onDecrementCountdown(
ScheduleDecrementCountdownEvent event,
Emitter<ScheduleState> emit,
) {
if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded;
emit(currentState.copyWith(
countdownRemaining: countdownRemaining,
));
}
}
@override
Future<void> close() {
_countdownTimer?.cancel();
return super.close();
}
Future<void> _fetchStatus(
ScheduleFetchStatusEvent event,
Emitter<ScheduleState> emit,
) async {
emit(ScheduleLoading());
try {
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
final deviceStatus =
WaterHeaterStatusModel.fromJson(event.deviceId, status.status);
final scheduleMode = deviceStatus.scheduleMode;
final isCountdown = scheduleMode == ScheduleModes.countdown;
final isInching = scheduleMode == ScheduleModes.inching;
Duration? countdownRemaining;
var isCountdownActive = false;
var isInchingActive = false;
if (isCountdown) {
countdownRemaining = Duration(
hours: deviceStatus.countdownHours,
minutes: deviceStatus.countdownMinutes,
);
isCountdownActive = countdownRemaining > Duration.zero;
} else if (isInching) {
isInchingActive = Duration(
hours: deviceStatus.inchingHours,
minutes: deviceStatus.inchingMinutes,
) >
Duration.zero;
}
if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded;
emit(currentState.copyWith(
scheduleMode: scheduleMode,
countdownHours: deviceStatus.countdownHours,
countdownMinutes: deviceStatus.countdownMinutes,
inchingHours: deviceStatus.inchingHours,
inchingMinutes: deviceStatus.inchingMinutes,
isCountdownActive: isCountdownActive,
isInchingActive: isInchingActive,
countdownRemaining: countdownRemaining ?? Duration.zero,
));
} else {
emit(ScheduleLoaded(
schedules: const [],
selectedTime: null,
selectedDays: List.filled(7, false),
functionOn: false,
isEditing: false,
deviceId: deviceId,
scheduleMode: scheduleMode,
countdownHours: deviceStatus.countdownHours,
countdownMinutes: deviceStatus.countdownMinutes,
inchingHours: deviceStatus.inchingHours,
inchingMinutes: deviceStatus.inchingMinutes,
isCountdownActive: isCountdownActive,
isInchingActive: isInchingActive,
countdownRemaining: countdownRemaining ?? Duration.zero,
));
}
if (isCountdownActive && countdownRemaining != null) {
_startCountdownTimer(emit, countdownRemaining);
}
} catch (e) {
emit(ScheduleError('Failed to fetch device status: $e'));
}
}
}

View File

@ -0,0 +1,221 @@
part of 'schedule_bloc.dart';
abstract class ScheduleEvent extends Equatable {
const ScheduleEvent();
}
class ScheduleInitializeAddEvent extends ScheduleEvent {
final bool isEditing;
final ScheduleModes scheduleMode;
final TimeOfDay? selectedTime;
final List<bool>? selectedDays;
final bool? functionOn;
const ScheduleInitializeAddEvent({
required this.isEditing,
required this.scheduleMode,
this.selectedTime,
this.selectedDays,
this.functionOn,
});
@override
List<Object?> get props => [
isEditing,
scheduleMode,
selectedTime,
selectedDays,
functionOn,
];
}
class ScheduleUpdateSelectedTimeEvent extends ScheduleEvent {
final TimeOfDay selectedTime;
const ScheduleUpdateSelectedTimeEvent(this.selectedTime);
@override
List<Object> get props => [selectedTime];
}
class ScheduleUpdateSelectedDayEvent extends ScheduleEvent {
final int index;
final bool value;
const ScheduleUpdateSelectedDayEvent(this.index, this.value);
@override
List<Object> get props => [index, value];
}
class ScheduleUpdateFunctionOnEvent extends ScheduleEvent {
final bool isOn;
const ScheduleUpdateFunctionOnEvent(this.isOn);
@override
List<Object> get props => [isOn];
}
class ScheduleGetEvent extends ScheduleEvent {
final String category;
const ScheduleGetEvent({required this.category});
@override
List<Object> get props => [category];
}
class ScheduleAddEvent extends ScheduleEvent {
final String category;
final String time;
final List<String> selectedDays;
final bool functionOn;
const ScheduleAddEvent({
required this.category,
required this.time,
required this.selectedDays,
required this.functionOn,
});
@override
List<Object> get props => [category, time, selectedDays, functionOn];
}
class ScheduleEditEvent extends ScheduleEvent {
final String scheduleId;
final String category;
final String time;
final List<String> selectedDays;
final bool functionOn;
const ScheduleEditEvent({
required this.scheduleId,
required this.category,
required this.time,
required this.selectedDays,
required this.functionOn,
});
@override
List<Object> get props => [
scheduleId,
category,
time,
selectedDays,
functionOn,
];
}
class ScheduleDeleteEvent extends ScheduleEvent {
final String scheduleId;
const ScheduleDeleteEvent(this.scheduleId);
@override
List<Object> get props => [scheduleId];
}
class ScheduleUpdateEntryEvent extends ScheduleEvent {
final String scheduleId;
final bool functionOn;
final bool enable;
const ScheduleUpdateEntryEvent({
required this.scheduleId,
required this.functionOn,
required this.enable,
});
@override
List<Object> get props => [scheduleId, functionOn, enable];
}
class UpdateScheduleModeEvent extends ScheduleEvent {
final ScheduleModes scheduleMode;
const UpdateScheduleModeEvent({required this.scheduleMode});
@override
List<Object> get props => [scheduleMode];
}
class UpdateCountdownTimeEvent extends ScheduleEvent {
final int hours;
final int minutes;
const UpdateCountdownTimeEvent({
required this.hours,
required this.minutes,
});
@override
List<Object> get props => [hours, minutes];
}
class UpdateInchingTimeEvent extends ScheduleEvent {
final int hours;
final int minutes;
const UpdateInchingTimeEvent({
required this.hours,
required this.minutes,
});
@override
List<Object> get props => [hours, minutes];
}
class StartScheduleEvent extends ScheduleEvent {
final ScheduleModes mode;
final int hours;
final int minutes;
const StartScheduleEvent({
required this.mode,
required this.hours,
required this.minutes,
});
@override
List<Object?> get props => [mode, hours, minutes];
}
class StopScheduleEvent extends ScheduleEvent {
final ScheduleModes mode;
final String deviceId;
const StopScheduleEvent({
required this.mode,
required this.deviceId,
});
@override
List<Object?> get props => [mode, deviceId];
}
class ScheduleDecrementCountdownEvent extends ScheduleEvent {
const ScheduleDecrementCountdownEvent();
@override
List<Object> get props => [];
}
class ScheduleFetchStatusEvent extends ScheduleEvent {
final String deviceId;
const ScheduleFetchStatusEvent(this.deviceId);
@override
List<Object> get props => [deviceId];
}
class DeleteScheduleEvent extends ScheduleEvent {
final String scheduleId;
const DeleteScheduleEvent(this.scheduleId);
@override
List<Object> get props => [scheduleId];
}

View File

@ -0,0 +1,109 @@
part of 'schedule_bloc.dart';
abstract class ScheduleState extends Equatable {
const ScheduleState();
}
class ScheduleInitial extends ScheduleState {
@override
List<Object> get props => [];
}
class ScheduleLoading extends ScheduleState {
@override
List<Object> get props => [];
}
class ScheduleLoaded extends ScheduleState {
final List<ScheduleModel> schedules;
final TimeOfDay? selectedTime;
final List<bool> selectedDays;
final bool functionOn;
final bool isEditing;
final String deviceId;
final int countdownHours;
final int countdownMinutes;
final bool isCountdownActive;
final int inchingHours;
final int inchingMinutes;
final bool isInchingActive;
final ScheduleModes scheduleMode;
final Duration? countdownRemaining;
const ScheduleLoaded({
required this.schedules,
this.selectedTime,
required this.selectedDays,
required this.functionOn,
required this.isEditing,
required this.deviceId,
this.countdownHours = 0,
this.countdownMinutes = 0,
this.isCountdownActive = false,
this.inchingHours = 0,
this.inchingMinutes = 0,
this.isInchingActive = false,
this.scheduleMode = ScheduleModes.countdown,
this.countdownRemaining,
});
ScheduleLoaded copyWith({
List<ScheduleModel>? schedules,
TimeOfDay? selectedTime,
List<bool>? selectedDays,
bool? functionOn,
bool? isEditing,
int? countdownHours,
int? countdownMinutes,
bool? isCountdownActive,
int? inchingHours,
int? inchingMinutes,
bool? isInchingActive,
ScheduleModes? scheduleMode,
Duration? countdownRemaining,
}) {
return ScheduleLoaded(
schedules: schedules ?? this.schedules,
selectedTime: selectedTime ?? this.selectedTime,
selectedDays: selectedDays ?? this.selectedDays,
functionOn: functionOn ?? this.functionOn,
isEditing: isEditing ?? this.isEditing,
deviceId: deviceId,
countdownHours: countdownHours ?? this.countdownHours,
countdownMinutes: countdownMinutes ?? this.countdownMinutes,
isCountdownActive: isCountdownActive ?? this.isCountdownActive,
inchingHours: inchingHours ?? this.inchingHours,
inchingMinutes: inchingMinutes ?? this.inchingMinutes,
isInchingActive: isInchingActive ?? this.isInchingActive,
scheduleMode: scheduleMode ?? this.scheduleMode,
countdownRemaining: countdownRemaining ?? this.countdownRemaining,
);
}
@override
List<Object?> get props => [
schedules,
selectedTime,
selectedDays,
functionOn,
isEditing,
deviceId,
countdownHours,
countdownMinutes,
isCountdownActive,
inchingHours,
inchingMinutes,
isInchingActive,
scheduleMode,
countdownRemaining,
];
}
class ScheduleError extends ScheduleState {
final String error;
const ScheduleError(this.error);
@override
List<Object> get props => [error];
}