Skip to content

Commit c0c94e0

Browse files
committed
refactor(authentication): make AccountLinkingPage dismissible
- Wrap DraggableScrollableSheet with GestureDetector to dismiss modal by tapping scrim - Add second GestureDetector inside sheet to prevent closing on inner content taps - Improve modal styling and layout
1 parent 27635c2 commit c0c94e0

File tree

1 file changed

+173
-146
lines changed

1 file changed

+173
-146
lines changed

lib/authentication/view/account_linking_page.dart

Lines changed: 173 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)