Offline Support
This commit is contained in:
@@ -1,19 +1,13 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
import 'stream_recovery.dart';
|
||||
import 'supabase_provider.dart';
|
||||
|
||||
final realtimeControllerProvider = ChangeNotifierProvider<RealtimeController>((
|
||||
ref,
|
||||
) {
|
||||
final client = ref.watch(supabaseClientProvider);
|
||||
// ChangeNotifierProvider automatically disposes the notifier; no need
|
||||
// for ref.onDispose here — adding it causes a double-dispose assertion.
|
||||
return RealtimeController(client);
|
||||
return RealtimeController();
|
||||
});
|
||||
|
||||
/// Per-channel realtime controller for UI skeleton indicators.
|
||||
@@ -27,42 +21,12 @@ final realtimeControllerProvider = ChangeNotifierProvider<RealtimeController>((
|
||||
/// - Per-channel recovering state for pinpoint skeleton indicators
|
||||
/// - Auth token refreshes for realtime connections
|
||||
class RealtimeController extends ChangeNotifier {
|
||||
final SupabaseClient _client;
|
||||
bool _disposed = false;
|
||||
|
||||
/// Channels currently in a recovering/polling/stale state.
|
||||
final Set<String> _recoveringChannels = {};
|
||||
|
||||
StreamSubscription<AuthState>? _authSub;
|
||||
|
||||
RealtimeController(this._client) {
|
||||
_init();
|
||||
}
|
||||
|
||||
void _init() {
|
||||
try {
|
||||
_authSub = _client.auth.onAuthStateChange.listen((data) {
|
||||
final event = data.event;
|
||||
if (event == AuthChangeEvent.tokenRefreshed) {
|
||||
_ensureTokenFresh();
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
debugPrint('RealtimeController._init error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _ensureTokenFresh() async {
|
||||
if (_disposed) return;
|
||||
try {
|
||||
final authDynamic = _client.auth as dynamic;
|
||||
if (authDynamic.refreshSession != null) {
|
||||
await authDynamic.refreshSession?.call();
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('RealtimeController: token refresh failed: $e');
|
||||
}
|
||||
}
|
||||
RealtimeController();
|
||||
|
||||
// ── Per-channel status ─────────────────────────────────────────────────
|
||||
|
||||
@@ -97,13 +61,17 @@ class RealtimeController extends ChangeNotifier {
|
||||
|
||||
/// Convenience callback suitable for [StreamRecoveryWrapper.onStatusChanged].
|
||||
///
|
||||
/// Routes [StreamConnectionStatus] to the appropriate mark method.
|
||||
/// Both `connected` and `polling` are treated as "recovered" because
|
||||
/// polling is a functional fallback that still delivers data — the user
|
||||
/// doesn't need to see a reconnection indicator while data flows via REST.
|
||||
/// Routes [StreamConnectionStatus] to the appropriate mark method:
|
||||
/// - `connected` — live stream; no indicator needed.
|
||||
/// - `polling` — REST fallback; data is fresh, no skeleton needed.
|
||||
/// - `offline` — device has no internet; Brick cache is active. The offline
|
||||
/// banner already communicates this state, so the reconnection skeleton
|
||||
/// should NOT show — it would be misleading while real data is on screen.
|
||||
/// - Everything else (`recovering`, `stale`, `failed`) — show skeleton.
|
||||
void handleChannelStatus(String channel, StreamConnectionStatus status) {
|
||||
if (status == StreamConnectionStatus.connected ||
|
||||
status == StreamConnectionStatus.polling) {
|
||||
status == StreamConnectionStatus.polling ||
|
||||
status == StreamConnectionStatus.offline) {
|
||||
markChannelRecovered(channel);
|
||||
} else {
|
||||
markChannelRecovering(channel);
|
||||
@@ -126,7 +94,6 @@ class RealtimeController extends ChangeNotifier {
|
||||
@override
|
||||
void dispose() {
|
||||
_disposed = true;
|
||||
_authSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user