Skip to content

Commit 27635c2

Browse files
committed
refactor(authentication): convert AccountLinkingPage to modal bottom sheet
- Replace Scaffold with DraggableScrollableSheet for a bottom sheet layout - Add drag handle and close button for improved user experience - Maintain visibility of the previous page underneath the sheet - Allow sheet to be dismissed by dragging down
1 parent 3cbda23 commit 27635c2

File tree

1 file changed

+151
-96
lines changed

1 file changed

+151
-96
lines changed

lib/authentication/view/account_linking_page.dart

Lines changed: 151 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -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}
1515
class 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

Comments
 (0)