From c45775be92285452b4c1aa2d4c2afd370cd7e013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bot=20of=20Thomas=20G=C3=BCttler?= Date: Thu, 28 May 2026 06:53:11 +0200 Subject: [PATCH] fix: move sync health report to own row below each account (#311) (#322) --- lib/ui/screens/account_list_screen.dart | 143 +++++++++++----------- scripts/agent_loop.py | 8 +- test/widget/account_list_screen_test.dart | 24 ++++ 3 files changed, 102 insertions(+), 73 deletions(-) diff --git a/lib/ui/screens/account_list_screen.dart b/lib/ui/screens/account_list_screen.dart index f013f29..5ea80d5 100644 --- a/lib/ui/screens/account_list_screen.dart +++ b/lib/ui/screens/account_list_screen.dart @@ -120,15 +120,76 @@ class _AccountTile extends ConsumerWidget { final health = ref.watch(syncHealthProvider(account.id)); final typeLabel = account.type == AccountType.jmap ? 'JMAP' : 'IMAP'; - return ListTile( - leading: const Icon(Icons.account_circle), - title: Text(account.displayName), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('${account.email}\n$typeLabel'), - const SizedBox(height: 4), - health.when( + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListTile( + leading: const Icon(Icons.account_circle), + title: Text(account.displayName), + subtitle: Text('${account.email}\n$typeLabel'), + isThreeLine: true, + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + status.when( + loading: () => const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ), + data: (_) => + const Icon(Icons.check_circle, color: Colors.green), + error: (e, _) => Tooltip( + message: e.toString(), + child: const Icon(Icons.error_outline, color: Colors.red), + ), + ), + PopupMenuButton<_AccountAction>( + onSelected: (action) => _onAction(context, action), + itemBuilder: (_) => [ + const PopupMenuItem( + value: _AccountAction.syncLog, + child: Text('Sync log'), + ), + const PopupMenuItem( + value: _AccountAction.verifySync, + child: Text('Verify sync health'), + ), + const PopupMenuItem( + value: _AccountAction.forceSync, + child: Text('Force full sync'), + ), + const PopupMenuItem( + value: _AccountAction.edit, + child: Text('Edit'), + ), + if (_sieveSupported(account)) + const PopupMenuItem( + value: _AccountAction.emailFiltersRemote, + child: Text('Server email filters'), + ), + const PopupMenuItem( + value: _AccountAction.emailFiltersLocal, + child: Text('Local email filters'), + ), + const PopupMenuItem( + value: _AccountAction.send, + child: Text('Send accounts'), + ), + const PopupMenuDivider(), + const PopupMenuItem( + value: _AccountAction.delete, + child: Text('Delete'), + ), + ], + ), + ], + ), + onTap: () => context.push('/accounts/${account.id}/mailboxes'), + ), + Padding( + padding: const EdgeInsets.fromLTRB(72, 0, 16, 8), + child: health.when( data: (h) { if (h == null) return const Text('Sync health: Not verified yet'); final date = h.lastVerifiedAt.toLocal().toString().split('.')[0]; @@ -141,7 +202,7 @@ class _AccountTile extends ConsumerWidget { color: h.isHealthy ? Colors.green : Colors.orange, ), const SizedBox(width: 4), - Flexible( + Expanded( child: Text( h.isHealthy ? 'Healthy' @@ -155,66 +216,8 @@ class _AccountTile extends ConsumerWidget { loading: () => const Text('Sync health: checking...'), error: (e, _) => Text('Sync health error: $e'), ), - ], - ), - isThreeLine: true, - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - status.when( - loading: () => const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator(strokeWidth: 2), - ), - data: (_) => const Icon(Icons.check_circle, color: Colors.green), - error: (e, _) => Tooltip( - message: e.toString(), - child: const Icon(Icons.error_outline, color: Colors.red), - ), - ), - PopupMenuButton<_AccountAction>( - onSelected: (action) => _onAction(context, action), - itemBuilder: (_) => [ - const PopupMenuItem( - value: _AccountAction.syncLog, - child: Text('Sync log'), - ), - const PopupMenuItem( - value: _AccountAction.verifySync, - child: Text('Verify sync health'), - ), - const PopupMenuItem( - value: _AccountAction.forceSync, - child: Text('Force full sync'), - ), - const PopupMenuItem( - value: _AccountAction.edit, - child: Text('Edit'), - ), - if (_sieveSupported(account)) - const PopupMenuItem( - value: _AccountAction.emailFiltersRemote, - child: Text('Server email filters'), - ), - const PopupMenuItem( - value: _AccountAction.emailFiltersLocal, - child: Text('Local email filters'), - ), - const PopupMenuItem( - value: _AccountAction.send, - child: Text('Send accounts'), - ), - const PopupMenuDivider(), - const PopupMenuItem( - value: _AccountAction.delete, - child: Text('Delete'), - ), - ], - ), - ], - ), - onTap: () => context.push('/accounts/${account.id}/mailboxes'), + ), + ], ); } diff --git a/scripts/agent_loop.py b/scripts/agent_loop.py index 7c49db5..f473e0b 100755 --- a/scripts/agent_loop.py +++ b/scripts/agent_loop.py @@ -912,9 +912,11 @@ def _run_loop() -> int: if not m: continue issue_num = int(m.group(1)) - labels = _get_issue_labels(issue_num) - if not labels: - # Issue is likely already closed — skip. + try: + issue_data = _tea_get(f"/repos/{REPO}/issues/{issue_num}") + except RuntimeError: + continue + if issue_data.get("state") != "open": continue pr_number = pr["number"] print(f"Catch-up (merged PR): PR #{pr_number} for issue #{issue_num} was merged — closing.") diff --git a/test/widget/account_list_screen_test.dart b/test/widget/account_list_screen_test.dart index ba52d33..d4159fe 100644 --- a/test/widget/account_list_screen_test.dart +++ b/test/widget/account_list_screen_test.dart @@ -252,5 +252,29 @@ void main() { expect(find.textContaining('flag mismatches: 1'), findsOneWidget); }, ); + + testWidgets( + 'sync health row is positioned below the account name row', + (tester) async { + await tester.pumpWidget( + buildApp( + initialLocation: '/accounts', + overrides: baseOverrides( + accounts: [kTestAccount], + syncHealth: SyncHealthRow( + accountId: kTestAccount.id, + lastVerifiedAt: DateTime(2024, 6), + isHealthy: true, + ), + ), + ), + ); + await tester.pumpAndSettle(); + + final namePos = tester.getTopLeft(find.text('Alice')).dy; + final healthPos = tester.getTopLeft(find.textContaining('Healthy')).dy; + expect(healthPos, greaterThan(namePos)); + }, + ); }); }