Skip to content

Commit 51128b5

Browse files
authored
fix: improve SSO popup UX #33 (#50)
1 parent 4436df5 commit 51128b5

File tree

5 files changed

+97
-67
lines changed

5 files changed

+97
-67
lines changed

packages/clerk_flutter/example/lib/main.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class _ExampleAppState extends State<ExampleApp> {
5757
@override
5858
Widget build(BuildContext context) {
5959
return MaterialApp(
60+
debugShowCheckedModeBanner: false,
6061
home: ClerkAuth(
6162
publicKey: publicKey,
6263
publishableKey: widget.publishableKey,

packages/clerk_flutter/lib/src/widgets/authentication/clerk_authentication_widget.dart

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:clerk_auth/clerk_auth.dart' as clerk;
2+
import 'package:clerk_auth/clerk_auth.dart';
23
import 'package:clerk_flutter/clerk_flutter.dart';
34
import 'package:clerk_flutter/src/assets.dart';
45
import 'package:flutter/gestures.dart';
@@ -50,20 +51,29 @@ class _ClerkAuthenticationWidgetState extends State<ClerkAuthenticationWidget> {
5051
ClerkAuthBuilder(
5152
builder: (context, auth) {
5253
return Closeable(
53-
closed: auth.signIn is clerk.SignIn ||
54-
auth.signUp is clerk.SignUp,
54+
closed: (auth.signIn is clerk.SignIn &&
55+
auth.signIn!.status.isActive) ||
56+
(auth.signUp is clerk.SignUp &&
57+
auth.signUp!.status.isActive),
5558
child: const ClerkSSOPanel(),
5659
);
5760
},
5861
),
5962
Closeable(
60-
open: _state.isSigningIn, child: const ClerkSignInPanel()),
63+
open: _state.isSigningIn,
64+
child: const ClerkSignInPanel(),
65+
),
6166
Closeable(
62-
open: _state.isSigningUp, child: const ClerkSignUpPanel()),
67+
open: _state.isSigningUp,
68+
child: const ClerkSignUpPanel(),
69+
),
6370
const ClerkErrorMessage(),
6471
],
6572
),
66-
bottomPortion: _BottomPortion(state: _state, onChange: _toggle),
73+
bottomPortion: _BottomPortion(
74+
state: _state,
75+
onChange: _toggle,
76+
),
6777
),
6878
);
6979
}

packages/clerk_flutter/lib/src/widgets/control/clerk_auth_provider.dart

