import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart'; import 'package:relatica/models/auth/basic_credentials.dart'; import 'package:relatica/routes.dart'; import 'package:string_validator/string_validator.dart'; import '../controls/padding.dart'; import '../globals.dart'; import '../models/auth/credentials_intf.dart'; import '../models/auth/oauth_credentials.dart'; import '../services/auth_service.dart'; import '../utils/snackbar_builder.dart'; class SignInScreen extends StatefulWidget { @override State createState() => _SignInScreenState(); } class _SignInScreenState extends State { static final _logger = Logger('$SignInScreen'); static const usernamePasswordType = 'Username/Password'; static const oauthType = 'OAuth'; static final authTypes = [usernamePasswordType, oauthType]; final formKey = GlobalKey(); final usernameController = TextEditingController(); final serverNameController = TextEditingController(); final passwordController = TextEditingController(); var authType = oauthType; var hidePassword = true; var showUsernameAndPasswordFields = true; @override void initState() { super.initState(); final service = getIt(); if (service.loggedIn) { setCredentials(null, service.currentProfile.credentials); } } 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, ICredentials credentials) { if (credentials is BasicCredentials) { setBasicCredentials(credentials); return; } if (credentials is OAuthCredentials) { setOauthCredentials(credentials); return; } final msg = 'Unknown credentials type: ${credentials.runtimeType}'; _logger.severe(msg); if (context?.mounted ?? false) { buildSnackbar(context!, msg); } } @override Widget build(BuildContext context) { final service = getIt(); final loggedInProfiles = service.loggedInProfiles; final loggedOutProfiles = service.loggedOutProfiles; return Scaffold( appBar: AppBar( title: const Text('Sign In'), ), body: Padding( padding: const EdgeInsets.all(20.0), child: Form( key: formKey, child: Center( child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ DropdownButton( value: authType, items: authTypes .map((a) => DropdownMenuItem(value: a, child: Text(a))) .toList(), onChanged: (value) { setState(() { authType = value ?? ''; switch (value) { case usernamePasswordType: showUsernameAndPasswordFields = true; break; case oauthType: showUsernameAndPasswordFields = false; break; default: print("Don't know this"); } }); }), const VerticalPadding(), TextFormField( autovalidateMode: AutovalidateMode.onUserInteraction, controller: serverNameController, validator: (value) => isFQDN(value ?? '') ? null : 'Not a valid server name', decoration: InputDecoration( prefixIcon: const Icon(Icons.alternate_email), hintText: 'Server Name (friendica.example.com)', border: OutlineInputBorder( borderSide: BorderSide( color: Theme.of(context).backgroundColor, ), borderRadius: BorderRadius.circular(5.0), ), labelText: 'Server Name', ), ), const VerticalPadding(), if (showUsernameAndPasswordFields) ...[ TextFormField( autovalidateMode: AutovalidateMode.onUserInteraction, controller: usernameController, keyboardType: TextInputType.emailAddress, validator: (value) { if (value == null) { return null; } if (value.contains('@')) { return isEmail(value) ? 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).backgroundColor, ), borderRadius: BorderRadius.circular(5.0), ), labelText: 'Username', ), ), const VerticalPadding(), TextFormField( obscureText: hidePassword, controller: passwordController, 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).backgroundColor, ), borderRadius: BorderRadius.circular(5.0), ), labelText: 'Password', ), ), const VerticalPadding(), ], ElevatedButton( onPressed: () => _signIn(context), child: const Text('Signin'), ), const VerticalPadding(), const Text('Logged out:'), Expanded( flex: 1, child: ListView.separated( itemBuilder: (context, index) { final p = loggedOutProfiles[index]; return ListTile( onTap: () { setCredentials(context, p.credentials); setState(() {}); }, title: Text(p.handle), subtitle: Text(p.credentials is BasicCredentials ? 'Username/Password' : 'OAuth Login'), ); }, separatorBuilder: (_, __) => const Divider(), itemCount: loggedOutProfiles.length, ), ), const VerticalPadding(), const Text('Logged in:'), Expanded( flex: 1, child: ListView.separated( itemBuilder: (context, index) { final p = loggedInProfiles[index]; final active = service.loggedIn ? p.id == service.currentProfile.id : false; return ListTile( onTap: () async { setCredentials(context, p.credentials); setState(() {}); await service.setActiveProfile(p); if (mounted) { clearCaches(); context.goNamed(ScreenPaths.timelines); } }, title: Text( p.handle, style: active ? const TextStyle( fontWeight: FontWeight.bold, fontStyle: FontStyle.italic) : null, ), subtitle: Text( p.credentials is BasicCredentials ? 'Username/Password' : 'OAuth Login', style: active ? const TextStyle( fontWeight: FontWeight.bold, fontStyle: FontStyle.italic) : null, ), ); }, separatorBuilder: (_, __) => const Divider(), itemCount: loggedInProfiles.length, ), ), ], ), ), ), ), ); } void _signIn(BuildContext context) async { if (formKey.currentState?.validate() ?? false) { ICredentials? creds; switch (authType) { case usernamePasswordType: creds = BasicCredentials( username: usernameController.text, password: passwordController.text, serverName: serverNameController.text); break; case oauthType: creds = OAuthCredentials.bootstrap(serverNameController.text); break; default: buildSnackbar(context, 'Unknown authorization type: $authType'); break; } if (creds == null) { return; } final result = await getIt().signIn(creds); if (!mounted) { return; } if (result.isFailure) { buildSnackbar(context, 'Error signing in: ${result.error}'); } context.goNamed(ScreenPaths.timelines); } } }