Refactor signin UI to be more user friendly.

This commit is contained in:
Hank Grabowski 2023-11-16 23:17:36 -05:00
parent 10bba23d61
commit 1162f988f0

View file

@ -23,7 +23,7 @@ class SignInScreen extends StatefulWidget {
class _SignInScreenState extends State<SignInScreen> {
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<FormState>();
final usernameController = TextEditingController();
@ -31,9 +31,12 @@ class _SignInScreenState extends State<SignInScreen> {
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<SignInScreen> {
}
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<SignInScreen> {
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<SignInScreen> {
.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<SignInScreen> {
),
),
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<SignInScreen> {
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<SignInScreen> {
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<SignInScreen> {
},
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<SignInScreen> {
if (confirm ?? false) {
await service.removeProfile(p);
}
setState(() {});
},
child: const Text('Remove'),
),
@ -297,16 +311,6 @@ class _SignInScreenState extends State<SignInScreen> {
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<SignInScreen> {
),
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<SignInScreen> {
context, 'Log out account?');
if (confirm == true) {
await getIt<AccountsService>().signOut(p);
setState(() {});
}
},
child: const Text('Sign out'),
@ -349,8 +354,17 @@ class _SignInScreenState extends State<SignInScreen> {
}
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<SignInScreen> {
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<AccountsService>().signIn(creds);
if (mounted && result.isFailure) {
buildSnackbar(context, 'Error signing in: ${result.error}');
return;
}
await getIt<AccountsService>().setActiveProfile(result.value);
if (mounted) {
context.goNamed(ScreenPaths.timelines);
}
final result = await getIt<AccountsService>().signIn(creds);
if (mounted && result.isFailure) {
buildSnackbar(context, 'Error signing in: ${result.error}');
return;
}
await getIt<AccountsService>().setActiveProfile(result.value);
if (mounted) {
context.goNamed(ScreenPaths.timelines);
}
}
}