import 'package:flutter/material.dart'; // ── Model: WateringSchedule ──────────────────────────────────── class WateringSchedule { final String id; final TimeOfDay time; final String repeat; final bool isEnabled; final String? cron; final int durationS; const WateringSchedule({ required this.id, required this.time, this.repeat = 'Setiap Hari', this.isEnabled = true, this.cron, this.durationS = 30, }); String get formattedTime { final h = time.hour.toString().padLeft(2, '0'); final m = time.minute.toString().padLeft(2, '0'); return '$h.$m'; } WateringSchedule copyWith({ String? id, TimeOfDay? time, String? repeat, bool? isEnabled, String? cron, int? durationS, }) { return WateringSchedule( id: id ?? this.id, time: time ?? this.time, repeat: repeat ?? this.repeat, isEnabled: isEnabled ?? this.isEnabled, cron: cron ?? this.cron, durationS: durationS ?? this.durationS, ); } @override bool operator ==(Object other) => identical(this, other) || (other is WateringSchedule && runtimeType == other.runtimeType && id == other.id); @override int get hashCode => id.hashCode; } // ── Enum: DeviceMode ───────────────────────────────────────────────── enum DeviceMode { auto_, manual, offline } extension DeviceModeX on DeviceMode { String get apiValue { switch (this) { case DeviceMode.auto_: return 'auto'; case DeviceMode.manual: return 'manual'; case DeviceMode.offline: return 'offline'; } } String get label { switch (this) { case DeviceMode.auto_: return 'AUTO'; case DeviceMode.manual: return 'MANUAL'; case DeviceMode.offline: return 'OFFLINE'; } } static DeviceMode fromString(String s) { final str = s.toLowerCase(); if (str.startsWith('manual')) { return DeviceMode.manual; } else if (str.startsWith('offline')) { return DeviceMode.offline; } else { return DeviceMode.auto_; } } } // ── State: ControlState ──────────────────────────────────────────── class ControlState { final bool isLoading; final bool isPumpOn; final double suhuLimit; final double kelembapanLimit; final List schedules; final String? errorMessage; final String? successMessage; final int? activePumpDuration; final DateTime? pumpTurnedOnAt; final DeviceMode deviceMode; final bool isModeLoading; const ControlState({ this.isLoading = false, this.isPumpOn = false, this.suhuLimit = 30, this.kelembapanLimit = 75, this.schedules = const [], this.errorMessage, this.successMessage, this.activePumpDuration, this.pumpTurnedOnAt, this.deviceMode = DeviceMode.auto_, this.isModeLoading = false, }); factory ControlState.initial() => const ControlState( isLoading: true, // Saat mulai aplikasi, data sedang di-fetch schedules: [], ); ControlState copyWith({ bool? isLoading, bool? isPumpOn, double? suhuLimit, double? kelembapanLimit, List? schedules, String? errorMessage, String? successMessage, int? activePumpDuration, DateTime? pumpTurnedOnAt, DeviceMode? deviceMode, bool? isModeLoading, bool clearError = false, bool clearSuccess = false, bool clearTimer = false, }) { return ControlState( isLoading: isLoading ?? this.isLoading, isPumpOn: isPumpOn ?? this.isPumpOn, suhuLimit: suhuLimit ?? this.suhuLimit, kelembapanLimit: kelembapanLimit ?? this.kelembapanLimit, schedules: schedules ?? this.schedules, errorMessage: clearError ? null : (errorMessage ?? this.errorMessage), successMessage: clearSuccess ? null : (successMessage ?? this.successMessage), activePumpDuration: clearTimer ? null : (activePumpDuration ?? this.activePumpDuration), pumpTurnedOnAt: clearTimer ? null : (pumpTurnedOnAt ?? this.pumpTurnedOnAt), deviceMode: deviceMode ?? this.deviceMode, isModeLoading: isModeLoading ?? this.isModeLoading, ); } @override bool operator ==(Object other) => identical(this, other) || (other is ControlState && isLoading == other.isLoading && isPumpOn == other.isPumpOn && suhuLimit == other.suhuLimit && kelembapanLimit == other.kelembapanLimit && errorMessage == other.errorMessage && successMessage == other.successMessage && activePumpDuration == other.activePumpDuration && pumpTurnedOnAt == other.pumpTurnedOnAt && schedules == other.schedules); @override int get hashCode => isLoading.hashCode ^ isPumpOn.hashCode ^ suhuLimit.hashCode ^ kelembapanLimit.hashCode ^ errorMessage.hashCode ^ successMessage.hashCode ^ activePumpDuration.hashCode ^ pumpTurnedOnAt.hashCode ^ schedules.hashCode; }