225 lines
7.3 KiB
Dart
225 lines
7.3 KiB
Dart
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 maksimal (°C).
|
|
final double suhuLimit;
|
|
|
|
/// Nilai threshold kelembapan minimal (%).
|
|
final double kelembapanLimit;
|
|
|
|
/// Daftar jadwal penyiraman otomatis yang telah diatur pengguna.
|
|
final List<WateringSchedule> 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.suhuLimit = 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? suhuLimit,
|
|
double? kelembapanLimit,
|
|
List<WateringSchedule>? 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;
|
|
}
|