@@ -9,8 +9,8 @@ import 'package:ui_kit/ui_kit.dart';
99/// {@template account_linking_page}
1010/// Displays options for an anonymous user to link their account to an email.
1111///
12- /// This page is presented modally to indicate it's an in-app process
13- /// rather than a full sign-out/sign-in flow .
12+ /// This page is presented as a modal bottom sheet, allowing the previous
13+ /// page to remain visible underneath and providing a dismissible experience .
1414/// {@endtemplate}
1515class AccountLinkingPage extends StatelessWidget {
1616 /// {@macro account_linking_page}
@@ -22,107 +22,162 @@ class AccountLinkingPage extends StatelessWidget {
2222 final textTheme = Theme .of (context).textTheme;
2323 final colorScheme = Theme .of (context).colorScheme;
2424
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,
25+ return DraggableScrollableSheet (
26+ // The initial height of the bottom sheet (approx 2/3 of the screen).
27+ initialChildSize: 0.66 ,
28+ // The minimum height the sheet can be dragged down to before dismissing.
29+ minChildSize: 0.4 ,
30+ // The maximum height the sheet can be dragged up to.
31+ maxChildSize: 0.9 ,
32+ expand: false , // Do not expand to full screen by default
33+ builder: (BuildContext context, ScrollController scrollController) {
34+ return Container (
35+ // Apply styling for the bottom sheet container.
36+ decoration: BoxDecoration (
37+ color: colorScheme.surface,
38+ borderRadius: const BorderRadius .vertical (top: Radius .circular (16 )),
39+ ),
40+ child: Stack (
41+ children: [
42+ Column (
43+ children: [
44+ // --- Drag Handle ---
45+ Padding (
46+ padding: const EdgeInsets .symmetric (vertical: AppSpacing .md),
47+ child: Center (
48+ child: Container (
49+ height: 4 ,
50+ width: 40 ,
51+ decoration: BoxDecoration (
52+ color: colorScheme.onSurface.withOpacity (0.4 ),
53+ borderRadius: BorderRadius .circular (8 ),
7854 ),
79- textAlign: TextAlign .center,
8055 ),
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),
56+ ),
57+ ),
58+ // --- Main Content ---
59+ Expanded (
60+ child: BlocConsumer <AuthenticationBloc , AuthenticationState >(
61+ listener: (context, state) {
62+ if (state.status == AuthenticationStatus .failure) {
63+ ScaffoldMessenger .of (context)
64+ ..hideCurrentSnackBar ()
65+ ..showSnackBar (
66+ SnackBar (
67+ content: Text (
68+ state.exception! .toFriendlyMessage (context),
69+ ),
70+ backgroundColor: colorScheme.error,
71+ ),
72+ );
73+ }
74+ },
75+ builder: (context, state) {
76+ final isLoading =
77+ state.status == AuthenticationStatus .loading;
9078
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- ),
79+ return Padding (
80+ padding:
81+ const EdgeInsets .all (AppSpacing .paddingLarge),
82+ child: Center (
83+ child: SingleChildScrollView (
84+ // Link the scroll controller to enable dragging the sheet.
85+ controller: scrollController,
86+ child: Column (
87+ mainAxisAlignment: MainAxisAlignment .center,
88+ crossAxisAlignment:
89+ CrossAxisAlignment .stretch,
90+ children: [
91+ // --- Icon ---
92+ Padding (
93+ padding: const EdgeInsets .only (
94+ bottom: AppSpacing .xl,
95+ ),
96+ child: Icon (
97+ Icons .sync ,
98+ size: AppSpacing .xxl * 2 ,
99+ color: colorScheme.primary,
100+ ),
101+ ),
102+ // --- Headline and Subheadline ---
103+ Text (
104+ l10n.accountLinkingHeadline,
105+ style: textTheme.headlineMedium? .copyWith (
106+ fontWeight: FontWeight .bold,
107+ ),
108+ textAlign: TextAlign .center,
109+ ),
110+ const SizedBox (height: AppSpacing .md),
111+ Text (
112+ l10n.accountLinkingBody,
113+ style: textTheme.bodyLarge? .copyWith (
114+ color: colorScheme.onSurfaceVariant,
115+ ),
116+ textAlign: TextAlign .center,
117+ ),
118+ const SizedBox (height: AppSpacing .xxl),
110119
111- // --- Loading Indicator ---
112- if (isLoading) ...[
113- const Padding (
114- padding: EdgeInsets .only (top: AppSpacing .xl),
115- child: Center (child: CircularProgressIndicator ()),
116- ),
117- ],
118- ],
120+ // --- Email Linking Button ---
121+ ElevatedButton .icon (
122+ icon: const Icon (Icons .email_outlined),
123+ onPressed: isLoading
124+ ? null
125+ : () {
126+ // Navigate to the request code page for linking.
127+ context.goNamed (
128+ Routes
129+ .accountLinkingRequestCodeName,
130+ );
131+ },
132+ label: Text (
133+ l10n.accountLinkingSendLinkButton,
134+ ),
135+ style: ElevatedButton .styleFrom (
136+ padding: const EdgeInsets .symmetric (
137+ vertical: AppSpacing .md,
138+ ),
139+ textStyle: textTheme.labelLarge,
140+ ),
141+ ),
142+
143+ // --- Loading Indicator ---
144+ if (isLoading) ...[
145+ const Padding (
146+ padding: EdgeInsets .only (
147+ top: AppSpacing .xl,
148+ ),
149+ child: Center (
150+ child: CircularProgressIndicator (),
151+ ),
152+ ),
153+ ],
154+ ],
155+ ),
156+ ),
157+ ),
158+ );
159+ },
160+ ),
119161 ),
162+ ],
163+ ),
164+ // --- Close Button ---
165+ Positioned (
166+ top: AppSpacing .sm,
167+ right: AppSpacing .sm,
168+ child: IconButton (
169+ icon: const Icon (Icons .close),
170+ tooltip: MaterialLocalizations .of (context).closeButtonTooltip,
171+ onPressed: () {
172+ // Dismiss the modal bottom sheet.
173+ context.pop ();
174+ },
120175 ),
121176 ),
122- );
123- } ,
124- ),
125- ) ,
177+ ],
178+ ) ,
179+ );
180+ } ,
126181 );
127182 }
128183}
0 commit comments