Files
sharedinbox/lib/ui/screens/sync_log_screen.dart
T
Thomas GüttlerandClaude Sonnet 4.6 6a457a9f7a fix: IMAP full sync via UID SEARCH+FETCH; add sync log UI
- Replace full-sync fetchMessages(1:*) with UID SEARCH ALL + UID FETCH
  so every message gets a reliable UID on all servers
- Guard CONDSTORE select on server capability to avoid BAD from
  servers that do not advertise CONDSTORE/QRESYNC
- Add SyncLogEntry model + observeSyncLogs stream to SyncLogRepository
- Add SyncLogScreen with per-entry duration/error display
- Wire history icon in SettingsScreen → /accounts/:id/sync-log route
- Fix FakeImapClient to expose initialized serverInfo via field override

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 07:43:30 +02:00

76 lines
2.6 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import '../../core/repositories/sync_log_repository.dart';
import '../../di.dart';
final _timeFmt = DateFormat('MMM d, HH:mm:ss');
class SyncLogScreen extends ConsumerWidget {
const SyncLogScreen({super.key, required this.accountId});
final String accountId;
@override
Widget build(BuildContext context, WidgetRef ref) {
final repo = ref.watch(syncLogRepositoryProvider);
return Scaffold(
appBar: AppBar(title: const Text('Sync log')),
body: StreamBuilder<List<SyncLogEntry>>(
stream: repo.observeSyncLogs(accountId),
builder: (ctx, snap) {
if (!snap.hasData) {
return const Center(child: CircularProgressIndicator());
}
final entries = snap.data!;
if (entries.isEmpty) {
return const Center(child: Text('No sync entries yet'));
}
return ListView.builder(
itemCount: entries.length,
itemBuilder: (ctx, i) {
final e = entries[i];
final ms = e.duration.inMilliseconds;
final durationLabel =
ms < 1000 ? '${ms}ms' : '${(ms / 1000).toStringAsFixed(1)}s';
return ListTile(
leading: Icon(
e.isOk ? Icons.check_circle : Icons.error_outline,
color:
e.isOk ? Colors.green : Theme.of(ctx).colorScheme.error,
),
title: Text(
e.isOk ? 'OK' : 'Error',
style: e.isOk
? null
: TextStyle(color: Theme.of(ctx).colorScheme.error),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(_timeFmt.format(e.startedAt)),
Text('took $durationLabel'),
if (e.errorMessage != null)
Text(
e.errorMessage!,
style: TextStyle(
color: Theme.of(ctx).colorScheme.error,
fontSize: 12,
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
],
),
isThreeLine: e.errorMessage != null,
);
},
);
},
),
);
}
}