TKK_E32231105/lib/services/panen_runtime_scheduler_ser...

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);
}
}