From ea13e1129c957b17c20de82dc065ec1f052677fe Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Fri, 26 Jul 2024 09:59:53 -0400 Subject: [PATCH] Restore sign-in changes to fix username/email confusion --- lib/screens/sign_in.dart | 470 ++++++++++++++++++++------------------- 1 file changed, 240 insertions(+), 230 deletions(-) diff --git a/lib/screens/sign_in.dart b/lib/screens/sign_in.dart index 7870384..e4e6b3a 100644 --- a/lib/screens/sign_in.dart +++ b/lib/screens/sign_in.dart @@ -61,8 +61,7 @@ class _SignInScreenState extends State { void _updateSignInButtonStatus() { setState(() { signInButtonEnabled = switch (oauthType) { - usernamePasswordType => - serverNameController.text.isNotEmpty && + usernamePasswordType => serverNameController.text.isNotEmpty && usernameController.text.isNotEmpty && passwordController.text.isNotEmpty, oauthType => serverNameController.text.isNotEmpty, @@ -141,249 +140,259 @@ class _SignInScreenState extends State { child: Form( key: formKey, child: Center( - child: ListView( - children: [ - Center( - child: DropdownButton( - value: authType, - items: authTypes - .map( - (a) => DropdownMenuItem(value: a, child: Text(a))) - .toList(), - onChanged: existingAccount - ? null - : (value) { - if (existingAccount) { - buildSnackbar(context, - "Can't change the type on an existing account"); - return; - } - authType = value!; - setState(() {}); - }), - ), - const VerticalPadding(), - TextFormField( - autocorrect: false, - readOnly: existingAccount, - autovalidateMode: AutovalidateMode.onUserInteraction, - controller: serverNameController, - validator: (value) => - isFQDN(value ?? '') ? null : 'Not a valid server name', - decoration: InputDecoration( - hintText: 'Server Name (friendica.example.com)', - border: OutlineInputBorder( - borderSide: BorderSide( - color: Theme - .of(context) - .colorScheme - .surface, - ), - borderRadius: BorderRadius.circular(5.0), - ), - labelText: 'Server Name', - ), - ), - 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, + child: AutofillGroup( + child: ListView( + children: [ + Center( + child: DropdownButton( + value: authType, + items: authTypes + .map((a) => + DropdownMenuItem(value: a, child: Text(a))) + .toList(), + onChanged: existingAccount + ? null + : (value) { + if (existingAccount) { + buildSnackbar(context, + "Can't change the type on an existing account"); + return; + } + authType = value!; + setState(() {}); + }), ), const VerticalPadding(), - ], - if (showUsernameAndPasswordFields) ...[ TextFormField( + autocorrect: false, readOnly: existingAccount, autovalidateMode: AutovalidateMode.onUserInteraction, - controller: usernameController, - keyboardType: TextInputType.emailAddress, - validator: (value) { - if (value == null) { - return null; - } + controller: serverNameController, + validator: (value) => + isFQDN(value ?? '') ? null : 'Not a valid server name', + decoration: InputDecoration( + hintText: 'Server Name (friendica.example.com)', + border: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.surface, + ), + borderRadius: BorderRadius.circular(5.0), + ), + labelText: 'Server Name', + ), + ), + 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, + autovalidateMode: AutovalidateMode.onUserInteraction, + autofillHints: const [AutofillHints.username], + controller: usernameController, + keyboardType: TextInputType.emailAddress, + validator: (value) { + if (value == null) { + return null; + } - if (value.contains('@')) { - return isEmail(value) + if (value.contains('@')) { + final properFormat = isEmail(value); + if (!properFormat) { + return 'Not a valid Friendica Account Address'; + } + final elements = value.split('@'); + if (elements.last != serverNameController.text) { + return 'Server name must match above field.\nUsername should be the *Friendica* username.\nThis is not the email address of the account.'; + } + return null; + } + + return isAlphanumeric(value.replaceAll('-', '')) ? null - : 'Not a valid Friendica Account Address'; - } - - return isAlphanumeric(value.replaceAll('-', '')) - ? null - : 'Username should be alpha-numeric'; - }, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.alternate_email), - hintText: 'Username (user@example.com)', - border: OutlineInputBorder( - borderSide: BorderSide( - color: Theme - .of(context) - .colorScheme - .surface, + : 'Username should be alpha-numeric'; + }, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.alternate_email), + hintText: + 'Your username on the server (not email address)', + border: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.surface, + ), + borderRadius: BorderRadius.circular(5.0), ), - borderRadius: BorderRadius.circular(5.0), + labelText: 'Username', ), - labelText: 'Username', ), - ), - const VerticalPadding(), - TextFormField( - readOnly: existingAccount, - obscureText: hidePassword, - controller: passwordController, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Password field cannot be empty'; - } + const VerticalPadding(), + TextFormField( + readOnly: existingAccount, + obscureText: hidePassword, + controller: passwordController, + autofillHints: const [AutofillHints.password], + validator: (value) { + if (value == null || value.isEmpty) { + return 'Password field cannot be empty'; + } - return null; - }, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.password), - suffixIcon: IconButton( - onPressed: () { - setState(() { - hidePassword = !hidePassword; - }); - }, - icon: hidePassword - ? const Icon(Icons.remove_red_eye_outlined) - : const Icon(Icons.remove_red_eye), - ), - hintText: 'Password', - border: OutlineInputBorder( - borderSide: BorderSide( - color: Theme - .of(context) - .colorScheme - .surface, + return null; + }, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.password), + suffixIcon: IconButton( + onPressed: () { + setState(() { + hidePassword = !hidePassword; + }); + }, + icon: hidePassword + ? const Icon(Icons.remove_red_eye_outlined) + : const Icon(Icons.remove_red_eye), ), - borderRadius: BorderRadius.circular(5.0), + hintText: 'Password', + border: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.surface, + ), + borderRadius: BorderRadius.circular(5.0), + ), + labelText: 'Password', ), - labelText: 'Password', ), - ), + const VerticalPadding(), + ], + signInButtonEnabled + ? ElevatedButton( + onPressed: () async => await _signIn(context), + child: const Text('Signin'), + ) + : SizedBox(), const VerticalPadding(), + Text( + 'Logged out:', + style: Theme.of(context).textTheme.headlineSmall, + ), + loggedOutProfiles.isEmpty + ? const Text( + 'No logged out profiles', + textAlign: TextAlign.center, + ) + : Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + width: 0.5, + )), + child: Column( + children: loggedOutProfiles.map((p) { + return ListTile( + onTap: () { + setCredentials(context, p); + setState(() {}); + }, + title: Text(p.handle), + subtitle: Text(p.credentials is BasicCredentials + ? usernamePasswordType + : oauthType), + trailing: ElevatedButton( + onPressed: () async { + final confirm = await showYesNoDialog( + context, + 'Remove login information from app?'); + if (confirm ?? false) { + await service.removeProfile(p); + } + setState(() {}); + }, + child: const Text('Remove'), + ), + ); + }).toList(), + ), + ), + const VerticalPadding(), + Text( + 'Logged in:', + style: Theme.of(context).textTheme.headlineSmall, + ), + loggedInProfiles.isEmpty + ? const Text( + 'No logged in profiles', + textAlign: TextAlign.center, + ) + : Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + width: 0.5, + )), + child: Column( + children: loggedInProfiles.map((p) { + final active = service.loggedIn + ? p.id == service.currentProfile.id + : false; + return ListTile( + onTap: () async { + setCredentials(context, p); + setState(() {}); + }, + title: Text( + p.handle, + style: active + ? const TextStyle( + fontWeight: FontWeight.bold, + fontStyle: FontStyle.italic) + : null, + ), + subtitle: Text( + p.credentials is BasicCredentials + ? usernamePasswordType + : oauthType, + style: active + ? const TextStyle( + fontWeight: FontWeight.bold, + fontStyle: FontStyle.italic) + : null, + ), + trailing: ElevatedButton( + onPressed: () async { + final confirm = await showYesNoDialog( + context, 'Log out account?'); + if (confirm == true) { + await getIt().signOut(p); + setState(() {}); + } + }, + child: const Text('Sign out'), + ), + ); + }).toList(), + ), + ), + const VerticalPadding(), + ElevatedButton( + onPressed: () async { + final confirm = await showYesNoDialog(context, + 'Are you sure you want to logout and delete *all* accounts? This cannot be undone.') ?? + false; + print(confirm); + if (!confirm) { + return; + } + + await getIt().clearAllProfiles(); + }, + child: Text('Clear All')), ], - signInButtonEnabled - ? ElevatedButton( - onPressed: () async => await _signIn(context), - child: const Text('Signin'), - ) - : const SizedBox(), - const VerticalPadding(), - Text( - 'Logged out:', - style: Theme - .of(context) - .textTheme - .headlineSmall, - ), - loggedOutProfiles.isEmpty - ? const Text( - 'No logged out profiles', - textAlign: TextAlign.center, - ) - : Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - border: Border.all( - width: 0.5, - )), - child: Column( - children: loggedOutProfiles.map((p) { - return ListTile( - onTap: () { - setCredentials(context, p); - setState(() {}); - }, - title: Text(p.handle), - subtitle: Text(p.credentials is BasicCredentials - ? usernamePasswordType - : oauthType), - trailing: ElevatedButton( - onPressed: () async { - final confirm = await showYesNoDialog(context, - 'Remove login information from app?'); - if (confirm ?? false) { - await service.removeProfile(p); - } - setState(() {}); - }, - child: const Text('Remove'), - ), - ); - }).toList(), - ), - ), - const VerticalPadding(), - Text( - 'Logged in:', - style: Theme - .of(context) - .textTheme - .headlineSmall, - ), - loggedInProfiles.isEmpty - ? const Text( - 'No logged in profiles', - textAlign: TextAlign.center, - ) - : Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - border: Border.all( - width: 0.5, - )), - child: Column( - children: loggedInProfiles.map((p) { - final active = service.loggedIn - ? p.id == service.currentProfile.id - : false; - return ListTile( - onTap: () async { - setCredentials(context, p); - setState(() {}); - }, - title: Text( - p.handle, - style: active - ? const TextStyle( - fontWeight: FontWeight.bold, - fontStyle: FontStyle.italic) - : null, - ), - subtitle: Text( - p.credentials is BasicCredentials - ? usernamePasswordType - : oauthType, - style: active - ? const TextStyle( - fontWeight: FontWeight.bold, - fontStyle: FontStyle.italic) - : null, - ), - trailing: ElevatedButton( - onPressed: () async { - final confirm = await showYesNoDialog( - context, 'Log out account?'); - if (confirm == true) { - await getIt().signOut(p); - setState(() {}); - } - }, - child: const Text('Sign out'), - ), - ); - }).toList(), - ), - ), - ], + ), ), ), ), @@ -405,8 +414,9 @@ class _SignInScreenState extends State { } else { switch (authType) { case usernamePasswordType: + final username = usernameController.text.split('@').first; creds = BasicCredentials( - username: usernameController.text, + username: username, password: passwordController.text, serverName: serverNameController.text); break;