Lines changed: 56 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,11 @@ class ClerkAuthProvider extends clerk.Auth with ChangeNotifier {
5151

5252
final _errors = StreamController<clerk.AuthError>();
5353
final OverlayEntry _loadingOverlay;
54-
OverlayEntry? _ssoOverlay;
5554

5655
static const _kRotatingTokenNonce = 'rotating_token_nonce';
5756

57+
static const _kSsoRouteName = 'clerk_sso_popup';
58+
5859
@override
5960
void update() => notifyListeners();
6061

@@ -71,53 +72,42 @@ class ClerkAuthProvider extends clerk.Auth with ChangeNotifier {
7172
void Function(clerk.AuthError)? onError,
7273
}) async {
7374
final auth = ClerkAuth.of(context);
74-
final overlay = Overlay.of(context);
7575
final client = await call(
7676
context,
7777
() => auth.oauthSignIn(strategy: strategy),
7878
onError: onError,
7979
);
8080
final url = client?.signIn?.firstFactorVerification?.providerUrl;
81-
if (url case String url) {
82-
_ssoOverlay = OverlayEntry(
83-
builder: (BuildContext context) {
84-
return _SsoWebViewHost(
85-
url: url,
86-
callback: _ssoCallback(
87-
strategy,
88-
onError: onError,
89-
auth: auth,
90-
),
91-
);
92-
},
81+
if (url != null && context.mounted) {
82+
final redirectUrl = await showDialog<String>(
83+
context: context,
84+
useSafeArea: false,
85+
useRootNavigator: true,
86+
routeSettings: const RouteSettings(name: _kSsoRouteName),
87+
builder: (context) => _SsoWebViewOverlay(url: url),
9388
);
94-
overlay.insert(_ssoOverlay!);
95-
}
96-
}
97-
98-
Function(BuildContext, String) _ssoCallback(
99-
clerk.Strategy strategy, {
100-
void Function(clerk.AuthError)? onError,
101-
required ClerkAuthProvider auth,
102-
}) {
103-
return (BuildContext context, String redirectUrl) async {
104-
final uri = Uri.parse(redirectUrl);
105-
final token = uri.queryParameters[_kRotatingTokenNonce];
106-
if (token case String token) {
107-
await call(
108-
context,
109-
() => auth.attemptSignIn(strategy: strategy, token: token),
110-
onError: onError,
111-
);
112-
} else {
113-
await auth.refreshClient();
89+
if (redirectUrl != null && context.mounted) {
90+
final uri = Uri.parse(redirectUrl);
91+
final token = uri.queryParameters[_kRotatingTokenNonce];
92+
if (token case String token) {
93+
await call(
94+
context,
95+
() => auth.attemptSignIn(strategy: strategy, token: token),
96+
onError: onError,
97+
);
98+
} else {
99+
await auth.refreshClient();
100+
if (context.mounted) {
101+
await call(context, () => auth.transfer(), onError: onError);
102+
}
103+
}
114104
if (context.mounted) {
115-
await call(context, () => auth.transfer(), onError: onError);
105+
Navigator.of(context).popUntil(
106+
(route) => route.settings.name != _kSsoRouteName,
107+
);
116108
}
117109
}
118-
_ssoOverlay?.remove();
119-
_ssoOverlay = null;
120-
};
110+
}
121111
}
122112

123113
/// Convenience method to make an auth call to the backend via ClerkAuth
@@ -198,33 +188,38 @@ class ClerkAuthProvider extends clerk.Auth with ChangeNotifier {
198188
_errors.add(clerk.AuthError(message: message));
199189
}
200190

201-
class _SsoWebViewHost extends StatefulWidget {
202-
const _SsoWebViewHost({
191+
class _SsoWebViewOverlay extends StatefulWidget {
192+
const _SsoWebViewOverlay({
203193
required this.url,
204-
required this.callback,
205194
});
206195

207196
final String url;
208-
final Function(BuildContext context, String redirectUrl) callback;
209197

210198
@override
211-
State<_SsoWebViewHost> createState() => _SsoWebViewHostState();
199+
State<_SsoWebViewOverlay> createState() => _SsoWebViewOverlayState();
212200
}
213201

214-
class _SsoWebViewHostState extends State<_SsoWebViewHost> {
202+
class _SsoWebViewOverlayState extends State<_SsoWebViewOverlay> {
215203
late final WebViewController controller;
204+
var _title = Future<String?>.value('Loading…');
216205

217206
@override
218207
void initState() {
219208
super.initState();
220209
controller = WebViewController()
221210
..setUserAgent('Clerk Flutter SDK v${clerk.Auth.jsVersion}')
222211
..setJavaScriptMode(JavaScriptMode.unrestricted)
212+
..setBackgroundColor(Colors.white)
223213
..setNavigationDelegate(
224214
NavigationDelegate(
215+
onPageFinished: (_) => _updateTitle(),
225216
onNavigationRequest: (NavigationRequest request) async {
226217
if (request.url.startsWith(clerk.Auth.oauthRedirect)) {
227-
widget.callback(context, request.url);
218+
scheduleMicrotask(() {
219+
if (mounted) {
220+
Navigator.of(context).pop(request.url);
221+
}
222+
});
228223
return NavigationDecision.prevent;
229224
}
230225
return NavigationDecision.navigate;
@@ -234,9 +229,25 @@ class _SsoWebViewHostState extends State<_SsoWebViewHost> {
234229
controller.loadRequest(Uri.parse(widget.url));
235230
}
236231

232+
void _updateTitle() {
233+
setState(() {
234+
_title = controller.getTitle();
235+
});
236+
}
237+
237238
@override
238239
Widget build(BuildContext context) {
239240
return Scaffold(
241+
appBar: AppBar(
242+
automaticallyImplyLeading: false,
243+
title: FutureBuilder(
244+
future: _title,
245+
builder: (context, snapshot) {
246+
return Text(snapshot.data ?? '');
247+
},
248+
),
249+
actions: const [CloseButton()],
250+
),
240251
body: WebViewWidget(controller: controller),
241252
);
242253
}

packages/clerk_flutter/lib/src/widgets/ui/social_connection_button.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ class SocialConnectionButton extends StatelessWidget {
2020
return SizedBox(
2121
height: 30.0,
2222
child: MaterialButton(
23-
onPressed: () =>
24-
ClerkAuth.above(context).sso(context, connection.strategy),
23+
onPressed: () {
24+
ClerkAuth.above(context).sso(context, connection.strategy);
25+
},
2526
elevation: 2.0,
2627
shape: RoundedRectangleBorder(
2728
borderRadius: borderRadius4,

packages/clerk_flutter/lib/src/widgets/user/clerk_user_button.dart

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -356,27 +356,34 @@ class _SessionRow extends StatelessWidget {
356356
auth.call(context, () => auth.signOut());
357357
} else {
358358
auth.call(
359-
context, () => auth.signOutOf(session));
359+
context,
360+
() => auth.signOutOf(session),
361+
);
360362
}
361363
},
362-
label: Row(
363-
mainAxisAlignment: MainAxisAlignment.center,
364-
crossAxisAlignment: CrossAxisAlignment.end,
365-
children: [
366-
const Icon(Icons.logout,
367-
color: ClerkColors.charcoalGrey, size: 11),
368-
horizontalMargin8,
369-
Text(
370-
translator.translate('Sign Out'),
371-
style: ClerkTextStyle.buttonSubtitle.copyWith(
372-
fontSize: 8,
364+
label: Padding(
365+
padding: verticalPadding4,
366+
child: Row(
367+
mainAxisAlignment: MainAxisAlignment.center,
368+
crossAxisAlignment: CrossAxisAlignment.center,
369+
children: [
370+
const Icon(
371+
Icons.logout,
373372
color: ClerkColors.charcoalGrey,
373+
size: 20,
374374
),
375-
),
376-
],
375+
horizontalMargin8,
376+
Text(
377+
translator.translate('Sign Out'),
378+
style: ClerkTextStyle.buttonSubtitle.copyWith(
379+
fontSize: 12,
380+
color: ClerkColors.charcoalGrey,
381+
),
382+
),
383+
],
384+
),
377385
),
378386
style: ClerkMaterialButtonStyle.light,
379-
height: 16,
380387
),
381388
),
382389
],

0 commit comments

Comments
 (0)