From f307fad07493b0f020d8ddbf8c0236366683cd63 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Sun, 16 Nov 2025 16:56:22 -0500 Subject: [PATCH] add login flow --- lib/controllers/client_controller.dart | 59 +++++++++++++--------- lib/helpers/login_helper.dart | 19 ------- lib/pages/login_page.dart | 70 ++++++++++++++++++-------- 3 files changed, 84 insertions(+), 64 deletions(-) delete mode 100644 lib/helpers/login_helper.dart diff --git a/lib/controllers/client_controller.dart b/lib/controllers/client_controller.dart index f317c42..9426771 100644 --- a/lib/controllers/client_controller.dart +++ b/lib/controllers/client_controller.dart @@ -7,35 +7,44 @@ import "package:sqflite_common_ffi/sqflite_ffi.dart"; class ClientController extends AsyncNotifier { @override - Future build() async { - final client = Client( + Future build() async => Client( + "nexus", + logLevel: kReleaseMode ? Level.warning : Level.verbose, + importantStateEvents: {"im.ponies.room_emotes"}, + supportedLoginTypes: {AuthenticationTypes.password}, + database: await MatrixSdkDatabase.init( "nexus", - logLevel: kReleaseMode ? Level.warning : Level.verbose, - importantStateEvents: {"im.ponies.room_emotes"}, - supportedLoginTypes: {AuthenticationTypes.password}, - database: await MatrixSdkDatabase.init( - "nexus", - database: await databaseFactoryFfi.openDatabase( - join((await getApplicationSupportDirectory()).path, "database.db"), - ), + database: await databaseFactoryFfi.openDatabase( + join((await getApplicationSupportDirectory()).path, "database.db"), ), - ); + ), + ); - // TODO: Save info - // if (client.homeserver == null) { - // await client.checkHomeserver(Uri.https("federated.nexus")); - // } - // if (client.accessToken == null) { - // await client.login( - // LoginType.mLoginPassword, - // initialDeviceDisplayName: "Nexus Client", - // deviceId: "temp", // TODO - // identifier: AuthenticationUserIdentifier(user: "quadradical"), - // password: File("./password.txt").readAsStringSync(), - // ); - // } + Future setHomeserver(Uri homeserverUrl) async { + final client = await future; + try { + await client.checkHomeserver(homeserverUrl); + return true; + } catch (_) { + return false; + } + } - return client; + Future login(String username, String password) async { + final client = await future; + try { + await client.login( + LoginType.mLoginPassword, + initialDeviceDisplayName: + "Nexus Client login at ${DateTime.now().toIso8601String()}", + identifier: AuthenticationUserIdentifier(user: username), + password: password, + ); + //TODO: refresh + return true; + } catch (_) { + return false; + } } static final provider = AsyncNotifierProvider( diff --git a/lib/helpers/login_helper.dart b/lib/helpers/login_helper.dart deleted file mode 100644 index b8c9cec..0000000 --- a/lib/helpers/login_helper.dart +++ /dev/null @@ -1,19 +0,0 @@ -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/client_controller.dart"; - -class LoginHelper { - final Ref ref; - LoginHelper(this.ref); - - Future setHomeserver(Uri homeserverUrl) async { - final client = await ref.watch(ClientController.provider.future); - try { - await client.checkHomeserver(homeserverUrl); - return true; - } catch (_) { - return false; - } - } - - static final provider = Provider(LoginHelper.new); -} diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart index 3af9351..1207495 100644 --- a/lib/pages/login_page.dart +++ b/lib/pages/login_page.dart @@ -2,7 +2,7 @@ import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_svg/flutter_svg.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:nexus/helpers/login_helper.dart"; +import "package:nexus/controllers/client_controller.dart"; import "package:nexus/helpers/launch_helper.dart"; import "package:nexus/models/homeserver.dart"; import "package:nexus/widgets/appbar.dart"; @@ -14,14 +14,17 @@ class LoginPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isChecking = useState(false); + final theme = Theme.of(context); + + final isLoading = useState(false); final allowLogin = useState(false); + Future setHomeserver(Uri? homeserver) async { - isChecking.value = true; + isLoading.value = true; final succeeded = homeserver == null ? false : await ref - .watch(LoginHelper.provider) + .watch(ClientController.provider.notifier) .setHomeserver( homeserver.hasScheme ? homeserver @@ -35,18 +38,18 @@ class LoginPage extends HookConsumerWidget { SnackBar( content: Text( "Homeserver verification failed. Is your homeserver down?", - style: TextStyle( - color: Theme.of(context).colorScheme.onErrorContainer, - ), + style: TextStyle(color: theme.colorScheme.onErrorContainer), ), - backgroundColor: Theme.of(context).colorScheme.errorContainer, + backgroundColor: theme.colorScheme.errorContainer, ), ); } - isChecking.value = false; + isLoading.value = false; } final homeserverUrl = useTextEditingController(); + final username = useTextEditingController(); + final password = useTextEditingController(); return Scaffold( appBar: Appbar(), @@ -64,13 +67,10 @@ class LoginPage extends HookConsumerWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Nexus", - style: Theme.of(context).textTheme.displayMedium, - ), + Text("Nexus", style: theme.textTheme.displayMedium), Text( "A Simple Matrix Client", - style: Theme.of(context).textTheme.headlineMedium, + style: theme.textTheme.headlineMedium, ), ], ), @@ -94,7 +94,7 @@ class LoginPage extends HookConsumerWidget { ), ), IconButton.filled( - onPressed: isChecking.value + onPressed: isLoading.value ? null : () => setHomeserver(Uri.tryParse(homeserverUrl.text)), icon: Icon(Icons.check), @@ -132,7 +132,7 @@ class LoginPage extends HookConsumerWidget { title: Text(homeserver.name), leading: Image.network(homeserver.iconUrl, height: 32), subtitle: Text(homeserver.description), - onTap: isChecking.value + onTap: isLoading.value ? null : () => setHomeserver(homeserver.url), trailing: IconButton( @@ -151,16 +151,46 @@ class LoginPage extends HookConsumerWidget { .launchUrl(Uri.https("servers.joinmatrix.org")), child: Text("See more homeservers..."), ), - if (isChecking.value) + if (isLoading.value) Padding(padding: EdgeInsets.only(top: 32), child: Loading()) else if (allowLogin.value) ...[ DividerText("Then, sign in:"), SizedBox(height: 4), - TextField(decoration: InputDecoration(label: Text("Username"))), + TextField( + decoration: InputDecoration(label: Text("Username")), + controller: username, + ), SizedBox(height: 12), - TextField(decoration: InputDecoration(label: Text("Password"))), + TextField( + decoration: InputDecoration(label: Text("Password")), + controller: password, + obscureText: true, + ), SizedBox(height: 12), - ElevatedButton(onPressed: () {}, child: Text("Sign in")), + ElevatedButton( + onPressed: () async { + isLoading.value = true; + final succeeded = await ref + .watch(ClientController.provider.notifier) + .login(username.text, password.text); + + if (!succeeded && context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "Login failed. Is your password right?", + style: TextStyle( + color: theme.colorScheme.onErrorContainer, + ), + ), + backgroundColor: theme.colorScheme.errorContainer, + ), + ); + isLoading.value = false; + } + }, + child: Text("Sign In"), + ), ], ], ),