Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf6e9c9af3 | ||
|
|
beae8d8843 |
@@ -65,6 +65,10 @@ abstract class SyncLogRepository {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Stream<List<SyncLogEntry>> observeSyncLogs(String accountId);
|
Stream<List<SyncLogEntry>> observeSyncLogs(String accountId);
|
||||||
|
|
||||||
|
/// Emits the error message of the most recent sync attempt for [accountId],
|
||||||
|
/// or null when the last sync succeeded (or no syncs have run yet).
|
||||||
|
Stream<String?> observeLastError(String accountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoOpSyncLogRepository implements SyncLogRepository {
|
class NoOpSyncLogRepository implements SyncLogRepository {
|
||||||
@@ -90,4 +94,7 @@ class NoOpSyncLogRepository implements SyncLogRepository {
|
|||||||
@override
|
@override
|
||||||
Stream<List<SyncLogEntry>> observeSyncLogs(String accountId) =>
|
Stream<List<SyncLogEntry>> observeSyncLogs(String accountId) =>
|
||||||
Stream.value([]);
|
Stream.value([]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<String?> observeLastError(String accountId) => Stream.value(null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,4 +99,14 @@ class SyncLogRepositoryImpl implements SyncLogRepository {
|
|||||||
return entries;
|
return entries;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<String?> observeLastError(String accountId) {
|
||||||
|
return (_db.select(_db.syncLogs)
|
||||||
|
..where((t) => t.accountId.equals(accountId))
|
||||||
|
..orderBy([(t) => OrderingTerm.desc(t.startedAt)])
|
||||||
|
..limit(1))
|
||||||
|
.watchSingleOrNull()
|
||||||
|
.map((row) => (row?.result == 'error') ? row?.errorMessage : null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,11 @@ final syncLogRepositoryProvider = Provider((ref) {
|
|||||||
return SyncLogRepositoryImpl(ref.watch(dbProvider));
|
return SyncLogRepositoryImpl(ref.watch(dbProvider));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final syncLastErrorProvider =
|
||||||
|
StreamProvider.autoDispose.family<String?, String>((ref, accountId) {
|
||||||
|
return ref.watch(syncLogRepositoryProvider).observeLastError(accountId);
|
||||||
|
});
|
||||||
|
|
||||||
final reliabilityRunnerProvider = Provider<ReliabilityRunner>((ref) {
|
final reliabilityRunnerProvider = Provider<ReliabilityRunner>((ref) {
|
||||||
final runner = ReliabilityRunner(
|
final runner = ReliabilityRunner(
|
||||||
ref.watch(dbProvider),
|
ref.watch(dbProvider),
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ class _EmailListScreenState extends ConsumerState<EmailListScreen> {
|
|||||||
bool _searchLoading = false;
|
bool _searchLoading = false;
|
||||||
bool get _searching => _searchController.text.isNotEmpty;
|
bool get _searching => _searchController.text.isNotEmpty;
|
||||||
|
|
||||||
|
// Error banner — tracks the last error message that the user dismissed.
|
||||||
|
String? _dismissedError;
|
||||||
|
|
||||||
// Thread-level selection (key = threadId).
|
// Thread-level selection (key = threadId).
|
||||||
final Set<String> _selectedThreadIds = {};
|
final Set<String> _selectedThreadIds = {};
|
||||||
// Last-emitted thread list, used to resolve emailIds for batch operations.
|
// Last-emitted thread list, used to resolve emailIds for batch operations.
|
||||||
@@ -131,9 +134,16 @@ class _EmailListScreenState extends ConsumerState<EmailListScreen> {
|
|||||||
currentMailboxPath: widget.mailboxPath,
|
currentMailboxPath: widget.mailboxPath,
|
||||||
),
|
),
|
||||||
bottomNavigationBar: _selecting ? _selectionBottomBar() : null,
|
bottomNavigationBar: _selecting ? _selectionBottomBar() : null,
|
||||||
body: (_searchResults != null || _searchLoading)
|
body: Column(
|
||||||
? _buildSearchBody()
|
children: [
|
||||||
: _buildStreamBody(repo),
|
_buildSyncErrorBanner(),
|
||||||
|
Expanded(
|
||||||
|
child: (_searchResults != null || _searchLoading)
|
||||||
|
? _buildSearchBody()
|
||||||
|
: _buildStreamBody(repo),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,6 +277,39 @@ class _EmailListScreenState extends ConsumerState<EmailListScreen> {
|
|||||||
return _buildEmailList(_searchResults!);
|
return _buildEmailList(_searchResults!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildSyncErrorBanner() {
|
||||||
|
final errorAsync = ref.watch(syncLastErrorProvider(widget.accountId));
|
||||||
|
final error = errorAsync.valueOrNull;
|
||||||
|
if (error == null || error == _dismissedError) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return MaterialBanner(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 8, 8, 8),
|
||||||
|
content: Text(
|
||||||
|
error,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
leading: Icon(
|
||||||
|
Icons.sync_problem,
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.errorContainer,
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
ref.read(syncManagerProvider).syncNow(widget.accountId);
|
||||||
|
},
|
||||||
|
child: const Text('Retry'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => setState(() => _dismissedError = error),
|
||||||
|
child: const Text('Dismiss'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildStreamBody(EmailRepository emailRepo) {
|
Widget _buildStreamBody(EmailRepository emailRepo) {
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
|
|||||||
@@ -220,4 +220,7 @@ class _FakeLogs implements SyncLogRepository {
|
|||||||
@override
|
@override
|
||||||
Stream<List<SyncLogEntry>> observeSyncLogs(String accountId) =>
|
Stream<List<SyncLogEntry>> observeSyncLogs(String accountId) =>
|
||||||
Stream.value([]);
|
Stream.value([]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<String?> observeLastError(String accountId) => Stream.value(null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,6 +132,9 @@ class FakeSyncLogRepository implements SyncLogRepository {
|
|||||||
@override
|
@override
|
||||||
Stream<List<SyncLogEntry>> observeSyncLogs(String accountId) =>
|
Stream<List<SyncLogEntry>> observeSyncLogs(String accountId) =>
|
||||||
Stream.value([]);
|
Stream.value([]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<String?> observeLastError(String accountId) => Stream.value(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeMailboxRepositoryWithInbox implements MailboxRepository {
|
class FakeMailboxRepositoryWithInbox implements MailboxRepository {
|
||||||
|
|||||||
Reference in New Issue
Block a user