import 'package:flutter/material.dart'; // ── Model: WateringSchedule ──────────────────────────────────── /// Model data untuk jadwal penyiraman otomatis yang tersimpan di sistem. class WateringSchedule { /// ID unik jadwal yang digenerate oleh database. final String id; /// Jam penyiraman (WIB). final TimeOfDay time; /// Teks deskripsi pengulangan jadwal (misal: "Setiap Hari" atau "Sen, Rab, Jum"). final String repeat; /// Menandakan apakah jadwal ini sedang aktif atau dinonaktifkan oleh pengguna. final bool isEnabled; /// Format string Cron asli yang disimpan di database (dalam zona waktu UTC). final String? cron; /// Durasi penyiraman dalam satuan detik. Default: 30 detik. final int durationS; const WateringSchedule({ required this.id, required this.time, this.repeat = 'Setiap Hari', this.isEnabled = true, this.cron, this.durationS = 30, }); /// Mengembalikan format jam dalam string 'HH.MM' untuk kebutuhan UI. String get formattedTime { final h = time.hour.toString().padLeft(2, '0'); final m = time.minute.toString().padLeft(2, '0'); return '$h.$m'; } /// Menggandakan objek [WateringSchedule] dengan nilai baru secara immutable. 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 ───────────────────────────────────────────────── /// Mode operasional dari modul IoT penyiram jamur: /// - [auto_] : Pompa menyala otomatis jika sensor mendeteksi nilai melewati threshold atau sesuai jadwal. /// - [manual] : Pompa hanya menyala saat dikendalikan langsung oleh pengguna dari aplikasi. /// - [offline] : Status perangkat sedang tidak terhubung ke jaringan internet. enum DeviceMode { auto_, manual, offline } /// Ekstensi pendukung untuk mempermudah manipulasi nilai enum [DeviceMode]. extension DeviceModeX on DeviceMode { /// Nilai string yang digunakan dalam pertukaran data API dengan backend. String get apiValue { switch (this) { case DeviceMode.auto_: return 'auto'; case DeviceMode.manual: return 'manual'; case DeviceMode.offline: return 'offline'; } } /// Teks berformat rapi untuk ditampilkan di UI label status. String get label { switch (this) { case DeviceMode.auto_: return 'AUTO'; case DeviceMode.manual: return 'MANUAL'; case DeviceMode.offline: return 'OFFLINE'; } } /// Memparsing string dari backend menjadi tipe [DeviceMode]. 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 ──────────────────────────────────────────── /// State yang menampung seluruh konfigurasi kendali modul IoT (Pompa/Watering). class ControlState { /// Status loading pemuatan data konfigurasi dari API. final bool isLoading; /// Status relay pompa saat ini (true = menyala, false = mati). final bool isPumpOn; /// Nilai threshold suhu minimal (°C). final double suhuMinLimit; /// Nilai threshold suhu maksimal (°C). final double suhuMaxLimit; /// Nilai threshold kelembapan minimal (%). final double kelembapanLimit; /// Daftar jadwal penyiraman otomatis yang telah diatur pengguna. final List schedules; /// Pesan error dari server (jika ada). final String? errorMessage; /// Pesan sukses aksi (jika ada). final String? successMessage; /// Berapa lama pompa manual diset menyala (detik). final int? activePumpDuration; /// Timestamp waktu ketika pompa dinyalakan (untuk kalkulasi countdown sisa durasi). final DateTime? pumpTurnedOnAt; /// Mode alat saat ini (Auto / Manual / Offline). final DeviceMode deviceMode; /// Status loading khusus saat mengubah mode alat. final bool isModeLoading; const ControlState({ this.isLoading = false, this.isPumpOn = false, this.suhuMinLimit = 22, this.suhuMaxLimit = 30, this.kelembapanLimit = 75, this.schedules = const [], this.errorMessage, this.successMessage, this.activePumpDuration, this.pumpTurnedOnAt, this.deviceMode = DeviceMode.auto_, this.isModeLoading = false, }); /// State awal saat pertama kali dimuat di memori. factory ControlState.initial() => const ControlState( isLoading: true, // Data sedang dimuat dari API pada awal jalannya screen schedules: [], ); /// Menggandakan [ControlState] secara immutable. /// Menyediakan fitur pembersihan pesan sukses/error atau countdown timer. ControlState copyWith({ bool? isLoading, bool? isPumpOn, double? suhuMinLimit, double? suhuMaxLimit, 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, suhuMinLimit: suhuMinLimit ?? this.suhuMinLimit, suhuMaxLimit: suhuMaxLimit ?? this.suhuMaxLimit, 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 && suhuMinLimit == other.suhuMinLimit && suhuMaxLimit == other.suhuMaxLimit && 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 ^ suhuMinLimit.hashCode ^ suhuMaxLimit.hashCode ^ kelembapanLimit.hashCode ^ errorMessage.hashCode ^ successMessage.hashCode ^ activePumpDuration.hashCode ^ pumpTurnedOnAt.hashCode ^ schedules.hashCode; }