From 1162f988f01727a02387288926d889da810a5bc3 Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Thu, 16 Nov 2023 23:17:36 -0500 Subject: [PATCH] Refactor signin UI to be more user friendly. --- lib/screens/sign_in.dart | 152 +++++++++++++++++++++------------------ 1 file changed, 83 insertions(+), 69 deletions(-) diff --git a/lib/screens/sign_in.dart b/lib/screens/sign_in.dart index e1b9261..81ba373 100644 --- a/lib/screens/sign_in.dart +++ b/lib/screens/sign_in.dart @@ -23,7 +23,7 @@ class SignInScreen extends StatefulWidget { class _SignInScreenState extends State { static final _logger = Logger('$SignInScreen'); static const usernamePasswordType = 'Username/Password'; - static const oauthType = 'OAuth'; + static const oauthType = 'Standard Login'; static final authTypes = [usernamePasswordType, oauthType]; final formKey = GlobalKey(); final usernameController = TextEditingController(); @@ -31,9 +31,12 @@ class _SignInScreenState extends State { final passwordController = TextEditingController(); var authType = oauthType; var hidePassword = true; - var showUsernameAndPasswordFields = false; var signInButtonEnabled = false; - var existingAccount = false; + Profile? existingProfile; + + bool get showUsernameAndPasswordFields => authType == usernamePasswordType; + + bool get existingAccount => existingProfile != null; @override void initState() { @@ -47,32 +50,29 @@ class _SignInScreenState extends State { } void newProfile() { - usernameController.text = ''; - passwordController.text = ''; - serverNameController.text = ''; - showUsernameAndPasswordFields = false; + usernameController.clear(); + passwordController.clear(); + serverNameController.clear(); authType = oauthType; signInButtonEnabled = true; - existingAccount = false; + existingProfile = null; } void setBasicCredentials(BasicCredentials credentials) { usernameController.text = credentials.username; passwordController.text = credentials.password; serverNameController.text = credentials.serverName; - showUsernameAndPasswordFields = true; authType = usernamePasswordType; } void setOauthCredentials(OAuthCredentials credentials) { serverNameController.text = credentials.serverName; - showUsernameAndPasswordFields = false; authType = oauthType; } void setCredentials(BuildContext? context, Profile profile) { final ICredentials credentials = profile.credentials; - existingAccount = true; + existingProfile = profile; signInButtonEnabled = !profile.loggedIn; if (credentials is BasicCredentials) { setBasicCredentials(credentials); @@ -99,7 +99,20 @@ class _SignInScreenState extends State { final loggedOutProfiles = service.loggedOutProfiles; return Scaffold( appBar: AppBar( - title: const Text('Sign In'), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('Sign In'), + IconButton( + icon: const Icon(Icons.add), + tooltip: 'Add new account', + onPressed: () { + newProfile(); + setState(() {}); + }, + ) + ], + ), ), body: Padding( padding: const EdgeInsets.all(20.0), @@ -115,26 +128,17 @@ class _SignInScreenState extends State { .map( (a) => DropdownMenuItem(value: a, child: Text(a))) .toList(), - onChanged: (value) { - if (existingAccount) { - buildSnackbar(context, - "Can't change the type on an existing account"); - return; - } - setState(() { - authType = value ?? ''; - switch (value) { - case usernamePasswordType: - showUsernameAndPasswordFields = true; - break; - case oauthType: - showUsernameAndPasswordFields = false; - break; - default: - print("Don't know this"); - } - }); - }), + onChanged: existingAccount + ? null + : (value) { + if (existingAccount) { + buildSnackbar(context, + "Can't change the type on an existing account"); + return; + } + authType = value!; + setState(() {}); + }), ), const VerticalPadding(), TextFormField( @@ -156,6 +160,15 @@ class _SignInScreenState extends State { ), ), const VerticalPadding(), + if (!showUsernameAndPasswordFields) ...[ + Text( + existingAccount + ? 'Configured to sign in as user ${existingProfile?.handle}' + : 'Relatica will open the requested Friendica site in a web browser where you will be asked to authorize this client.', + softWrap: true, + ), + const VerticalPadding(), + ], if (showUsernameAndPasswordFields) ...[ TextFormField( readOnly: existingAccount, @@ -194,6 +207,13 @@ class _SignInScreenState extends State { readOnly: existingAccount, obscureText: hidePassword, controller: passwordController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Password field cannot be empty'; + } + + return null; + }, decoration: InputDecoration( prefixIcon: const Icon(Icons.password), suffixIcon: IconButton( @@ -223,14 +243,7 @@ class _SignInScreenState extends State { onPressed: () => _signIn(context), child: const Text('Signin'), ) - : ElevatedButton( - onPressed: () { - setState(() { - newProfile(); - }); - }, - child: const Text('New'), - ), + : SizedBox(), const VerticalPadding(), Text( 'Logged out:', @@ -256,8 +269,8 @@ class _SignInScreenState extends State { }, title: Text(p.handle), subtitle: Text(p.credentials is BasicCredentials - ? 'Username/Password' - : 'OAuth Login'), + ? usernamePasswordType + : oauthType), trailing: ElevatedButton( onPressed: () async { final confirm = await showYesNoDialog(context, @@ -265,6 +278,7 @@ class _SignInScreenState extends State { if (confirm ?? false) { await service.removeProfile(p); } + setState(() {}); }, child: const Text('Remove'), ), @@ -297,16 +311,6 @@ class _SignInScreenState extends State { onTap: () async { setCredentials(context, p); setState(() {}); - - final confirm = await showYesNoDialog( - context, 'Switch to account?'); - - if (confirm ?? false) { - service.setActiveProfile(p); - if (mounted) { - context.goNamed(ScreenPaths.timelines); - } - } }, title: Text( p.handle, @@ -318,8 +322,8 @@ class _SignInScreenState extends State { ), subtitle: Text( p.credentials is BasicCredentials - ? 'Username/Password' - : 'OAuth Login', + ? usernamePasswordType + : oauthType, style: active ? const TextStyle( fontWeight: FontWeight.bold, @@ -332,6 +336,7 @@ class _SignInScreenState extends State { context, 'Log out account?'); if (confirm == true) { await getIt().signOut(p); + setState(() {}); } }, child: const Text('Sign out'), @@ -349,8 +354,17 @@ class _SignInScreenState extends State { } void _signIn(BuildContext context) async { - if (formKey.currentState?.validate() ?? false) { - ICredentials? creds; + final valid = formKey.currentState?.validate() ?? false; + if (!valid) { + buildSnackbar( + context, 'Cannot login. Fix highlighted errors and try again.'); + return; + } + + ICredentials? creds; + if (existingProfile != null) { + creds = existingProfile?.credentials; + } else { switch (authType) { case usernamePasswordType: creds = BasicCredentials( @@ -365,22 +379,22 @@ class _SignInScreenState extends State { buildSnackbar(context, 'Unknown authorization type: $authType'); break; } + } - if (creds == null) { - return; - } + if (creds == null) { + return; + } - print('Sign in credentials: ${creds.toJson()}'); + print('Sign in credentials: ${creds.toJson()}'); - final result = await getIt().signIn(creds); - if (mounted && result.isFailure) { - buildSnackbar(context, 'Error signing in: ${result.error}'); - return; - } - await getIt().setActiveProfile(result.value); - if (mounted) { - context.goNamed(ScreenPaths.timelines); - } + final result = await getIt().signIn(creds); + if (mounted && result.isFailure) { + buildSnackbar(context, 'Error signing in: ${result.error}'); + return; + } + await getIt().setActiveProfile(result.value); + if (mounted) { + context.goNamed(ScreenPaths.timelines); } } }