182 lines
5.8 KiB
Dart
182 lines
5.8 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
import '../providers/kandang_provider.dart';
|
|
import '../providers/panen_provider.dart';
|
|
import '../providers/penjadwalan_provider.dart';
|
|
import 'panen_auto_capture_service.dart';
|
|
|
|
/// Scheduler ringan di level aplikasi untuk memicu panen otomatis pagi/sore.
|
|
///
|
|
/// Catatan: ini berjalan selama proses app masih hidup (foreground/background).
|
|
/// Jika proses dibunuh OS, trigger akan berhenti sampai app dibuka lagi.
|
|
class PanenRuntimeSchedulerService {
|
|
PanenRuntimeSchedulerService._();
|
|
|
|
static Timer? _timer;
|
|
static bool _isRunning = false;
|
|
static bool _isProcessing = false;
|
|
static String? _lastPagiTriggerDate;
|
|
static final Map<int, String> _lastSoreTriggerDateByIndex =
|
|
{}; // Track per jadwal sore
|
|
|
|
static Future<void> start({
|
|
required PanenProvider panenProvider,
|
|
required KandangProvider kandangProvider,
|
|
required PenjadwalanProvider penjadwalanProvider,
|
|
}) async {
|
|
stop();
|
|
|
|
_isRunning = true;
|
|
_timer = Timer.periodic(const Duration(seconds: 30), (_) async {
|
|
await _tick(
|
|
panenProvider: panenProvider,
|
|
kandangProvider: kandangProvider,
|
|
penjadwalanProvider: penjadwalanProvider,
|
|
);
|
|
});
|
|
|
|
await _tick(
|
|
panenProvider: panenProvider,
|
|
kandangProvider: kandangProvider,
|
|
penjadwalanProvider: penjadwalanProvider,
|
|
);
|
|
|
|
if (kDebugMode) {
|
|
print('✅ PanenRuntimeSchedulerService started (30s tick)');
|
|
}
|
|
}
|
|
|
|
static void stop() {
|
|
_isRunning = false;
|
|
_timer?.cancel();
|
|
_timer = null;
|
|
}
|
|
|
|
static Future<void> _tick({
|
|
required PanenProvider panenProvider,
|
|
required KandangProvider kandangProvider,
|
|
required PenjadwalanProvider penjadwalanProvider,
|
|
}) async {
|
|
if (!_isRunning || _isProcessing) return;
|
|
|
|
_isProcessing = true;
|
|
try {
|
|
final now = DateTime.now();
|
|
final dateKey =
|
|
'${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';
|
|
|
|
final activeSchedules =
|
|
penjadwalanProvider.penjadwalans.where((j) => j.aktif).toList();
|
|
|
|
// Sort by ID urutan untuk memastikan penjadwalan1, penjadwalan2, dst
|
|
activeSchedules.sort((a, b) {
|
|
int numA = int.tryParse(a.id.replaceAll('penjadwalan', '')) ?? 999;
|
|
int numB = int.tryParse(b.id.replaceAll('penjadwalan', '')) ?? 999;
|
|
return numA.compareTo(numB);
|
|
});
|
|
|
|
int? morningHour;
|
|
int? morningMinute;
|
|
final afternoonSchedules = <(int index, int hour, int minute)>[];
|
|
|
|
// Jadwal pertama (penjadwalan1) = PAGI
|
|
// Jadwal ke-2+ (penjadwalan2, penjadwalan3, dst) = SORE
|
|
for (int i = 0; i < activeSchedules.length; i++) {
|
|
final schedule = activeSchedules[i];
|
|
final parsed = _parseJam(schedule.jam);
|
|
if (parsed == null) continue;
|
|
|
|
final hour = parsed.$1;
|
|
final minute = parsed.$2;
|
|
|
|
if (i == 0) {
|
|
// Jadwal pertama = PAGI
|
|
morningHour = hour;
|
|
morningMinute = minute;
|
|
} else {
|
|
// Jadwal ke-2+ = SORE (simpan semua jadwal sore)
|
|
afternoonSchedules.add((i, hour, minute));
|
|
}
|
|
}
|
|
|
|
// Trigger pagi hanya jika ada jadwal pagi yang aktif
|
|
if (morningHour != null && morningMinute != null) {
|
|
final morningStart = DateTime(
|
|
now.year,
|
|
now.month,
|
|
now.day,
|
|
morningHour,
|
|
morningMinute,
|
|
);
|
|
final morningEnd = morningStart.add(const Duration(minutes: 10));
|
|
final isMorningWindow =
|
|
!now.isBefore(morningStart) && !now.isAfter(morningEnd);
|
|
if (isMorningWindow && _lastPagiTriggerDate != dateKey) {
|
|
if (kDebugMode) {
|
|
print(
|
|
'🕘 Trigger runtime panen pagi ($dateKey @ $morningHour:${morningMinute.toString().padLeft(2, '0')})');
|
|
}
|
|
await PanenAutoCaptureService.triggerMorningCapture(
|
|
panenProvider,
|
|
kandangProvider,
|
|
);
|
|
_lastPagiTriggerDate = dateKey;
|
|
}
|
|
}
|
|
|
|
// Trigger semua jadwal sore yang waktunya match
|
|
for (int schedIdx = 0; schedIdx < afternoonSchedules.length; schedIdx++) {
|
|
final (index, afternoonHour, afternoonMinute) =
|
|
afternoonSchedules[schedIdx];
|
|
final isLastSchedule = schedIdx == afternoonSchedules.length - 1;
|
|
|
|
final afternoonStart = DateTime(
|
|
now.year,
|
|
now.month,
|
|
now.day,
|
|
afternoonHour,
|
|
afternoonMinute,
|
|
);
|
|
final afternoonEnd = afternoonStart.add(const Duration(minutes: 10));
|
|
final isAfternoonWindow =
|
|
!now.isBefore(afternoonStart) && !now.isAfter(afternoonEnd);
|
|
|
|
final lastTriggerDate = _lastSoreTriggerDateByIndex[index];
|
|
final shouldTrigger = isAfternoonWindow &&
|
|
(lastTriggerDate == null || lastTriggerDate != dateKey);
|
|
|
|
if (shouldTrigger) {
|
|
if (kDebugMode) {
|
|
print(
|
|
'🕒 Trigger runtime panen sore #${index} ($dateKey @ $afternoonHour:${afternoonMinute.toString().padLeft(2, '0')})');
|
|
}
|
|
await PanenAutoCaptureService.triggerAfternoonCapture(
|
|
panenProvider,
|
|
kandangProvider,
|
|
isLastSchedule: isLastSchedule,
|
|
);
|
|
_lastSoreTriggerDateByIndex[index] = dateKey;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('❌ PanenRuntimeSchedulerService tick error: $e');
|
|
}
|
|
} finally {
|
|
_isProcessing = false;
|
|
}
|
|
}
|
|
|
|
static (int, int)? _parseJam(String jam) {
|
|
final parts = jam.split(':');
|
|
if (parts.length < 2) return null;
|
|
final hour = int.tryParse(parts[0]);
|
|
final minute = int.tryParse(parts[1]);
|
|
if (hour == null || minute == null) return null;
|
|
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return null;
|
|
return (hour, minute);
|
|
}
|
|
}
|