Offline Support

This commit is contained in:
2026-04-27 06:20:39 +08:00
parent 7d8851a94a
commit 48cd3bcd60
121 changed files with 14798 additions and 2214 deletions
+53
View File
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:awesome_snackbar_content/awesome_snackbar_content.dart';
import 'package:flutter/foundation.dart';
import 'package:supabase_flutter/supabase_flutter.dart' show PostgrestException;
/// A global messenger key used to show snackbars from contexts without a
/// Scaffold (e.g. dialogs, background callbacks, or tests).
@@ -218,3 +219,55 @@ void showWarningSnackBarGlobal(String message) => showAwesomeSnackBarGlobal(
message: message,
snackType: SnackType.warning,
);
/// Returns true when [error] is a network-connectivity failure (socket closed,
/// no route to host, failed DNS lookup, etc.).
///
/// When this is true in a Brick-enabled app, the write was already queued in
/// the offline SQLite store and will sync automatically when the device
/// reconnects — so the UI should treat it as a success rather than an error.
bool isOfflineSaveError(Object error) {
if (error is PostgrestException) {
// Null code = no HTTP response received at all (pure network failure).
if (error.code == null) return true;
// Brick's RestOfflineQueueClient catches network errors and returns a
// synthetic StreamedResponse(501, body: 'unknown internal error') rather
// than rethrowing. PostgREST then parses this as
// PostgrestException(code: '501', message: 'unknown internal error').
// This is the primary offline indicator when Supabase is used with Brick.
if (error.code == '501' && error.message == 'unknown internal error') {
return true;
}
// Fallback: Supabase may wrap a SocketException in a PostgrestException
// message field before it reaches Brick's queue (rare, but defensive).
final pmsg = error.message.toLowerCase();
if (pmsg.contains('socketexception') ||
pmsg.contains('failed host lookup') ||
pmsg.contains('connection refused') ||
pmsg.contains('network is unreachable') ||
pmsg.contains('no route to host') ||
pmsg.contains('connection timed out') ||
pmsg.contains('failed to connect')) {
return true;
}
}
// Non-PostgrestException network errors (SocketException, ClientException)
// that propagate before Brick's HTTP interceptor can catch them.
final msg = error.toString().toLowerCase();
return msg.contains('socketexception') ||
msg.contains('socket') && msg.contains('exception') ||
msg.contains('connection refused') ||
msg.contains('network is unreachable') ||
msg.contains('no route to host') ||
msg.contains('failed host lookup') ||
msg.contains('connection reset') ||
msg.contains('broken pipe') ||
msg.contains('connection timed out') ||
msg.contains('clientexception') && msg.contains('network') ||
msg.contains('failed to connect') ||
msg.contains('errno = 111') || // ECONNREFUSED
msg.contains('errno = 101'); // ENETUNREACH
}