diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart index 38941e2..4f57549 100644 --- a/lib/pages/login_page.dart +++ b/lib/pages/login_page.dart @@ -2,58 +2,43 @@ import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/client_controller.dart"; -import "package:nexus/models/requests/login_request.dart"; import "package:nexus/widgets/appbar.dart"; class LoginPage extends HookConsumerWidget { final Uri homeserver; - const LoginPage({super.key, required this.homeserver}); @override Widget build(BuildContext context, WidgetRef ref) { final client = ref.watch(ClientController.provider.notifier); - final isLoggingIn = useState(false); - final hasError = useState(false); - final passwordFocusNode = useFocusNode(); - - final theme = Theme.of(context); + final isLoading = useState(false); final username = useTextEditingController(); final password = useTextEditingController(); + final inputError = useState(null); + Future tryLogin() async { - if (isLoggingIn.value) return; - isLoggingIn.value = true; - hasError.value = false; + isLoading.value = true; - final error = await client.login( - LoginRequest( - username: username.text, - password: password.text, - homeserverUrl: homeserver.origin, - ), - ); - - if (!context.mounted) return; - - if (error != null) { - hasError.value = true; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - "Login failed. Is your password right?\nError: $error", - style: TextStyle(color: theme.colorScheme.onErrorContainer), - ), - backgroundColor: theme.colorScheme.errorContainer, + try { + final error = await client.login( + .new( + username: username.text, + password: password.text, + homeserverUrl: homeserver.origin, ), ); - isLoggingIn.value = false; - } else { - Navigator.of(context).pop(); + + if (error != null) { + inputError.value = error; + isLoading.value = false; + } else { + if (context.mounted) Navigator.of(context).pop(); + } + } finally { + isLoading.value = false; } - passwordFocusNode.requestFocus(); - isLoggingIn.value = false; } return Scaffold( @@ -66,57 +51,24 @@ class LoginPage extends HookConsumerWidget { body: AlertDialog( title: Text("Login to ${homeserver.host}"), content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: .min, + crossAxisAlignment: .start, children: [ - Text("Enter your login credentials:"), - SizedBox(height: 12), TextField( autofocus: true, - textInputAction: TextInputAction.next, - onChanged: (newVal) { - if (hasError.value) { - hasError.value = false; - } - }, - decoration: InputDecoration( - label: Text("Username"), - focusedBorder: hasError.value - ? OutlineInputBorder( - borderSide: BorderSide(color: theme.colorScheme.error), - ) - : null, - enabledBorder: hasError.value - ? OutlineInputBorder( - borderSide: BorderSide(color: theme.colorScheme.error), - ) - : null, - ), + textInputAction: .next, + decoration: .new(label: Text("Username")), controller: username, ), SizedBox(height: 12), TextField( - focusNode: passwordFocusNode, - textInputAction: TextInputAction.done, + textInputAction: .done, onSubmitted: (_) => tryLogin(), - onChanged: (newVal) { - if (hasError.value) { - hasError.value = false; - } - }, selectAllOnFocus: true, - decoration: InputDecoration( + decoration: .new( label: Text("Password"), - focusedBorder: hasError.value - ? OutlineInputBorder( - borderSide: BorderSide(color: theme.colorScheme.error), - ) - : null, - enabledBorder: hasError.value - ? OutlineInputBorder( - borderSide: BorderSide(color: theme.colorScheme.error), - ) - : null, + errorText: inputError.value, + errorMaxLines: 5, ), controller: password, obscureText: true, @@ -125,7 +77,7 @@ class LoginPage extends HookConsumerWidget { ), actions: [ TextButton( - onPressed: hasError.value ? null : tryLogin, + onPressed: isLoading.value ? null : tryLogin, child: Text("Sign In"), ), ], diff --git a/lib/pages/verify_page.dart b/lib/pages/verify_page.dart index 0469fa4..5f8c156 100644 --- a/lib/pages/verify_page.dart +++ b/lib/pages/verify_page.dart @@ -3,7 +3,6 @@ import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/client_controller.dart"; import "package:nexus/widgets/appbar.dart"; -import "package:nexus/widgets/form_text_input.dart"; class VerifyPage extends HookConsumerWidget { const VerifyPage({super.key}); @@ -11,7 +10,8 @@ class VerifyPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final passphraseController = useTextEditingController(); - final isVerifying = useState(false); + final isLoading = useState(false); + return Scaffold( appBar: Appbar(), body: AlertDialog( @@ -24,19 +24,17 @@ class VerifyPage extends HookConsumerWidget { "Enter your recovery key or passphrase below to unlock encrypted events.\nYour passphrase is usually not the same as your password.", ), SizedBox(height: 12), - FormTextInput( - required: false, + TextField( autofocus: true, - capitalize: true, controller: passphraseController, - obscure: true, - title: "Recovery Key or Passphrase", + obscureText: true, + decoration: .new(label: Text("Recovery Key or Passphrase")), ), ], ), actions: [ TextButton( - onPressed: isVerifying.value + onPressed: isLoading.value ? null : () async { final scaffoldMessenger = ScaffoldMessenger.of(context); @@ -49,7 +47,7 @@ class VerifyPage extends HookConsumerWidget { ), ); - isVerifying.value = true; + isLoading.value = true; final error = await ref .watch(ClientController.provider.notifier) @@ -57,7 +55,7 @@ class VerifyPage extends HookConsumerWidget { snackbar.close(); if (error != null) { - isVerifying.value = false; + isLoading.value = false; if (context.mounted) { scaffoldMessenger.showSnackBar( .new( diff --git a/lib/widgets/form_text_input.dart b/lib/widgets/form_text_input.dart deleted file mode 100644 index 8b48883..0000000 --- a/lib/widgets/form_text_input.dart +++ /dev/null @@ -1,81 +0,0 @@ -import "package:flutter/material.dart"; -import "package:flutter/services.dart"; - -class FormTextInput extends StatelessWidget { - final List extraValidators; - final TextEditingController? controller; - final TextInputType keyboardType; - final String? initialValue; - final bool readOnly; - final bool obscure; - final String? title; - final int? minLines; - final int? maxLength; - final bool outlined; - final int? maxLines; - final bool capitalize; - final bool required; - final bool autocorrect; - final void Function()? onTap; - final Widget? trailing; - final InputBorder? border; - final List? formatters; - final bool autofocus; - - const FormTextInput({ - super.key, - this.border, - this.controller, - this.autofocus = false, - this.title, - this.obscure = false, - this.readOnly = false, - this.extraValidators = const [], - this.keyboardType = TextInputType.text, - this.initialValue, - this.minLines, - this.capitalize = false, - this.maxLength, - this.formatters, - this.maxLines = 1, - this.outlined = true, - this.trailing, - this.onTap, - this.autocorrect = true, - this.required = true, - }); - - @override - Widget build(BuildContext context) => TextFormField( - autofocus: autofocus, - controller: controller, - keyboardType: keyboardType, - readOnly: readOnly, - minLines: minLines, - maxLines: maxLines, - maxLength: maxLength, - inputFormatters: formatters, - textCapitalization: capitalize ? .sentences : .none, - initialValue: initialValue, - autocorrect: autocorrect, - obscureText: obscure, - onTap: onTap, - decoration: .new( - labelText: title, - border: border ?? (outlined ? null : const UnderlineInputBorder()), - suffixIcon: trailing, - ), - validator: (value) { - if ((value?.isEmpty ?? true) && required) { - return "This field is required"; - } - - for (final validator in extraValidators) { - final reason = validator(value!); - if (reason != null) return reason; - } - - return null; - }, - ); -}