Offline Support
This commit is contained in:
@@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
import 'connectivity_provider.dart';
|
||||
import 'supabase_provider.dart';
|
||||
|
||||
class TypingIndicatorState {
|
||||
@@ -37,24 +38,47 @@ final typingIndicatorProvider =
|
||||
String
|
||||
>((ref, ticketId) {
|
||||
final client = ref.watch(supabaseClientProvider);
|
||||
final controller = TypingIndicatorController(client, ticketId);
|
||||
final controller = TypingIndicatorController(
|
||||
client,
|
||||
ticketId,
|
||||
isOnline: () => ref.read(isOnlineProvider),
|
||||
);
|
||||
|
||||
// Pause the realtime channel when the device goes offline so Supabase's
|
||||
// internal reconnection loop stops firing WebSocket errors.
|
||||
// Resume (re-subscribe) when connectivity returns.
|
||||
ref.listen<bool>(isOnlineProvider, (previous, next) {
|
||||
if (next == false) {
|
||||
controller.pauseForOffline();
|
||||
} else if (previous != true && next == true) {
|
||||
// previous is null on first call after a provider rebuild — treat
|
||||
// null and false both as "was offline" to ensure the channel resumes.
|
||||
controller.resumeFromOffline();
|
||||
}
|
||||
});
|
||||
|
||||
return controller;
|
||||
});
|
||||
|
||||
class TypingIndicatorController extends StateNotifier<TypingIndicatorState> {
|
||||
TypingIndicatorController(this._client, this._ticketId)
|
||||
: super(
|
||||
const TypingIndicatorState(
|
||||
userIds: {},
|
||||
channelStatus: 'init',
|
||||
lastPayload: {},
|
||||
),
|
||||
) {
|
||||
TypingIndicatorController(
|
||||
this._client,
|
||||
this._ticketId, {
|
||||
bool Function()? isOnline,
|
||||
}) : _isOnline = isOnline ?? (() => true),
|
||||
super(
|
||||
const TypingIndicatorState(
|
||||
userIds: {},
|
||||
channelStatus: 'init',
|
||||
lastPayload: {},
|
||||
),
|
||||
) {
|
||||
_initChannel();
|
||||
}
|
||||
|
||||
final SupabaseClient _client;
|
||||
final String _ticketId;
|
||||
final bool Function() _isOnline;
|
||||
RealtimeChannel? _channel;
|
||||
Timer? _typingTimer;
|
||||
final Map<String, Timer> _remoteTimeouts = {};
|
||||
@@ -94,7 +118,9 @@ class TypingIndicatorController extends StateNotifier<TypingIndicatorState> {
|
||||
try {
|
||||
if (_disposed || !mounted) return;
|
||||
state = state.copyWith(channelStatus: status.name);
|
||||
if (error != null) {
|
||||
if (error != null && _isOnline()) {
|
||||
// Only log when online — offline WebSocket errors are expected and
|
||||
// handled by pauseForOffline() which unsubscribes the channel.
|
||||
debugPrint('TypingIndicatorController: subscribe error: $error');
|
||||
}
|
||||
} catch (e, st) {
|
||||
@@ -235,6 +261,31 @@ class TypingIndicatorController extends StateNotifier<TypingIndicatorState> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Called when the device goes offline.
|
||||
///
|
||||
/// Unsubscribes the Supabase channel so its internal WebSocket reconnection
|
||||
/// loop stops firing errors. The channel object is retained so
|
||||
/// [resumeFromOffline] can cleanly re-subscribe on the same ticket.
|
||||
void pauseForOffline() {
|
||||
if (_disposed) return;
|
||||
debugPrint('TypingIndicatorController[$_ticketId]: offline — unsubscribing');
|
||||
_channel?.unsubscribe();
|
||||
}
|
||||
|
||||
/// Called when the device comes back online.
|
||||
///
|
||||
/// Tears down the stale channel and opens a fresh subscription so broadcast
|
||||
/// events are received again without any missed-message gaps.
|
||||
void resumeFromOffline() {
|
||||
if (_disposed) return;
|
||||
debugPrint(
|
||||
'TypingIndicatorController[$_ticketId]: back online — resubscribing',
|
||||
);
|
||||
_channel?.unsubscribe();
|
||||
_channel = null;
|
||||
_initChannel();
|
||||
}
|
||||
|
||||
// Exposed for tests only: simulate a remote typing broadcast.
|
||||
@visibleForTesting
|
||||
void debugSimulateRemoteTyping(String userId, {bool stop = false}) {
|
||||
|
||||
Reference in New Issue
Block a user