oke
This commit is contained in:
parent
53c01e694f
commit
f361b85612
|
|
@ -5,7 +5,8 @@ import 'package:firebase_database/firebase_database.dart';
|
||||||
part 'evaporasi_settings_event.dart';
|
part 'evaporasi_settings_event.dart';
|
||||||
part 'evaporasi_settings_state.dart';
|
part 'evaporasi_settings_state.dart';
|
||||||
|
|
||||||
class EvaporasiSettingsBloc extends Bloc<EvaporasiSettingsEvent, EvaporasiSettingsState> {
|
class EvaporasiSettingsBloc
|
||||||
|
extends Bloc<EvaporasiSettingsEvent, EvaporasiSettingsState> {
|
||||||
final DatabaseReference _ref;
|
final DatabaseReference _ref;
|
||||||
|
|
||||||
static const _path = 'Monitoring/settings/evaporasi';
|
static const _path = 'Monitoring/settings/evaporasi';
|
||||||
|
|
@ -44,10 +45,12 @@ class EvaporasiSettingsBloc extends Bloc<EvaporasiSettingsEvent, EvaporasiSettin
|
||||||
thresholdRendah: _toDouble(data['threshold_rendah'], 2.0),
|
thresholdRendah: _toDouble(data['threshold_rendah'], 2.0),
|
||||||
thresholdTinggi: _toDouble(data['threshold_tinggi'], 10.0),
|
thresholdTinggi: _toDouble(data['threshold_tinggi'], 10.0),
|
||||||
rumusKalibrasi: (data['rumus_kalibrasi'] as String?) ?? 'selisih_max',
|
rumusKalibrasi: (data['rumus_kalibrasi'] as String?) ?? 'selisih_max',
|
||||||
koreksiOffset: _toDouble(data['koreksi_offset'], 0.0), pumpStartTime: (data['pump_start_time'] as String?) ?? '06:00',
|
koreksiOffset: _toDouble(data['koreksi_offset'], 0.0),
|
||||||
|
pumpStartTime: (data['pump_start_time'] as String?) ?? '06:00',
|
||||||
pumpEndTime: (data['pump_end_time'] as String?) ?? '18:00',
|
pumpEndTime: (data['pump_end_time'] as String?) ?? '18:00',
|
||||||
d0: _toInt(data['d0'], 0),
|
d0: _toInt(data['d0'], 0),
|
||||||
dmaxManual: _toInt(data['dmax_manual'], 0), intervalRealtime_ms: _toInt(data['interval_realtime_ms'], 300000),
|
dmaxManual: _toInt(data['dmax_manual'], 0),
|
||||||
|
intervalRealtime_ms: _toInt(data['interval_realtime_ms'], 300000),
|
||||||
intervalHistory_ms: _toInt(data['interval_history_ms'], 600000),
|
intervalHistory_ms: _toInt(data['interval_history_ms'], 600000),
|
||||||
intervalBaca_ms: _toInt(data['interval_baca_ms'], 10000),
|
intervalBaca_ms: _toInt(data['interval_baca_ms'], 10000),
|
||||||
status: EvaporasiSettingsStatus.loaded,
|
status: EvaporasiSettingsStatus.loaded,
|
||||||
|
|
@ -70,15 +73,20 @@ class EvaporasiSettingsBloc extends Bloc<EvaporasiSettingsEvent, EvaporasiSettin
|
||||||
final rt = Map<String, dynamic>.from(rtSnap.value as Map);
|
final rt = Map<String, dynamic>.from(rtSnap.value as Map);
|
||||||
final val = _toInt(rt['dmax_saat_ini'], 0);
|
final val = _toInt(rt['dmax_saat_ini'], 0);
|
||||||
final firmware = (rt['firmware_version'] as String?) ?? '--';
|
final firmware = (rt['firmware_version'] as String?) ?? '--';
|
||||||
final wifi = rt['wifi_connected'] is bool ? (rt['wifi_connected'] as bool) : (rt['wifi_connected'] == 1);
|
final wifi = rt['wifi_connected'] is bool
|
||||||
final firebase = rt['firebase_connected'] is bool ? (rt['firebase_connected'] as bool) : (rt['firebase_connected'] == 1);
|
? (rt['wifi_connected'] as bool)
|
||||||
|
: (rt['wifi_connected'] == 1);
|
||||||
|
final firebase = rt['firebase_connected'] is bool
|
||||||
|
? (rt['firebase_connected'] as bool)
|
||||||
|
: (rt['firebase_connected'] == 1);
|
||||||
final activeD0 = _toInt(rt['d0_active'], state.d0);
|
final activeD0 = _toInt(rt['d0_active'], state.d0);
|
||||||
final activeDmax = _toInt(rt['dmax_active'], val);
|
final activeDmax = _toInt(rt['dmax_active'], val);
|
||||||
DateTime? lastUpd;
|
DateTime? lastUpd;
|
||||||
try {
|
try {
|
||||||
final lu = rt['last_update'];
|
final lu = rt['last_update'];
|
||||||
if (lu != null) {
|
if (lu != null) {
|
||||||
final ms = (lu is num) ? lu.toInt() : int.tryParse(lu.toString()) ?? 0;
|
final ms =
|
||||||
|
(lu is num) ? lu.toInt() : int.tryParse(lu.toString()) ?? 0;
|
||||||
if (ms > 0) lastUpd = DateTime.fromMillisecondsSinceEpoch(ms);
|
if (ms > 0) lastUpd = DateTime.fromMillisecondsSinceEpoch(ms);
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
@ -86,15 +94,16 @@ class EvaporasiSettingsBloc extends Bloc<EvaporasiSettingsEvent, EvaporasiSettin
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
dmax: val,
|
dmax: val,
|
||||||
firmwareVersion: firmware,
|
firmwareVersion: firmware,
|
||||||
wifiConnected: wifi ?? false,
|
// wifiConnected: wifi ?? false,
|
||||||
firebaseConnected: firebase ?? false,
|
// firebaseConnected: firebase ?? false,
|
||||||
activeD0: activeD0,
|
activeD0: activeD0,
|
||||||
activeDmax: activeDmax,
|
activeDmax: activeDmax,
|
||||||
lastUpdate: lastUpd,
|
lastUpdate: lastUpd,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
// fallback: try single path
|
// fallback: try single path
|
||||||
final snap = await FirebaseDatabase.instance.ref(_rtdbRealtimePath).get();
|
final snap =
|
||||||
|
await FirebaseDatabase.instance.ref(_rtdbRealtimePath).get();
|
||||||
final val = snap.exists ? (snap.value as num?)?.toInt() ?? 0 : 0;
|
final val = snap.exists ? (snap.value as num?)?.toInt() ?? 0 : 0;
|
||||||
emit(state.copyWith(dmax: val));
|
emit(state.copyWith(dmax: val));
|
||||||
}
|
}
|
||||||
|
|
@ -106,57 +115,68 @@ class EvaporasiSettingsBloc extends Bloc<EvaporasiSettingsEvent, EvaporasiSettin
|
||||||
void _onThresholdRendahChanged(
|
void _onThresholdRendahChanged(
|
||||||
EvaporasiThresholdRendahChanged event,
|
EvaporasiThresholdRendahChanged event,
|
||||||
Emitter<EvaporasiSettingsState> emit,
|
Emitter<EvaporasiSettingsState> emit,
|
||||||
) => emit(state.copyWith(thresholdRendah: event.value));
|
) =>
|
||||||
|
emit(state.copyWith(thresholdRendah: event.value));
|
||||||
|
|
||||||
void _onThresholdTinggiChanged(
|
void _onThresholdTinggiChanged(
|
||||||
EvaporasiThresholdTinggiChanged event,
|
EvaporasiThresholdTinggiChanged event,
|
||||||
Emitter<EvaporasiSettingsState> emit,
|
Emitter<EvaporasiSettingsState> emit,
|
||||||
) => emit(state.copyWith(thresholdTinggi: event.value));
|
) =>
|
||||||
|
emit(state.copyWith(thresholdTinggi: event.value));
|
||||||
|
|
||||||
void _onRumusChanged(
|
void _onRumusChanged(
|
||||||
EvaporasiRumusKalibrasiChanged event,
|
EvaporasiRumusKalibrasiChanged event,
|
||||||
Emitter<EvaporasiSettingsState> emit,
|
Emitter<EvaporasiSettingsState> emit,
|
||||||
) => emit(state.copyWith(rumusKalibrasi: event.rumus));
|
) =>
|
||||||
|
emit(state.copyWith(rumusKalibrasi: event.rumus));
|
||||||
|
|
||||||
void _onOffsetChanged(
|
void _onOffsetChanged(
|
||||||
EvaporasiKoreksiOffsetChanged event,
|
EvaporasiKoreksiOffsetChanged event,
|
||||||
Emitter<EvaporasiSettingsState> emit,
|
Emitter<EvaporasiSettingsState> emit,
|
||||||
) => emit(state.copyWith(koreksiOffset: event.value));
|
) =>
|
||||||
|
emit(state.copyWith(koreksiOffset: event.value));
|
||||||
|
|
||||||
void _onIntervalRealtimeChanged(
|
void _onIntervalRealtimeChanged(
|
||||||
EvaporasiIntervalRealtimeChanged event,
|
EvaporasiIntervalRealtimeChanged event,
|
||||||
Emitter<EvaporasiSettingsState> emit,
|
Emitter<EvaporasiSettingsState> emit,
|
||||||
) => emit(state.copyWith(intervalRealtime_ms: event.value));
|
) =>
|
||||||
|
emit(state.copyWith(intervalRealtime_ms: event.value));
|
||||||
|
|
||||||
void _onIntervalHistoryChanged(
|
void _onIntervalHistoryChanged(
|
||||||
EvaporasiIntervalHistoryChanged event,
|
EvaporasiIntervalHistoryChanged event,
|
||||||
Emitter<EvaporasiSettingsState> emit,
|
Emitter<EvaporasiSettingsState> emit,
|
||||||
) => emit(state.copyWith(intervalHistory_ms: event.value));
|
) =>
|
||||||
|
emit(state.copyWith(intervalHistory_ms: event.value));
|
||||||
|
|
||||||
void _onIntervalBacaChanged(
|
void _onIntervalBacaChanged(
|
||||||
EvaporasiIntervalBacaChanged event,
|
EvaporasiIntervalBacaChanged event,
|
||||||
Emitter<EvaporasiSettingsState> emit,
|
Emitter<EvaporasiSettingsState> emit,
|
||||||
) => emit(state.copyWith(intervalBaca_ms: event.value));
|
) =>
|
||||||
|
emit(state.copyWith(intervalBaca_ms: event.value));
|
||||||
|
|
||||||
void _onPumpStartChanged(
|
void _onPumpStartChanged(
|
||||||
EvaporasiPumpStartChanged event,
|
EvaporasiPumpStartChanged event,
|
||||||
Emitter<EvaporasiSettingsState> emit,
|
Emitter<EvaporasiSettingsState> emit,
|
||||||
) => emit(state.copyWith(pumpStartTime: event.value));
|
) =>
|
||||||
|
emit(state.copyWith(pumpStartTime: event.value));
|
||||||
|
|
||||||
void _onPumpEndChanged(
|
void _onPumpEndChanged(
|
||||||
EvaporasiPumpEndChanged event,
|
EvaporasiPumpEndChanged event,
|
||||||
Emitter<EvaporasiSettingsState> emit,
|
Emitter<EvaporasiSettingsState> emit,
|
||||||
) => emit(state.copyWith(pumpEndTime: event.value));
|
) =>
|
||||||
|
emit(state.copyWith(pumpEndTime: event.value));
|
||||||
|
|
||||||
void _onD0Changed(
|
void _onD0Changed(
|
||||||
EvaporasiD0Changed event,
|
EvaporasiD0Changed event,
|
||||||
Emitter<EvaporasiSettingsState> emit,
|
Emitter<EvaporasiSettingsState> emit,
|
||||||
) => emit(state.copyWith(d0: event.value));
|
) =>
|
||||||
|
emit(state.copyWith(d0: event.value));
|
||||||
|
|
||||||
void _onDmaxManualChanged(
|
void _onDmaxManualChanged(
|
||||||
EvaporasiDmaxManualChanged event,
|
EvaporasiDmaxManualChanged event,
|
||||||
Emitter<EvaporasiSettingsState> emit,
|
Emitter<EvaporasiSettingsState> emit,
|
||||||
) => emit(state.copyWith(dmaxManual: event.value));
|
) =>
|
||||||
|
emit(state.copyWith(dmaxManual: event.value));
|
||||||
|
|
||||||
Future<void> _onDmaxReset(
|
Future<void> _onDmaxReset(
|
||||||
EvaporasiDmaxResetRequested event,
|
EvaporasiDmaxResetRequested event,
|
||||||
|
|
@ -213,7 +233,8 @@ class EvaporasiSettingsBloc extends Bloc<EvaporasiSettingsEvent, EvaporasiSettin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double _toDouble(dynamic v, double def) => v == null ? def : (v as num).toDouble();
|
double _toDouble(dynamic v, double def) =>
|
||||||
|
v == null ? def : (v as num).toDouble();
|
||||||
|
|
||||||
int _toInt(dynamic v, int def) => v == null ? def : (v as num).toInt();
|
int _toInt(dynamic v, int def) => v == null ? def : (v as num).toInt();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ class EvaporasiSettingsScreen extends StatefulWidget {
|
||||||
const EvaporasiSettingsScreen({super.key});
|
const EvaporasiSettingsScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<EvaporasiSettingsScreen> createState() => _EvaporasiSettingsScreenState();
|
State<EvaporasiSettingsScreen> createState() =>
|
||||||
|
_EvaporasiSettingsScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EvaporasiSettingsScreenState extends State<EvaporasiSettingsScreen> {
|
class _EvaporasiSettingsScreenState extends State<EvaporasiSettingsScreen> {
|
||||||
|
|
@ -52,7 +53,11 @@ class _EvaporasiSettingsScreenState extends State<EvaporasiSettingsScreen> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _pickPumpTime(BuildContext context, TextEditingController controller, ValueChanged<String> onSelected, String initialValue) async {
|
Future<void> _pickPumpTime(
|
||||||
|
BuildContext context,
|
||||||
|
TextEditingController controller,
|
||||||
|
ValueChanged<String> onSelected,
|
||||||
|
String initialValue) async {
|
||||||
final parts = initialValue.split(':');
|
final parts = initialValue.split(':');
|
||||||
final initial = TimeOfDay(
|
final initial = TimeOfDay(
|
||||||
hour: int.tryParse(parts[0]) ?? 6,
|
hour: int.tryParse(parts[0]) ?? 6,
|
||||||
|
|
@ -64,7 +69,8 @@ class _EvaporasiSettingsScreenState extends State<EvaporasiSettingsScreen> {
|
||||||
helpText: 'Pilih Jam Pompa',
|
helpText: 'Pilih Jam Pompa',
|
||||||
);
|
);
|
||||||
if (picked != null) {
|
if (picked != null) {
|
||||||
final value = '${picked.hour.toString().padLeft(2, '0')}:${picked.minute.toString().padLeft(2, '0')}';
|
final value =
|
||||||
|
'${picked.hour.toString().padLeft(2, '0')}:${picked.minute.toString().padLeft(2, '0')}';
|
||||||
controller.text = value;
|
controller.text = value;
|
||||||
onSelected(value);
|
onSelected(value);
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +107,8 @@ class _EvaporasiSettingsScreenState extends State<EvaporasiSettingsScreen> {
|
||||||
]),
|
]),
|
||||||
backgroundColor: Colors.green.shade600,
|
backgroundColor: Colors.green.shade600,
|
||||||
behavior: SnackBarBehavior.floating,
|
behavior: SnackBarBehavior.floating,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10)),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if (state.status == EvaporasiSettingsStatus.error &&
|
if (state.status == EvaporasiSettingsStatus.error &&
|
||||||
|
|
@ -147,7 +154,8 @@ class _EvaporasiSettingsScreenState extends State<EvaporasiSettingsScreen> {
|
||||||
Text(
|
Text(
|
||||||
'Tentukan nilai (mm) untuk klasifikasi status '
|
'Tentukan nilai (mm) untuk klasifikasi status '
|
||||||
'Rendah, Normal, dan Tinggi.',
|
'Rendah, Normal, dan Tinggi.',
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade500),
|
style: TextStyle(
|
||||||
|
fontSize: 12, color: Colors.grey.shade500),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_SettingsCard(children: [
|
_SettingsCard(children: [
|
||||||
|
|
@ -157,7 +165,8 @@ class _EvaporasiSettingsScreenState extends State<EvaporasiSettingsScreen> {
|
||||||
controller: _rendahController,
|
controller: _rendahController,
|
||||||
label: 'Batas Rendah–Normal (mm)',
|
label: 'Batas Rendah–Normal (mm)',
|
||||||
hint: 'Contoh: 20.0',
|
hint: 'Contoh: 20.0',
|
||||||
helper: 'Nilai < batas ini → Status Rendah. Default: 20.0',
|
helper:
|
||||||
|
'Nilai < batas ini → Status Rendah. Default: 20.0',
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
final d = double.tryParse(v);
|
final d = double.tryParse(v);
|
||||||
if (d != null && d >= 0) {
|
if (d != null && d >= 0) {
|
||||||
|
|
@ -170,7 +179,8 @@ class _EvaporasiSettingsScreenState extends State<EvaporasiSettingsScreen> {
|
||||||
controller: _tinggiController,
|
controller: _tinggiController,
|
||||||
label: 'Batas Normal–Tinggi (mm)',
|
label: 'Batas Normal–Tinggi (mm)',
|
||||||
hint: 'Contoh: 30.0',
|
hint: 'Contoh: 30.0',
|
||||||
helper: 'Nilai ≥ batas ini → Status Tinggi. Default: 30.0',
|
helper:
|
||||||
|
'Nilai ≥ batas ini → Status Tinggi. Default: 30.0',
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
final d = double.tryParse(v);
|
final d = double.tryParse(v);
|
||||||
if (d != null && d >= 0) {
|
if (d != null && d >= 0) {
|
||||||
|
|
@ -185,13 +195,15 @@ class _EvaporasiSettingsScreenState extends State<EvaporasiSettingsScreen> {
|
||||||
Text(
|
Text(
|
||||||
'Pilih metode penghitungan nilai evaporasi '
|
'Pilih metode penghitungan nilai evaporasi '
|
||||||
'terkalibrasi (E) dari data sensor.',
|
'terkalibrasi (E) dari data sensor.',
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade500),
|
style: TextStyle(
|
||||||
|
fontSize: 12, color: Colors.grey.shade500),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_SettingsCard(children: [
|
_SettingsCard(children: [
|
||||||
_RumusSelector(
|
_RumusSelector(
|
||||||
selected: state.rumusKalibrasi,
|
selected: state.rumusKalibrasi,
|
||||||
onChanged: (v) => bloc.add(EvaporasiRumusKalibrasiChanged(v)),
|
onChanged: (v) =>
|
||||||
|
bloc.add(EvaporasiRumusKalibrasiChanged(v)),
|
||||||
),
|
),
|
||||||
const Divider(height: 28),
|
const Divider(height: 28),
|
||||||
_FormulaPreview(state: state),
|
_FormulaPreview(state: state),
|
||||||
|
|
@ -200,7 +212,8 @@ class _EvaporasiSettingsScreenState extends State<EvaporasiSettingsScreen> {
|
||||||
controller: _offsetController,
|
controller: _offsetController,
|
||||||
label: 'Koreksi Offset (mm)',
|
label: 'Koreksi Offset (mm)',
|
||||||
hint: 'Contoh: 0.0 atau -1.5',
|
hint: 'Contoh: 0.0 atau -1.5',
|
||||||
helper: 'Nilai ditambahkan ke hasil E. Gunakan negatif untuk koreksi ke bawah.',
|
helper:
|
||||||
|
'Nilai ditambahkan ke hasil E. Gunakan negatif untuk koreksi ke bawah.',
|
||||||
allowNegative: true,
|
allowNegative: true,
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
final d = double.tryParse(v);
|
final d = double.tryParse(v);
|
||||||
|
|
@ -216,13 +229,15 @@ class _EvaporasiSettingsScreenState extends State<EvaporasiSettingsScreen> {
|
||||||
Text(
|
Text(
|
||||||
'Atur seberapa sering ESP32 membaca sensor dan mengirim data. '
|
'Atur seberapa sering ESP32 membaca sensor dan mengirim data. '
|
||||||
'Interval lebih pendek = data lebih real-time, baterai lebih boros.',
|
'Interval lebih pendek = data lebih real-time, baterai lebih boros.',
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade500),
|
style: TextStyle(
|
||||||
|
fontSize: 12, color: Colors.grey.shade500),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_SettingsCard(children: [
|
_SettingsCard(children: [
|
||||||
_IntervalSelector(
|
_IntervalSelector(
|
||||||
label: 'Interval Baca Sensor',
|
label: 'Interval Baca Sensor',
|
||||||
helper: 'Seberapa sering sensor dibaca. Min: 5 detik.',
|
helper:
|
||||||
|
'Seberapa sering sensor dibaca. Min: 5 detik.',
|
||||||
options: const {
|
options: const {
|
||||||
5000: '5 detik',
|
5000: '5 detik',
|
||||||
10000: '10 detik (default)',
|
10000: '10 detik (default)',
|
||||||
|
|
@ -230,19 +245,22 @@ class _EvaporasiSettingsScreenState extends State<EvaporasiSettingsScreen> {
|
||||||
60000: '1 menit',
|
60000: '1 menit',
|
||||||
},
|
},
|
||||||
selected: state.intervalBaca_ms,
|
selected: state.intervalBaca_ms,
|
||||||
onChanged: (v) => bloc.add(EvaporasiIntervalBacaChanged(v)),
|
onChanged: (v) =>
|
||||||
|
bloc.add(EvaporasiIntervalBacaChanged(v)),
|
||||||
),
|
),
|
||||||
const Divider(height: 24),
|
const Divider(height: 24),
|
||||||
_IntervalSelector(
|
_IntervalSelector(
|
||||||
label: 'Interval Kirim Realtime',
|
label: 'Interval Kirim Realtime',
|
||||||
helper: 'Frekuensi update data real-time ke Firebase.',
|
helper:
|
||||||
|
'Frekuensi update data real-time ke Firebase.',
|
||||||
options: const {
|
options: const {
|
||||||
60000: '1 menit',
|
60000: '1 menit',
|
||||||
300000: '5 menit (default)',
|
300000: '5 menit (default)',
|
||||||
600000: '10 menit',
|
600000: '10 menit',
|
||||||
},
|
},
|
||||||
selected: state.intervalRealtime_ms,
|
selected: state.intervalRealtime_ms,
|
||||||
onChanged: (v) => bloc.add(EvaporasiIntervalRealtimeChanged(v)),
|
onChanged: (v) =>
|
||||||
|
bloc.add(EvaporasiIntervalRealtimeChanged(v)),
|
||||||
),
|
),
|
||||||
const Divider(height: 24),
|
const Divider(height: 24),
|
||||||
_IntervalSelector(
|
_IntervalSelector(
|
||||||
|
|
@ -255,7 +273,8 @@ class _EvaporasiSettingsScreenState extends State<EvaporasiSettingsScreen> {
|
||||||
3600000: '1 jam',
|
3600000: '1 jam',
|
||||||
},
|
},
|
||||||
selected: state.intervalHistory_ms,
|
selected: state.intervalHistory_ms,
|
||||||
onChanged: (v) => bloc.add(EvaporasiIntervalHistoryChanged(v)),
|
onChanged: (v) =>
|
||||||
|
bloc.add(EvaporasiIntervalHistoryChanged(v)),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
|
@ -263,7 +282,8 @@ class _EvaporasiSettingsScreenState extends State<EvaporasiSettingsScreen> {
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Text(
|
Text(
|
||||||
'Atur jam mulai dan selesai pompa secara langsung dari aplikasi.',
|
'Atur jam mulai dan selesai pompa secara langsung dari aplikasi.',
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade500),
|
style: TextStyle(
|
||||||
|
fontSize: 12, color: Colors.grey.shade500),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_SettingsCard(children: [
|
_SettingsCard(children: [
|
||||||
|
|
@ -274,7 +294,8 @@ class _EvaporasiSettingsScreenState extends State<EvaporasiSettingsScreen> {
|
||||||
onTap: () => _pickPumpTime(
|
onTap: () => _pickPumpTime(
|
||||||
context,
|
context,
|
||||||
_pumpStartController,
|
_pumpStartController,
|
||||||
(value) => bloc.add(EvaporasiPumpStartChanged(value)),
|
(value) =>
|
||||||
|
bloc.add(EvaporasiPumpStartChanged(value)),
|
||||||
state.pumpStartTime,
|
state.pumpStartTime,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -286,7 +307,8 @@ class _EvaporasiSettingsScreenState extends State<EvaporasiSettingsScreen> {
|
||||||
onTap: () => _pickPumpTime(
|
onTap: () => _pickPumpTime(
|
||||||
context,
|
context,
|
||||||
_pumpEndController,
|
_pumpEndController,
|
||||||
(value) => bloc.add(EvaporasiPumpEndChanged(value)),
|
(value) =>
|
||||||
|
bloc.add(EvaporasiPumpEndChanged(value)),
|
||||||
state.pumpEndTime,
|
state.pumpEndTime,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -296,7 +318,8 @@ class _EvaporasiSettingsScreenState extends State<EvaporasiSettingsScreen> {
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Text(
|
Text(
|
||||||
'Masukkan nilai D0 dan DMAX secara manual untuk kalibrasi sensor tanpa upload firmware.',
|
'Masukkan nilai D0 dan DMAX secara manual untuk kalibrasi sensor tanpa upload firmware.',
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade500),
|
style: TextStyle(
|
||||||
|
fontSize: 12, color: Colors.grey.shade500),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_SettingsCard(children: [
|
_SettingsCard(children: [
|
||||||
|
|
@ -317,7 +340,8 @@ class _EvaporasiSettingsScreenState extends State<EvaporasiSettingsScreen> {
|
||||||
controller: _dmaxManualController,
|
controller: _dmaxManualController,
|
||||||
label: 'Nilai DMAX Manual',
|
label: 'Nilai DMAX Manual',
|
||||||
hint: 'Contoh: 1023',
|
hint: 'Contoh: 1023',
|
||||||
helper: 'Kalibrasi nilai maksimum sensor secara manual.',
|
helper:
|
||||||
|
'Kalibrasi nilai maksimum sensor secara manual.',
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
final d = int.tryParse(v);
|
final d = int.tryParse(v);
|
||||||
if (d != null && d >= 0) {
|
if (d != null && d >= 0) {
|
||||||
|
|
@ -330,21 +354,28 @@ class _EvaporasiSettingsScreenState extends State<EvaporasiSettingsScreen> {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: isSaving ? null : () => bloc.add(EvaporasiSettingsSaved()),
|
onPressed: isSaving
|
||||||
|
? null
|
||||||
|
: () => bloc.add(EvaporasiSettingsSaved()),
|
||||||
icon: isSaving
|
icon: isSaving
|
||||||
? const SizedBox(
|
? const SizedBox(
|
||||||
width: 18,
|
width: 18,
|
||||||
height: 18,
|
height: 18,
|
||||||
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2, color: Colors.white),
|
||||||
)
|
)
|
||||||
: const Icon(Icons.save_rounded),
|
: const Icon(Icons.save_rounded),
|
||||||
label: Text(isSaving ? 'Menyimpan...' : 'Simpan ke Firebase'),
|
label: Text(isSaving
|
||||||
|
? 'Menyimpan...'
|
||||||
|
: 'Simpan ke Firebase'),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.blue.shade700,
|
backgroundColor: Colors.blue.shade700,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
|
shape: RoundedRectangleBorder(
|
||||||
textStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
|
borderRadius: BorderRadius.circular(14)),
|
||||||
|
textStyle: const TextStyle(
|
||||||
|
fontSize: 15, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -378,15 +409,23 @@ class _ThresholdPreview extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Text('Preview Klasifikasi',
|
const Text('Preview Klasifikasi',
|
||||||
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Colors.black54)),
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.black54)),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
_StatusChip('Rendah', '< ${rendah.toStringAsFixed(0)} mm', Colors.green),
|
_StatusChip(
|
||||||
|
'Rendah', '< ${rendah.toStringAsFixed(0)} mm', Colors.green),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_StatusChip('Normal', '${rendah.toStringAsFixed(0)}–${tinggi.toStringAsFixed(0)} mm', Colors.orange),
|
_StatusChip(
|
||||||
|
'Normal',
|
||||||
|
'${rendah.toStringAsFixed(0)}–${tinggi.toStringAsFixed(0)} mm',
|
||||||
|
Colors.orange),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_StatusChip('Tinggi', '≥ ${tinggi.toStringAsFixed(0)} mm', Colors.red),
|
_StatusChip(
|
||||||
|
'Tinggi', '≥ ${tinggi.toStringAsFixed(0)} mm', Colors.red),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (!isValid) ...[
|
if (!isValid) ...[
|
||||||
|
|
@ -422,9 +461,12 @@ class _StatusChip extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(label,
|
Text(label,
|
||||||
style: TextStyle(fontSize: 11, fontWeight: FontWeight.bold, color: color)),
|
style: TextStyle(
|
||||||
|
fontSize: 11, fontWeight: FontWeight.bold, color: color)),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text(range, style: TextStyle(fontSize: 9, color: color), textAlign: TextAlign.center),
|
Text(range,
|
||||||
|
style: TextStyle(fontSize: 9, color: color),
|
||||||
|
textAlign: TextAlign.center),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -458,7 +500,10 @@ class _RumusSelector extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Text('Metode Kalkulasi',
|
const Text('Metode Kalkulasi',
|
||||||
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Colors.black54)),
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.black54)),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
...options.map((opt) {
|
...options.map((opt) {
|
||||||
final isSelected = opt.value == selected;
|
final isSelected = opt.value == selected;
|
||||||
|
|
@ -472,13 +517,18 @@ class _RumusSelector extends StatelessWidget {
|
||||||
color: isSelected ? Colors.blue.shade50 : Colors.grey.shade50,
|
color: isSelected ? Colors.blue.shade50 : Colors.grey.shade50,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isSelected ? Colors.blue.shade400 : Colors.grey.shade200,
|
color:
|
||||||
|
isSelected ? Colors.blue.shade400 : Colors.grey.shade200,
|
||||||
width: isSelected ? 1.5 : 1,
|
width: isSelected ? 1.5 : 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(opt.icon, color: isSelected ? Colors.blue.shade700 : Colors.grey.shade500, size: 22),
|
Icon(opt.icon,
|
||||||
|
color: isSelected
|
||||||
|
? Colors.blue.shade700
|
||||||
|
: Colors.grey.shade500,
|
||||||
|
size: 22),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -488,18 +538,23 @@ class _RumusSelector extends StatelessWidget {
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: isSelected ? Colors.blue.shade800 : Colors.black87)),
|
color: isSelected
|
||||||
|
? Colors.blue.shade800
|
||||||
|
: Colors.black87)),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text(opt.subtitle,
|
Text(opt.subtitle,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
color: isSelected ? Colors.blue.shade600 : Colors.grey.shade500)),
|
color: isSelected
|
||||||
|
? Colors.blue.shade600
|
||||||
|
: Colors.grey.shade500)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (isSelected)
|
if (isSelected)
|
||||||
Icon(Icons.check_circle_rounded, color: Colors.blue.shade600, size: 20),
|
Icon(Icons.check_circle_rounded,
|
||||||
|
color: Colors.blue.shade600, size: 20),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -533,14 +588,21 @@ class _FormulaPreview extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(children: [
|
Row(children: [
|
||||||
Icon(Icons.functions_rounded, size: 15, color: Colors.amber.shade300),
|
Icon(Icons.functions_rounded,
|
||||||
|
size: 15, color: Colors.amber.shade300),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text('Preview Rumus',
|
Text('Preview Rumus',
|
||||||
style: TextStyle(fontSize: 12, color: Colors.amber.shade300, fontWeight: FontWeight.bold)),
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.amber.shade300,
|
||||||
|
fontWeight: FontWeight.bold)),
|
||||||
]),
|
]),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(rumus,
|
Text(rumus,
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade300, fontFamily: 'monospace')),
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
fontFamily: 'monospace')),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Text('offset = $offsetStr mm',
|
Text('offset = $offsetStr mm',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|
@ -570,10 +632,12 @@ class _InfoCard extends StatelessWidget {
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.info_outline_rounded, size: 18, color: Colors.blue.shade600),
|
Icon(Icons.info_outline_rounded,
|
||||||
|
size: 18, color: Colors.blue.shade600),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(text, style: TextStyle(fontSize: 12, color: Colors.blue.shade800)),
|
child: Text(text,
|
||||||
|
style: TextStyle(fontSize: 12, color: Colors.blue.shade800)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -596,7 +660,8 @@ class _SettingsCard extends StatelessWidget {
|
||||||
BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 6),
|
BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 6),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: children),
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start, children: children),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -622,7 +687,8 @@ class _NumericField extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return TextField(
|
return TextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true),
|
keyboardType:
|
||||||
|
const TextInputType.numberWithOptions(decimal: true, signed: true),
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
FilteringTextInputFormatter.allow(
|
FilteringTextInputFormatter.allow(
|
||||||
allowNegative ? RegExp(r'^-?[0-9]*\.?[0-9]*') : RegExp(r'[0-9.]'),
|
allowNegative ? RegExp(r'^-?[0-9]*\.?[0-9]*') : RegExp(r'[0-9.]'),
|
||||||
|
|
@ -634,7 +700,8 @@ class _NumericField extends StatelessWidget {
|
||||||
helperText: helper,
|
helperText: helper,
|
||||||
helperMaxLines: 2,
|
helperMaxLines: 2,
|
||||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)),
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
|
|
@ -667,7 +734,8 @@ class _TimePickerField extends StatelessWidget {
|
||||||
helperMaxLines: 2,
|
helperMaxLines: 2,
|
||||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)),
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
suffixIcon: const Icon(Icons.schedule_rounded),
|
suffixIcon: const Icon(Icons.schedule_rounded),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
|
|
@ -698,7 +766,8 @@ class _IntervalSelector extends StatelessWidget {
|
||||||
Text(label,
|
Text(label,
|
||||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600)),
|
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600)),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(helper, style: TextStyle(fontSize: 11, color: Colors.grey.shade500)),
|
Text(helper,
|
||||||
|
style: TextStyle(fontSize: 11, color: Colors.grey.shade500)),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
|
|
@ -709,12 +778,16 @@ class _IntervalSelector extends StatelessWidget {
|
||||||
onTap: () => onChanged(e.key),
|
onTap: () => onChanged(e.key),
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
duration: const Duration(milliseconds: 180),
|
duration: const Duration(milliseconds: 180),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isSelected ? Colors.blue.shade700 : Colors.grey.shade100,
|
color:
|
||||||
|
isSelected ? Colors.blue.shade700 : Colors.grey.shade100,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isSelected ? Colors.blue.shade700 : Colors.grey.shade300,
|
color: isSelected
|
||||||
|
? Colors.blue.shade700
|
||||||
|
: Colors.grey.shade300,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
@ -734,65 +807,65 @@ class _IntervalSelector extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RealtimeInfo extends StatelessWidget {
|
// class _RealtimeInfo extends StatelessWidget {
|
||||||
final EvaporasiSettingsState state;
|
// final EvaporasiSettingsState state;
|
||||||
const _RealtimeInfo({required this.state});
|
// const _RealtimeInfo({required this.state});
|
||||||
|
|
||||||
String _fmtDate(DateTime? d) {
|
// String _fmtDate(DateTime? d) {
|
||||||
if (d == null) return '--';
|
// if (d == null) return '--';
|
||||||
final l = d.toLocal();
|
// final l = d.toLocal();
|
||||||
return '${l.day.toString().padLeft(2, '0')}/${l.month.toString().padLeft(2, '0')}/${l.year} ${l.hour.toString().padLeft(2, '0')}:${l.minute.toString().padLeft(2, '0')}:${l.second.toString().padLeft(2, '0')}';
|
// return '${l.day.toString().padLeft(2, '0')}/${l.month.toString().padLeft(2, '0')}/${l.year} ${l.hour.toString().padLeft(2, '0')}:${l.minute.toString().padLeft(2, '0')}:${l.second.toString().padLeft(2, '0')}';
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
Widget build(BuildContext context) {
|
// Widget build(BuildContext context) {
|
||||||
return Container(
|
// return Container(
|
||||||
width: double.infinity,
|
// width: double.infinity,
|
||||||
padding: const EdgeInsets.all(12),
|
// padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
// decoration: BoxDecoration(
|
||||||
color: Colors.grey.shade50,
|
// color: Colors.grey.shade50,
|
||||||
borderRadius: BorderRadius.circular(10),
|
// borderRadius: BorderRadius.circular(10),
|
||||||
border: Border.all(color: Colors.grey.shade200),
|
// border: Border.all(color: Colors.grey.shade200),
|
||||||
),
|
// ),
|
||||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
// child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Row(children: [
|
// Row(children: [
|
||||||
Icon(Icons.devices_rounded, size: 15, color: Colors.blue.shade700),
|
// Icon(Icons.devices_rounded, size: 15, color: Colors.blue.shade700),
|
||||||
const SizedBox(width: 8),
|
// const SizedBox(width: 8),
|
||||||
Text('Informasi Perangkat', style: TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: Colors.blue.shade700)),
|
// Text('Informasi Perangkat', style: TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: Colors.blue.shade700)),
|
||||||
]),
|
// ]),
|
||||||
const SizedBox(height: 10),
|
// const SizedBox(height: 10),
|
||||||
Wrap(spacing: 12, runSpacing: 8, children: [
|
// Wrap(spacing: 12, runSpacing: 8, children: [
|
||||||
_InfoTile(label: 'Firmware', value: state.firmwareVersion),
|
// _InfoTile(label: 'Firmware', value: state.firmwareVersion),
|
||||||
_InfoTile(label: 'WiFi', value: state.wifiConnected ? 'Terhubung' : 'Tidak'),
|
// _InfoTile(label: 'WiFi', value: state.wifiConnected ? 'Terhubung' : 'Tidak'),
|
||||||
_InfoTile(label: 'Firebase', value: state.firebaseConnected ? 'Terhubung' : 'Tidak'),
|
// _InfoTile(label: 'Firebase', value: state.firebaseConnected ? 'Terhubung' : 'Tidak'),
|
||||||
_InfoTile(label: 'D0 aktif', value: state.activeD0 == 0 ? '--' : state.activeD0.toString()),
|
// _InfoTile(label: 'D0 aktif', value: state.activeD0 == 0 ? '--' : state.activeD0.toString()),
|
||||||
_InfoTile(label: 'DMAX aktif', value: state.activeDmax == 0 ? '--' : state.activeDmax.toString()),
|
// _InfoTile(label: 'DMAX aktif', value: state.activeDmax == 0 ? '--' : state.activeDmax.toString()),
|
||||||
_InfoTile(label: 'Terakhir', value: _fmtDate(state.lastUpdate)),
|
// _InfoTile(label: 'Terakhir', value: _fmtDate(state.lastUpdate)),
|
||||||
])
|
// ])
|
||||||
]),
|
// ]),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
class _InfoTile extends StatelessWidget {
|
// class _InfoTile extends StatelessWidget {
|
||||||
final String label;
|
// final String label;
|
||||||
final String value;
|
// final String value;
|
||||||
const _InfoTile({required this.label, required this.value});
|
// const _InfoTile({required this.label, required this.value});
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
Widget build(BuildContext context) {
|
// Widget build(BuildContext context) {
|
||||||
return Container(
|
// return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
// padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
decoration: BoxDecoration(
|
// decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
// color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(8),
|
// borderRadius: BorderRadius.circular(8),
|
||||||
border: Border.all(color: Colors.grey.shade100),
|
// border: Border.all(color: Colors.grey.shade100),
|
||||||
),
|
// ),
|
||||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
// child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Text(label, style: TextStyle(fontSize: 11, color: Colors.grey.shade600)),
|
// Text(label, style: TextStyle(fontSize: 11, color: Colors.grey.shade600)),
|
||||||
const SizedBox(height: 6),
|
// const SizedBox(height: 6),
|
||||||
Text(value, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold)),
|
// Text(value, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold)),
|
||||||
]),
|
// ]),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue