Harden app for 24-7 offline-first operation

This commit is contained in:
dwindown
2026-03-31 14:37:14 +07:00
parent 49f130b5ea
commit 081ed9f695
9 changed files with 289 additions and 17 deletions

View File

@@ -44,12 +44,15 @@ class _HomeViewState extends ConsumerState<HomeView> {
final List<LogicalKeyboardKey> _simulationShortcutKeys = [];
Timer? _comboResetTimer;
Timer? _simulationShortcutTimer;
Timer? _autoRefreshTimer;
bool _isAutoRefreshRunning = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_checkAutoSync();
_startAutoRefreshMonitor();
if (mounted) {
_homeFocusNode.requestFocus();
}
@@ -60,21 +63,38 @@ class _HomeViewState extends ConsumerState<HomeView> {
void dispose() {
_comboResetTimer?.cancel();
_simulationShortcutTimer?.cancel();
_autoRefreshTimer?.cancel();
_homeFocusNode.dispose();
super.dispose();
}
void _startAutoRefreshMonitor() {
_autoRefreshTimer?.cancel();
_autoRefreshTimer = Timer.periodic(
const Duration(hours: 6),
(_) => _checkAutoSync(),
);
}
Future<void> _checkAutoSync() async {
final schedule = ref.read(todayScheduleProvider);
if (schedule == null) {
debugPrint('[AutoSync] No schedule found for today! Starting auto-sync...');
final success = await SyncService.instance.syncMonthlyData();
if (success && mounted) {
debugPrint('[AutoSync] Success! Invalidating todayScheduleProvider.');
if (_isAutoRefreshRunning || !mounted) return;
_isAutoRefreshRunning = true;
try {
final result = await SyncService.instance.autoRefreshIfNeeded();
if (!mounted) return;
if (result.synced) {
debugPrint('[AutoSync] Cache refreshed successfully.');
ref.invalidate(todayScheduleProvider);
} else {
debugPrint('[AutoSync] Failed or data remained empty.');
ref.invalidate(scheduleCacheStatusProvider);
return;
}
if (result.attempted) {
debugPrint('[AutoSync] Refresh attempt failed. Staying on local cache.');
}
} finally {
_isAutoRefreshRunning = false;
}
}

View File

@@ -49,6 +49,7 @@ class JumatScreen extends ConsumerWidget {
fit: BoxFit.cover,
color: Colors.black.withValues(alpha: 0.55),
colorBlendMode: BlendMode.darken,
errorBuilder: (_, __, ___) => const UnsplashBackground(),
),
)
else

View File

@@ -53,6 +53,7 @@ class MainScreen extends ConsumerWidget {
fit: BoxFit.cover,
color: Colors.black.withValues(alpha: 0.55),
colorBlendMode: BlendMode.darken,
errorBuilder: (_, __, ___) => const UnsplashBackground(),
),
)
else

View File

@@ -96,6 +96,7 @@ class _UnsplashBackgroundState extends ConsumerState<UnsplashBackground> {
// Soft opacity behind the MainScreen's dark glass vignette
color: Colors.black.withValues(alpha: 0.5),
colorBlendMode: BlendMode.darken,
errorBuilder: (_, __, ___) => const SizedBox.shrink(),
),
);
}