feat: add-account wizard, edit account, inbox tap, connection status
- Add account wizard: email-first flow with JMAP/IMAP auto-detection via well-known URLs; falls back to manual type selection - Fix JMAP connection probe: GET session URL with Basic auth instead of the API endpoint, so 401 reliably signals bad credentials - Account list tile: tap → open INBOX directly; popup menu for all mailboxes / edit / delete (with confirmation dialog) - Show account type (JMAP/IMAP) and async connection status per tile: spinner while checking, green check on success, red error on failure - Add EditAccountScreen: edit name, password, server settings; runs connection test only when password is changed - Fix GTK window initialisation order so app starts with correct size - Fix 42 lint issues (avoid_redundant_argument_values, unnecessary_non_null_assertion, unawaited_futures) - 147 tests, 87% coverage, task check green Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
e2a87fc2b0
commit
442c3c4087
@@ -9,14 +9,19 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:sharedinbox/core/models/account.dart';
|
||||
import 'package:sharedinbox/core/models/discovery_result.dart';
|
||||
import 'package:sharedinbox/core/models/email.dart';
|
||||
import 'package:sharedinbox/core/models/mailbox.dart';
|
||||
import 'package:sharedinbox/core/repositories/account_repository.dart';
|
||||
import 'package:sharedinbox/core/repositories/email_repository.dart';
|
||||
import 'package:sharedinbox/core/repositories/mailbox_repository.dart';
|
||||
import 'package:sharedinbox/core/services/account_discovery_service.dart';
|
||||
import 'package:sharedinbox/core/services/connection_test_service.dart';
|
||||
import 'package:sharedinbox/di.dart';
|
||||
import 'package:sharedinbox/ui/screens/account_list_screen.dart';
|
||||
import 'package:sharedinbox/ui/screens/add_account_screen.dart';
|
||||
import 'package:sharedinbox/ui/screens/compose_screen.dart';
|
||||
import 'package:sharedinbox/ui/screens/edit_account_screen.dart';
|
||||
import 'package:sharedinbox/ui/screens/email_detail_screen.dart';
|
||||
import 'package:sharedinbox/ui/screens/email_list_screen.dart';
|
||||
import 'package:sharedinbox/ui/screens/mailbox_list_screen.dart';
|
||||
@@ -47,6 +52,12 @@ class FakeAccountRepository implements AccountRepository {
|
||||
Future<void> addAccount(Account account, String password) async =>
|
||||
_accounts.add(account);
|
||||
|
||||
@override
|
||||
Future<void> updateAccount(Account account, {String? password}) async {
|
||||
final idx = _accounts.indexWhere((a) => a.id == account.id);
|
||||
if (idx >= 0) _accounts[idx] = account;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> removeAccount(String id) async =>
|
||||
_accounts.removeWhere((a) => a.id == id);
|
||||
@@ -121,6 +132,28 @@ class FakeEmailRepository implements EmailRepository {
|
||||
_searchResults;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fake services
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class FakeDiscoveryService implements AccountDiscoveryService {
|
||||
FakeDiscoveryService(this._result);
|
||||
final DiscoveryResult _result;
|
||||
|
||||
@override
|
||||
Future<DiscoveryResult> discover(String email) async => _result;
|
||||
}
|
||||
|
||||
class FakeConnectionTestService implements ConnectionTestService {
|
||||
FakeConnectionTestService({Exception? error}) : _error = error;
|
||||
final Exception? _error;
|
||||
|
||||
@override
|
||||
Future<void> testConnection(Account account, String password) async {
|
||||
if (_error != null) throw _error;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// App builder
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -144,6 +177,12 @@ Widget buildApp({
|
||||
path: 'add',
|
||||
builder: (ctx, state) => const AddAccountScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: ':accountId/edit',
|
||||
builder: (ctx, state) => EditAccountScreen(
|
||||
accountId: state.pathParameters['accountId']!,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: ':accountId/mailboxes',
|
||||
builder: (ctx, state) => MailboxListScreen(
|
||||
@@ -202,6 +241,29 @@ Widget buildApp({
|
||||
);
|
||||
}
|
||||
|
||||
/// Convenience override list used by most widget tests.
|
||||
///
|
||||
/// Includes fakes for all repositories and the two network services so tests
|
||||
/// never hit the real database, network, or IMAP server.
|
||||
List<Override> baseOverrides({
|
||||
List<Account>? accounts,
|
||||
List<Mailbox>? mailboxes,
|
||||
DiscoveryResult? discovery,
|
||||
Exception? connectionError,
|
||||
}) =>
|
||||
[
|
||||
accountRepositoryProvider
|
||||
.overrideWithValue(FakeAccountRepository(accounts)),
|
||||
mailboxRepositoryProvider.overrideWithValue(FakeMailboxRepository(mailboxes)),
|
||||
emailRepositoryProvider.overrideWithValue(FakeEmailRepository()),
|
||||
accountDiscoveryServiceProvider.overrideWithValue(
|
||||
FakeDiscoveryService(discovery ?? UnknownDiscovery()),
|
||||
),
|
||||
connectionTestServiceProvider.overrideWithValue(
|
||||
FakeConnectionTestService(error: connectionError),
|
||||
),
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Common test fixtures
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -211,11 +273,7 @@ const kTestAccount = Account(
|
||||
displayName: 'Alice',
|
||||
email: 'alice@example.com',
|
||||
imapHost: 'imap.example.com',
|
||||
imapPort: 993,
|
||||
imapSsl: true,
|
||||
smtpHost: 'smtp.example.com',
|
||||
smtpPort: 587,
|
||||
smtpSsl: false,
|
||||
);
|
||||
|
||||
const kTestMailbox = Mailbox(
|
||||
|
||||
Reference in New Issue
Block a user