Compare commits

...
Author SHA1 Message Date
Thomas SharedInboxandClaude Sonnet 4.6 7d69740626 feat: onboarding walkthrough for first-time users (U7)
Replace the bare "No accounts yet." empty state with a three-step
welcome card (Add account → Wait for sync → Open inbox) so new users
have clear guidance from the first launch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:40:48 +02:00
3 changed files with 111 additions and 18 deletions
+107 -14
View File
@@ -60,20 +60,7 @@ class AccountListScreen extends ConsumerWidget {
}
final accounts = snap.data!;
if (accounts.isEmpty) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('No accounts yet.'),
const SizedBox(height: 12),
FilledButton.icon(
onPressed: () => context.push('/accounts/add'),
icon: const Icon(Icons.add),
label: const Text('Add account'),
),
],
),
);
return const _OnboardingView();
}
return ListView.builder(
itemCount: accounts.length,
@@ -233,6 +220,112 @@ class _AccountTile extends ConsumerWidget {
}
}
class _OnboardingView extends StatelessWidget {
const _OnboardingView();
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.mail_outline,
size: 64,
color: theme.colorScheme.primary,
),
const SizedBox(height: 16),
Text(
'Welcome to SharedInbox',
style: theme.textTheme.headlineSmall,
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'Get started in three steps:',
style: theme.textTheme.bodyMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
const _Step(
number: '1',
title: 'Add an account',
description: 'Connect your IMAP or JMAP email account.',
),
const _Step(
number: '2',
title: 'Wait for sync',
description:
'SharedInbox downloads your messages in the background.',
),
const _Step(
number: '3',
title: 'Open your inbox',
description:
'Tap the account to browse mailboxes and read emails.',
),
const SizedBox(height: 32),
FilledButton.icon(
onPressed: () => context.push('/accounts/add'),
icon: const Icon(Icons.add),
label: const Text('Add account'),
),
],
),
),
);
}
}
class _Step extends StatelessWidget {
const _Step({
required this.number,
required this.title,
required this.description,
});
final String number;
final String title;
final String description;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
radius: 16,
backgroundColor: theme.colorScheme.primaryContainer,
child: Text(
number,
style: TextStyle(
color: theme.colorScheme.onPrimaryContainer,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: theme.textTheme.titleSmall),
Text(description, style: theme.textTheme.bodySmall),
],
),
),
],
),
);
}
}
enum _AccountAction { syncLog, verifySync, edit, emailFilters, delete }
/// Whether to surface the "Email filters" (Sieve) entry for [account].
+2 -2
View File
@@ -5,7 +5,7 @@ import 'helpers.dart';
void main() {
group('AccountListScreen', () {
testWidgets('shows "No accounts yet." when repository is empty', (
testWidgets('shows onboarding walkthrough when repository is empty', (
tester,
) async {
await tester.pumpWidget(
@@ -13,7 +13,7 @@ void main() {
);
await tester.pumpAndSettle();
expect(find.text('No accounts yet.'), findsOneWidget);
expect(find.text('Welcome to SharedInbox'), findsOneWidget);
expect(find.text('Add account'), findsOneWidget);
});
+2 -2
View File
@@ -203,7 +203,7 @@ void main() {
await tester.tap(find.text('Save'));
await tester.pumpAndSettle();
expect(find.text('No accounts yet.'), findsOneWidget);
expect(find.text('Welcome to SharedInbox'), findsOneWidget);
});
testWidgets('JMAP connection failure shows error message', (tester) async {
@@ -284,7 +284,7 @@ void main() {
await tester.tap(find.text('Save'));
await tester.pumpAndSettle();
expect(find.text('No accounts yet.'), findsOneWidget);
expect(find.text('Welcome to SharedInbox'), findsOneWidget);
});
testWidgets(