diff --git a/lib/controllers/client_controller.dart b/lib/controllers/client_controller.dart index fc4a31f..f1ff6a6 100644 --- a/lib/controllers/client_controller.dart +++ b/lib/controllers/client_controller.dart @@ -263,12 +263,12 @@ class ClientController extends AsyncNotifier { } } - Future discoverHomeserver(Uri homeserver) async { + Future discoverHomeserver(Uri homeserver) async { try { final response = await _sendCommand("discover_homeserver", { "user_id": "@fake-user:${homeserver.host}", }); - return Uri.parse(response["m.homeserver"]?["base_url"]); + return response["m.homeserver"]?["base_url"]; } catch (error) { return null; } diff --git a/lib/main.dart b/lib/main.dart index dab4e16..cb1e53d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -123,7 +123,7 @@ class App extends StatelessWidget { return Loading(); } - if (!clientState.isLoggedIn) { + if (!clientState.isLoggedIn) { return SelectServerPage(); } else if (!clientState.isVerified) { return VerifyPage(); diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart index 3b2b8a6..5d12d00 100644 --- a/lib/pages/login_page.dart +++ b/lib/pages/login_page.dart @@ -15,8 +15,17 @@ class LoginPage extends HookConsumerWidget { final client = ref.watch(ClientController.provider.notifier); final isLoggingIn = useState(false); final hasError = useState(false); + final userNameFocusNode = useFocusNode(); final passwordFocusNode = useFocusNode(); + //This is the safe way to request things directly after page load. + useEffect(() { + WidgetsBinding.instance.addPostFrameCallback((_) { + userNameFocusNode.requestFocus(); + }); + return null; + }, []); + final theme = Theme.of(context); final username = useTextEditingController(); @@ -31,8 +40,8 @@ class LoginPage extends HookConsumerWidget { LoginRequest( username: username.text, password: password.text, - homeserverUrl: homeserver.origin, - ), + homeserverUrl: homeserver.origin + ) ); if (!context.mounted) return; @@ -43,13 +52,16 @@ class LoginPage extends HookConsumerWidget { SnackBar( content: Text( "Login failed. Is your password right?\nError: $error", - style: TextStyle(color: theme.colorScheme.onErrorContainer), + style: TextStyle( + color: theme.colorScheme.onErrorContainer, + ), ), backgroundColor: theme.colorScheme.errorContainer, ), ); isLoggingIn.value = false; - } else { + } + else{ Navigator.pop(context); } passwordFocusNode.requestFocus(); @@ -60,8 +72,8 @@ class LoginPage extends HookConsumerWidget { appBar: Appbar( leading: IconButton( icon: Icon(Icons.arrow_back), - onPressed: () => Navigator.pop(context), - ), + onPressed: () => Navigator.pop(context) + ), ), body: AlertDialog( title: Text("Login to ${homeserver.host}"), @@ -69,10 +81,12 @@ class LoginPage extends HookConsumerWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Enter your login credentials:"), + Text( + "Enter your login credentials:", + ), SizedBox(height: 12), TextField( - autofocus: true, + focusNode: userNameFocusNode, textInputAction: TextInputAction.next, onChanged: (newVal) { if (hasError.value) { @@ -81,22 +95,14 @@ class LoginPage extends HookConsumerWidget { }, decoration: InputDecoration( label: Text("Username"), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - width: hasError.value ? 4 : 2, - color: hasError.value - ? theme.colorScheme.error - : theme.colorScheme.primary, - ), - ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( width: hasError.value ? 4 : 2, - color: hasError.value - ? theme.colorScheme.error - : theme.colorScheme.primary, + color: hasError.value + ? theme.colorScheme.error + : theme.colorScheme.primary ), - ), + ) ), controller: username, ), @@ -117,26 +123,29 @@ class LoginPage extends HookConsumerWidget { borderSide: BorderSide( width: hasError.value ? 4 : 2, color: hasError.value - ? theme.colorScheme.error - : theme.colorScheme.primary, - ), + ? theme.colorScheme.error + : theme.colorScheme.primary + ) ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( width: hasError.value ? 4 : 2, - color: hasError.value - ? theme.colorScheme.error - : theme.colorScheme.primary, + color: hasError.value + ? theme.colorScheme.error + : theme.colorScheme.primary ), - ), + ) ), controller: password, obscureText: true, - ), + ), ], ), actions: [ - TextButton(onPressed: () => tryLogin(), child: Text("Sign In")), + TextButton( + onPressed: () => tryLogin(), + child: Text("Sign In"), + ), ], ), ); diff --git a/lib/pages/select_server_page.dart b/lib/pages/select_server_page.dart index 8a8ac9f..af9be91 100644 --- a/lib/pages/select_server_page.dart +++ b/lib/pages/select_server_page.dart @@ -20,14 +20,14 @@ class SelectServerPage extends HookConsumerWidget { final hasError = useState(false); final isLoading = useState(false); final homeserverFocusNode = useFocusNode(); - + final launch = ref.watch(LaunchHelper.provider).launchUrl; final homeserverUrl = useTextEditingController(); Future setHomeserver(Uri? newHomeserver) async { isLoading.value = true; - if (newHomeserver?.hasScheme == false) { + if(newHomeserver?.hasScheme == false){ newHomeserver = Uri.https(newHomeserver!.path); } @@ -49,9 +49,7 @@ class SelectServerPage extends HookConsumerWidget { ); } else { homeserverUrl.text = newHomeserver!.origin; - await Navigator.of(context).push( - MaterialPageRoute(builder: (_) => LoginPage(homeserver: newUrl)), - ); + Navigator.push(context, MaterialPageRoute(builder: (_) => LoginPage(homeserver: Uri.parse(newUrl)))); } } @@ -61,163 +59,149 @@ class SelectServerPage extends HookConsumerWidget { return Scaffold( appBar: Appbar(), - body: isLoading.value - ? const Loading() - : Center( - child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: 600), - child: Column( - children: [ - Row( + body: isLoading.value + ? const Loading() + : Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 600), + child: Column( + children: [ + Row( + children: [ + SvgPicture.asset("assets/icon.svg", width: 128), + SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - SvgPicture.asset("assets/icon.svg", width: 128), - SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Nexus", - style: theme.textTheme.displayMedium, - ), - Text( - "A Simple Matrix Client", - style: theme.textTheme.headlineMedium, - overflow: TextOverflow.ellipsis, - ), - ], - ), + Text("Nexus", style: theme.textTheme.displayMedium), + Text( + "A Simple Matrix Client", + style: theme.textTheme.headlineMedium, + overflow: TextOverflow.ellipsis, ), ], ), - Padding( - padding: EdgeInsetsGeometry.symmetric(vertical: 12), - child: Divider(), - ), - DividerText("Enter a homeserver domain:"), - Row( - spacing: 8, - children: [ - Expanded( - child: TextField( - focusNode: homeserverFocusNode, - textInputAction: TextInputAction.done, - onSubmitted: (_) => - setHomeserver(Uri.tryParse(homeserverUrl.text)), - onChanged: (newVal) { - if (hasError.value) { - hasError.value = false; - } - }, - controller: homeserverUrl, - decoration: InputDecoration( - labelText: "Homeserver URL (e.g. matrix.org)", - hintText: "e.g. matrix.org", - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - width: hasError.value ? 4 : 2, - color: hasError.value - ? theme.colorScheme.error - : theme.colorScheme.primary, - ), - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - width: hasError.value ? 4 : 2, - color: hasError.value - ? theme.colorScheme.error - : theme.colorScheme.primary, - ), - ), - ), - ), + ), + ], + ), + Padding( + padding: EdgeInsetsGeometry.symmetric(vertical: 12), + child: Divider(), + ), + DividerText("Enter a homeserver domain:"), + Row( + spacing: 8, + children: [ + Expanded( + child: TextField( + focusNode: homeserverFocusNode, + textInputAction: TextInputAction.done, + onSubmitted: (_) => setHomeserver(Uri.tryParse(homeserverUrl.text)), + onChanged: (newVal) { + if (hasError.value) { + hasError.value = false; + } + }, + controller: homeserverUrl, + decoration: InputDecoration( + labelText: "Homeserver URL (e.g. matrix.org)", + hintText: "e.g. matrix.org", + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + width: hasError.value ? 4 : 2, + color: hasError.value + ? theme.colorScheme.error + : theme.colorScheme.primary + ) ), - IconButton.filled( - tooltip: "Confirm homeserver choice", - onPressed: isLoading.value - ? null - : () => setHomeserver( - Uri.tryParse(homeserverUrl.text), - ), - icon: Icon(Icons.check), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + width: hasError.value ? 4 : 2, + color: hasError.value + ? theme.colorScheme.error + : theme.colorScheme.primary + ) ), - ], - ), - Expanded( - child: ListView( - padding: EdgeInsets.only(top: 12), - children: [ - DividerText( - "Or, choose from some popular homeservers:", - ), - ...([ - Homeserver( - name: "Matrix.org", - description: - "The Matrix.org Foundation offers the matrix.org homeserver as an easy entry point for anyone wanting to try out Matrix.", - url: Uri.https("matrix.org"), - iconUrl: - "https://raw.githubusercontent.com/element-hq/logos/refs/heads/master/matrix/matrix-favicon${Theme.brightnessOf(context) == Brightness.dark ? "-white" : ""}.png", - ), - Homeserver( - name: "Federated Nexus", - description: - "Federated Nexus is a community resource hosting multiple FOSS (especially federated) services, including Matrix and Forgejo. By the same developers who made Nexus client.", - url: Uri.https("federated.nexus"), - iconUrl: - "https://federated.nexus/images/icon.png", - ), - Homeserver( - name: "Unredacted", - description: - "Unredacted is a 501(c)(3) non-profit organization that builds Internet infrastructure and services to help people evade censorship and protect their right to privacy.", - url: Uri.https( - "unredacted.org", - "services/si/matrix", - ), - iconUrl: "https://unredacted.org/favicon.ico", - ), - Homeserver( - name: "Lorem ipsum", - description: - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - url: Uri.https("loremipsum.io"), - iconUrl: "https://loremipsum.io/favicon.ico", - ), - ].map( - (homeserver) => Card( - child: ListTile( - title: Text(homeserver.name), - leading: Image.network( - homeserver.iconUrl, - errorBuilder: (_, _, _) => SizedBox.shrink(), - height: 32, - ), - subtitle: Text(homeserver.description), - onTap: isLoading.value - ? null - : () => setHomeserver(homeserver.url), - trailing: IconButton( - tooltip: "Launch homeserver info page", - onPressed: () => launch(homeserver.url), - icon: Icon(Icons.info_outline), - ), - ), - ), - )), - ], ), ), - SizedBox(height: 5), - TextButton( - onPressed: () => - launch(Uri.https("servers.joinmatrix.org")), - child: Text("See more homeservers..."), - ), + ), + IconButton.filled( + tooltip: "Confirm homeserver choice", + onPressed: isLoading.value + ? null + : () => setHomeserver(Uri.tryParse(homeserverUrl.text)), + icon: Icon(Icons.check), + ), + ], + ), + Expanded( + child: ListView( + padding: EdgeInsets.only(top: 12), + children: [ + DividerText("Or, choose from some popular homeservers:"), + ...([ + Homeserver( + name: "Matrix.org", + description: + "The Matrix.org Foundation offers the matrix.org homeserver as an easy entry point for anyone wanting to try out Matrix.", + url: Uri.https("matrix.org"), + iconUrl: + "https://raw.githubusercontent.com/element-hq/logos/refs/heads/master/matrix/matrix-favicon${Theme.brightnessOf(context) == Brightness.dark ? "-white" : ""}.png", + ), + Homeserver( + name: "Federated Nexus", + description: + "Federated Nexus is a community resource hosting multiple FOSS (especially federated) services, including Matrix and Forgejo. By the same developers who made Nexus client.", + url: Uri.https("federated.nexus"), + iconUrl: "https://federated.nexus/images/icon.png", + ), + Homeserver( + name: "Unredacted", + description: + "Unredacted is a 501(c)(3) non-profit organization that builds Internet infrastructure and services to help people evade censorship and protect their right to privacy.", + url: Uri.https("unredacted.org", "services/si/matrix"), + iconUrl: "https://unredacted.org/favicon.ico", + ), + Homeserver( + name: "Lorem ipsum", + description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + url: Uri.https("loremipsum.io"), + iconUrl: "https://loremipsum.io/favicon.ico" + ), + ].map( + (homeserver) => Card( + child: ListTile( + title: Text(homeserver.name), + leading: Image.network( + homeserver.iconUrl, + errorBuilder: (_, _, _) => SizedBox.shrink(), + height: 32, + ), + subtitle: Text(homeserver.description), + onTap: isLoading.value + ? null + : () => setHomeserver(homeserver.url), + trailing: IconButton( + tooltip: "Launch homeserver info page", + onPressed: () => launch(homeserver.url), + icon: Icon(Icons.info_outline), + ), + ), + ), + )), ], ), ), - ), + SizedBox(height: 5), + TextButton( + onPressed: () => launch(Uri.https("servers.joinmatrix.org")), + child: Text("See more homeservers..."), + ), + ], + ), + ), + ), ); } }