Skip to content

Commit ee612c6

Browse files
committed
feat(authentication): add account linking page for anonymous users
- Create a new page for anonymous users to link their account to an email - Display options for account linking in a modal style - Show loading indicator and error handling - Add localization support for page content
1 parent 0bcaa26 commit ee612c6

File tree

1 file changed

+128
-0
lines changed

1 file changed

+128
-0
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_bloc/flutter_bloc.dart';
3+
import 'package:flutter_news_app_mobile_client_full_source_code/authentication/bloc/authentication_bloc.dart';
4+
import 'package:flutter_news_app_mobile_client_full_source_code/l10n/l10n.dart';
5+
import 'package:flutter_news_app_mobile_client_full_source_code/router/routes.dart';
6+
import 'package:go_router/go_router.dart';
7+
import 'package:ui_kit/ui_kit.dart';
8+
9+
/// {@template account_linking_page}
10+
/// Displays options for an anonymous user to link their account to an email.
11+
///
12+
/// This page is presented modally to indicate it's an in-app process
13+
/// rather than a full sign-out/sign-in flow.
14+
/// {@endtemplate}
15+
class AccountLinkingPage extends StatelessWidget {
16+
/// {@macro account_linking_page}
17+
const AccountLinkingPage({super.key});
18+
19+
@override
20+
Widget build(BuildContext context) {
21+
final l10n = AppLocalizationsX(context).l10n;
22+
final textTheme = Theme.of(context).textTheme;
23+
final colorScheme = Theme.of(context).colorScheme;
24+
25+
return Scaffold(
26+
appBar: AppBar(
27+
backgroundColor: Colors.transparent,
28+
elevation: 0,
29+
leading: IconButton(
30+
icon: const Icon(Icons.close),
31+
tooltip: MaterialLocalizations.of(context).closeButtonTooltip,
32+
onPressed: () {
33+
// Navigate back to the account page when close is pressed.
34+
// This uses pop() because it's a fullscreenDialog.
35+
context.pop();
36+
},
37+
),
38+
),
39+
body: SafeArea(
40+
child: BlocConsumer<AuthenticationBloc, AuthenticationState>(
41+
listener: (context, state) {
42+
if (state.status == AuthenticationStatus.failure) {
43+
ScaffoldMessenger.of(context)
44+
..hideCurrentSnackBar()
45+
..showSnackBar(
46+
SnackBar(
47+
content: Text(state.exception!.toFriendlyMessage(context)),
48+
backgroundColor: colorScheme.error,
49+
),
50+
);
51+
}
52+
},
53+
builder: (context, state) {
54+
final isLoading = state.status == AuthenticationStatus.loading;
55+
56+
return Padding(
57+
padding: const EdgeInsets.all(AppSpacing.paddingLarge),
58+
child: Center(
59+
child: SingleChildScrollView(
60+
child: Column(
61+
mainAxisAlignment: MainAxisAlignment.center,
62+
crossAxisAlignment: CrossAxisAlignment.stretch,
63+
children: [
64+
// --- Icon ---
65+
Padding(
66+
padding: const EdgeInsets.only(bottom: AppSpacing.xl),
67+
child: Icon(
68+
Icons.sync,
69+
size: AppSpacing.xxl * 2,
70+
color: colorScheme.primary,
71+
),
72+
),
73+
// --- Headline and Subheadline ---
74+
Text(
75+
l10n.accountLinkingHeadline,
76+
style: textTheme.headlineMedium?.copyWith(
77+
fontWeight: FontWeight.bold,
78+
),
79+
textAlign: TextAlign.center,
80+
),
81+
const SizedBox(height: AppSpacing.md),
82+
Text(
83+
l10n.accountLinkingBody,
84+
style: textTheme.bodyLarge?.copyWith(
85+
color: colorScheme.onSurfaceVariant,
86+
),
87+
textAlign: TextAlign.center,
88+
),
89+
const SizedBox(height: AppSpacing.xxl),
90+
91+
// --- Email Linking Button ---
92+
ElevatedButton.icon(
93+
icon: const Icon(Icons.email_outlined),
94+
onPressed: isLoading
95+
? null
96+
: () {
97+
// Navigate to the request code page for linking.
98+
context.goNamed(
99+
Routes.accountLinkingRequestCodeName,
100+
);
101+
},
102+
label: Text(l10n.accountLinkingSendLinkButton),
103+
style: ElevatedButton.styleFrom(
104+
padding: const EdgeInsets.symmetric(
105+
vertical: AppSpacing.md,
106+
),
107+
textStyle: textTheme.labelLarge,
108+
),
109+
),
110+
111+
// --- Loading Indicator ---
112+
if (isLoading) ...[
113+
const Padding(
114+
padding: EdgeInsets.only(top: AppSpacing.xl),
115+
child: Center(child: CircularProgressIndicator()),
116+
),
117+
],
118+
],
119+
),
120+
),
121+
),
122+
);
123+
},
124+
),
125+
),
126+
);
127+
}
128+
}

0 commit comments

Comments
 (0)