diff --git a/lib/screens/auth/create_account_content.dart b/lib/screens/auth/create_account_content.dart index f1ea6565..a6de181e 100644 --- a/lib/screens/auth/create_account_content.dart +++ b/lib/screens/auth/create_account_content.dart @@ -120,7 +120,7 @@ class _CreateAccountEmailScreen extends HookConsumerWidget { return null; }, [isBusy]); - void performNext() { + Future performNext() async { final email = emailController.text.trim(); if (email.isEmpty) { showErrorAlert('fieldCannotBeEmpty'.tr()); @@ -130,7 +130,18 @@ class _CreateAccountEmailScreen extends HookConsumerWidget { showErrorAlert('fieldEmailAddressMustBeValid'.tr()); return; } - onNext(); + + // Validate email availability with API + isBusy.value = true; + try { + final client = ref.watch(apiClientProvider); + await client.post('/pass/accounts/validate', data: {'email': email}); + onNext(); + } catch (err) { + showErrorAlert(err); + } finally { + isBusy.value = false; + } } return Column( @@ -343,14 +354,25 @@ class _CreateAccountProfileScreen extends HookConsumerWidget { return null; }, [isBusy]); - void performNext() { + Future performNext() async { final username = usernameController.text.trim(); final nickname = nicknameController.text.trim(); if (username.isEmpty || nickname.isEmpty) { showErrorAlert('fieldCannotBeEmpty'.tr()); return; } - onNext(); + + // Validate username availability with API + isBusy.value = true; + try { + final client = ref.watch(apiClientProvider); + await client.post('/pass/accounts/validate', data: {'name': username}); + onNext(); + } catch (err) { + showErrorAlert(err); + } finally { + isBusy.value = false; + } } return Column( diff --git a/lib/screens/auth/login_content.dart b/lib/screens/auth/login_content.dart index fe43ba73..1dca2855 100644 --- a/lib/screens/auth/login_content.dart +++ b/lib/screens/auth/login_content.dart @@ -38,6 +38,20 @@ final Map kFactorTypes = { 4: ('authFactorPin', 'authFactorPinDescription', Symbols.nest_secure_alarm), }; +/// Performs post-login tasks including fetching user info, subscribing to push +/// notifications, connecting websocket, and closing the login dialog. +Future performPostLogin(BuildContext context, WidgetRef ref) async { + final userNotifier = ref.read(userInfoProvider.notifier); + await userNotifier.fetchUser(); + final apiClient = ref.read(apiClientProvider); + subscribePushNotification(apiClient); + final wsNotifier = ref.read(websocketStateProvider.notifier); + wsNotifier.connect(); + if (context.mounted && Navigator.canPop(context)) { + Navigator.pop(context, true); + } +} + class _LoginCheckScreen extends HookConsumerWidget { final SnAuthChallenge? challenge; final SnAuthFactor? factor; @@ -80,14 +94,7 @@ class _LoginCheckScreen extends HookConsumerWidget { if (!context.mounted) return; // Do post login tasks - final userNotifier = ref.read(userInfoProvider.notifier); - userNotifier.fetchUser().then((_) { - final apiClient = ref.read(apiClientProvider); - subscribePushNotification(apiClient); - final wsNotifier = ref.read(websocketStateProvider.notifier); - wsNotifier.connect(); - if (context.mounted) Navigator.pop(context, true); - }); + await performPostLogin(context, ref); } useEffect(() { @@ -628,17 +635,13 @@ class _LoginLookupScreen extends HookConsumerWidget { }, ); - final challenge = SnAuthChallenge.fromJson(resp.data); - onChallenge(challenge); - final factorResp = await client.get( - '/pass/auth/challenge/${challenge.id}/factors', - ); - onFactor( - List.from( - factorResp.data.map((ele) => SnAuthFactor.fromJson(ele)), - ), - ); - onNext(); + final token = resp.data['token']; + setToken(ref.watch(sharedPreferencesProvider), token); + ref.invalidate(tokenProvider); + if (!context.mounted) return; + + // Do post login tasks + await performPostLogin(context, ref); } catch (err) { if (err is SignInWithAppleAuthorizationException) return; showErrorAlert(err); diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index 8db36857..1efa0733 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -5,10 +5,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:protocol_handler/protocol_handler.dart'; import 'package:island/pods/activity/activity_rpc.dart'; +import 'package:island/pods/config.dart'; +import 'package:island/pods/network.dart'; import 'package:island/pods/websocket.dart'; import 'package:island/route.dart'; +import 'package:island/screens/auth/login_content.dart'; import 'package:island/screens/tray_manager.dart'; -import 'package:island/services/event_bus.dart'; import 'package:island/pods/web_auth/web_auth_providers.dart'; import 'package:island/services/notify.dart'; import 'package:island/services/sharing_intent.dart'; @@ -117,14 +119,20 @@ class _AppWrapperState extends ConsumerState TrayService.instance.handleAction(menuItem); } - void _handleDeepLink(Uri uri, WidgetRef ref) { + void _handleDeepLink(Uri uri, WidgetRef ref) async { String path = '/${uri.host}${uri.path}'; // Special handling for OIDC auth callback - if (path == '/auth/callback' && - uri.queryParameters.containsKey('challenge')) { - final challenge = uri.queryParameters['challenge']!; - eventBus.fire(OidcAuthCallbackEvent(challenge)); + if (path == '/auth/callback' && uri.queryParameters.containsKey('token')) { + final token = uri.queryParameters['token']!; + setToken(ref.read(sharedPreferencesProvider), token); + ref.invalidate(tokenProvider); + + // Do post login tasks + if (mounted) { + await performPostLogin(context, ref); + } + if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) { windowManager.show();