Offline Support
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart' show debugPrint;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
import '../brick/cache_warmer.dart';
|
||||
import '../brick/repository.dart';
|
||||
import 'stream_recovery.dart';
|
||||
|
||||
/// Current online/offline status.
|
||||
/// Defaults to true so the UI doesn't flash an offline banner on startup.
|
||||
final isOnlineProvider = StateProvider<bool>((ref) => true);
|
||||
|
||||
/// Activating this provider starts a periodic connectivity poll.
|
||||
/// Watch it from the root widget to keep it alive.
|
||||
final connectivityMonitorProvider = Provider.autoDispose<void>((ref) {
|
||||
// Wire up the global online check for StreamRecoveryWrapper so all stream
|
||||
// wrappers can suppress recovery while offline without per-instance setup.
|
||||
StreamRecoveryWrapper.setIsOnlineCallback(() => ref.read(isOnlineProvider));
|
||||
|
||||
bool previouslyOnline = true;
|
||||
|
||||
Future<void> check() async {
|
||||
final online = await ConnectivityMonitor.check();
|
||||
final wasOnline = ref.read(isOnlineProvider);
|
||||
if (online != wasOnline) {
|
||||
ref.read(isOnlineProvider.notifier).state = online;
|
||||
}
|
||||
if (previouslyOnline && !online) {
|
||||
_onDisconnected();
|
||||
}
|
||||
if (!previouslyOnline && online) {
|
||||
_onReconnected();
|
||||
}
|
||||
previouslyOnline = online;
|
||||
}
|
||||
|
||||
// Run immediately, then every 5 seconds.
|
||||
check();
|
||||
final timer = Timer.periodic(const Duration(seconds: 5), (_) => check());
|
||||
ref.onDispose(timer.cancel);
|
||||
});
|
||||
|
||||
/// Pings the app's own backend to determine online status.
|
||||
class ConnectivityMonitor {
|
||||
static Future<bool> check() async {
|
||||
try {
|
||||
final res = await http
|
||||
.head(Uri.parse('https://tasq.crmc.ph'))
|
||||
.timeout(const Duration(seconds: 5));
|
||||
return res.statusCode < 500;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _onDisconnected() {
|
||||
debugPrint('[Connectivity] Went offline — pausing Brick request queue');
|
||||
// Stop the retry loop so the queue doesn't spam network errors while offline.
|
||||
// Queued writes are preserved in SQLite and will sync when _onReconnected fires.
|
||||
AppRepository.offlineQueue?.stop();
|
||||
}
|
||||
|
||||
void _onReconnected() {
|
||||
debugPrint('[Connectivity] Back online — queued writes will sync via Brick');
|
||||
if (!AppRepository.isConfigured) return;
|
||||
|
||||
// Resume the Brick HTTP queue so queued writes (e.g. offline task creation)
|
||||
// are replayed against Supabase now that the network is available again.
|
||||
AppRepository.offlineQueue?.start();
|
||||
|
||||
// Restart any Supabase realtime streams that were suppressed while offline.
|
||||
// Streams that disconnected due to no network are NOT in an error-retry loop
|
||||
// (recovery was suppressed); this kick-starts them back to live updates.
|
||||
StreamRecoveryWrapper.notifyAllOnlineRestored();
|
||||
|
||||
// Re-warm the Brick cache so the local SQLite mirror reflects any changes
|
||||
// that happened on the server while we were offline. Fire-and-forget — it
|
||||
// runs in the background and never throws.
|
||||
final user = Supabase.instance.client.auth.currentUser;
|
||||
if (user != null) {
|
||||
CacheWarmer.warmAll(Supabase.instance.client);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user