@@ -22,162 +22,189 @@ class AccountLinkingPage extends StatelessWidget {
2222 final textTheme = Theme .of (context).textTheme;
2323 final colorScheme = Theme .of (context).colorScheme;
2424
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 ),
54- ),
55- ),
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;
78-
79- return Padding (
25+ // GestureDetector allows dismissing the modal by tapping the scrim.
26+ return GestureDetector (
27+ onTap: () => context.pop (),
28+ child: Scaffold (
29+ // A transparent background is crucial for the modal effect.
30+ backgroundColor: Colors .transparent,
31+ // The DraggableScrollableSheet provides the bottom sheet behavior.
32+ body: DraggableScrollableSheet (
33+ // The initial height of the bottom sheet (approx 2/3 of the screen).
34+ initialChildSize: 0.66 ,
35+ // The minimum height the sheet can be dragged down to before dismissing.
36+ minChildSize: 0.4 ,
37+ // The maximum height the sheet can be dragged up to.
38+ maxChildSize: 0.9 ,
39+ expand: false , // Do not expand to full screen by default
40+ builder: (BuildContext context, ScrollController scrollController) {
41+ // A second GestureDetector prevents the inner content taps from
42+ // bubbling up and closing the modal.
43+ return GestureDetector (
44+ onTap: () {}, // Absorb taps within the sheet
45+ child: Container (
46+ // Apply styling for the bottom sheet container.
47+ decoration: BoxDecoration (
48+ color: colorScheme.surface,
49+ borderRadius:
50+ const BorderRadius .vertical (top: Radius .circular (16 )),
51+ ),
52+ child: Stack (
53+ children: [
54+ Column (
55+ children: [
56+ // --- Drag Handle ---
57+ Padding (
8058 padding:
81- const EdgeInsets .all ( AppSpacing .paddingLarge ),
59+ const EdgeInsets .symmetric (vertical : AppSpacing .md ),
8260 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),
119-
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,
61+ child: Container (
62+ height: 4 ,
63+ width: 40 ,
64+ decoration: BoxDecoration (
65+ color: colorScheme.onSurface.withOpacity (0.4 ),
66+ borderRadius: BorderRadius .circular (8 ),
67+ ),
68+ ),
69+ ),
70+ ),
71+ // --- Main Content ---
72+ Expanded (
73+ child: BlocConsumer <AuthenticationBloc ,
74+ AuthenticationState >(
75+ listener: (context, state) {
76+ if (state.status ==
77+ AuthenticationStatus .failure) {
78+ ScaffoldMessenger .of (context)
79+ ..hideCurrentSnackBar ()
80+ ..showSnackBar (
81+ SnackBar (
82+ content: Text (
83+ state.exception!
84+ .toFriendlyMessage (context),
13885 ),
139- textStyle : textTheme.labelLarge ,
86+ backgroundColor : colorScheme.error ,
14087 ),
141- ),
88+ );
89+ }
90+ },
91+ builder: (context, state) {
92+ final isLoading =
93+ state.status == AuthenticationStatus .loading;
14294
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- ),
95+ return Padding (
96+ padding: const EdgeInsets .all (
97+ AppSpacing .paddingLarge,
98+ ),
99+ child: Center (
100+ child: SingleChildScrollView (
101+ // Link the scroll controller to enable dragging the sheet.
102+ controller: scrollController,
103+ child: Column (
104+ mainAxisAlignment:
105+ MainAxisAlignment .center,
106+ crossAxisAlignment:
107+ CrossAxisAlignment .stretch,
108+ children: [
109+ // --- Icon ---
110+ Padding (
111+ padding: const EdgeInsets .only (
112+ bottom: AppSpacing .xl,
113+ ),
114+ child: Icon (
115+ Icons .sync ,
116+ size: AppSpacing .xxl * 2 ,
117+ color: colorScheme.primary,
118+ ),
119+ ),
120+ // --- Headline and Subheadline ---
121+ Text (
122+ l10n.accountLinkingHeadline,
123+ style: textTheme.headlineMedium
124+ ? .copyWith (
125+ fontWeight: FontWeight .bold,
126+ ),
127+ textAlign: TextAlign .center,
128+ ),
129+ const SizedBox (height: AppSpacing .md),
130+ Text (
131+ l10n.accountLinkingBody,
132+ style: textTheme.bodyLarge? .copyWith (
133+ color:
134+ colorScheme.onSurfaceVariant,
135+ ),
136+ textAlign: TextAlign .center,
137+ ),
138+ const SizedBox (height: AppSpacing .xxl),
139+
140+ // --- Email Linking Button ---
141+ ElevatedButton .icon (
142+ icon:
143+ const Icon (Icons .email_outlined),
144+ onPressed: isLoading
145+ ? null
146+ : () {
147+ // Navigate to the request code page for linking.
148+ context.goNamed (
149+ Routes
150+ .accountLinkingRequestCodeName,
151+ );
152+ },
153+ label: Text (
154+ l10n.accountLinkingSendLinkButton,
155+ ),
156+ style: ElevatedButton .styleFrom (
157+ padding:
158+ const EdgeInsets .symmetric (
159+ vertical: AppSpacing .md,
160+ ),
161+ textStyle: textTheme.labelLarge,
162+ ),
163+ ),
164+
165+ // --- Loading Indicator ---
166+ if (isLoading) ...[
167+ const Padding (
168+ padding: EdgeInsets .only (
169+ top: AppSpacing .xl,
170+ ),
171+ child: Center (
172+ child:
173+ CircularProgressIndicator (),
174+ ),
175+ ),
176+ ],
177+ ],
152178 ),
153- ] ,
154- ] ,
155- ),
156- ) ,
179+ ) ,
180+ ) ,
181+ );
182+ } ,
157183 ),
158- );
159- } ,
184+ ),
185+ ] ,
160186 ),
161- ),
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- },
187+ // --- Close Button ---
188+ Positioned (
189+ top: AppSpacing .sm,
190+ right: AppSpacing .sm,
191+ child: IconButton (
192+ icon: const Icon (Icons .close),
193+ tooltip: MaterialLocalizations .of (context)
194+ .closeButtonTooltip,
195+ onPressed: () {
196+ // Dismiss the modal bottom sheet.
197+ context.pop ();
198+ },
199+ ),
200+ ),
201+ ],
175202 ),
176203 ),
177- ],
178- ) ,
179- );
180- } ,
204+ );
205+ } ,
206+ ),
207+ ) ,
181208 );
182209 }
183210}
0 commit comments