-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This change changes the login flow to first request the uri of the server, then the user selects login with password or quick connect. At that point, the existing password login flow takes over, or a new one that gets a quick connect code is used. As part of this change, the auth information stored by the system has been changed. Previously the password had been stored. This is probably not needed (at least basic usage shows it doesn't affect anything) since there's a refresh token there. Since both login flows provide a refresh token, we can store that instead.
- Loading branch information
1 parent
e370982
commit 12fc38e
Showing
9 changed files
with
592 additions
and
202 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import 'package:dio/dio.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter/services.dart'; | ||
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||
|
||
Future<void> showErrorDialog( | ||
BuildContext context, | ||
Object e, | ||
) { | ||
return showInfoDialog( | ||
context, | ||
Text( | ||
AppLocalizations.of(context)!.errorConnectingToServer, | ||
), | ||
content: Text(e.toString()) | ||
); | ||
} | ||
|
||
Future<void> showHttpErrorDialog( | ||
BuildContext context, | ||
DioException e, | ||
) { | ||
return showInfoDialog( | ||
context, | ||
Text( | ||
AppLocalizations.of(context)!.errorConnectingToServer, | ||
), | ||
content: e.response?.statusCode == null | ||
? Text(e.toString()) | ||
: Text(_formatHttpErrorCode(e.response)), | ||
); | ||
} | ||
|
||
Future<void> showInfoDialog( | ||
BuildContext context, | ||
Widget title, { | ||
Widget? content, | ||
}) async { | ||
await showDialog( | ||
context: context, | ||
builder: (context) => CallbackShortcuts( | ||
bindings: <ShortcutActivator, VoidCallback>{ | ||
const SingleActivator(LogicalKeyboardKey.enter): () { | ||
Navigator.pop(context); | ||
} | ||
}, | ||
child: FocusScope( | ||
autofocus: true, | ||
child: AlertDialog( | ||
title: title, | ||
content: content, | ||
actions: [ | ||
ElevatedButton( | ||
onPressed: () { | ||
Navigator.pop(context); | ||
}, | ||
child: const Text('Ok'), | ||
) | ||
], | ||
), | ||
), | ||
), | ||
); | ||
} | ||
|
||
String _formatHttpErrorCode(Response? resp) { | ||
// todo PLACEHOLDER MESSAGES NOT FINAL | ||
var message = ''; | ||
switch (resp!.statusCode) { | ||
case 400: | ||
message = | ||
'The server could not understand the request, if you are using proxies check the configuration, if the issue still persists let us know'; | ||
case 401: | ||
message = 'Your username or password may be incorrect'; | ||
case 403: | ||
message = | ||
'The server is blocking request from this device, this probably means the device has been banned, please contact your admin to resolve this issue'; | ||
default: | ||
message = ''; | ||
} | ||
|
||
return '$message\n\n' | ||
'Http Code: ${resp.statusCode ?? 'Unknown'}\n\n' | ||
'Http Response: ${resp.statusMessage ?? 'Unknown'}\n\n' | ||
.trim(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
import 'package:dio/dio.dart'; | ||
import 'package:flutter/foundation.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter/services.dart'; | ||
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||
import 'package:flutter_hooks/flutter_hooks.dart'; | ||
import 'package:go_router/go_router.dart'; | ||
import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||
import 'package:jellyflix/models/screen_paths.dart'; | ||
import 'package:jellyflix/models/user.dart'; | ||
import 'package:jellyflix/providers/auth_provider.dart'; | ||
import 'package:jellyflix/screens/login_messages.dart'; | ||
|
||
class LoginWithPasswordScreen extends HookConsumerWidget { | ||
final String serverAddress; | ||
const LoginWithPasswordScreen({super.key, required this.serverAddress}); | ||
|
||
@override | ||
Widget build(BuildContext context, WidgetRef ref) { | ||
final userName = useTextEditingController(); | ||
final password = useTextEditingController(); | ||
|
||
final loadingListenable = useValueNotifier<bool>(false); | ||
|
||
return Scaffold( | ||
appBar: AppBar(), | ||
body: CallbackShortcuts( | ||
bindings: <ShortcutActivator, VoidCallback>{ | ||
const SingleActivator(LogicalKeyboardKey.enter): () async { | ||
await login( | ||
context, | ||
ref, | ||
loadingListenable, | ||
username: userName.text, | ||
serverAddress: serverAddress, | ||
password: password.text, | ||
); | ||
} | ||
}, | ||
child: FocusScope( | ||
// needed for enter shortcut to work | ||
autofocus: true, | ||
child: Center( | ||
child: SingleChildScrollView( | ||
child: SizedBox( | ||
width: 400, | ||
child: Padding( | ||
padding: const EdgeInsets.all(20.0), | ||
child: AutofillGroup( | ||
child: Column( | ||
children: [ | ||
Text(AppLocalizations.of(context)!.appName, | ||
style: Theme.of(context).textTheme.displaySmall), | ||
Text( | ||
AppLocalizations.of(context)!.appSubtitle, | ||
), | ||
const SizedBox(height: 20), | ||
Padding( | ||
padding: const EdgeInsets.symmetric(vertical: 10.0), | ||
child: Text( | ||
serverAddress, | ||
textAlign: TextAlign.left, | ||
), | ||
), | ||
Padding( | ||
padding: const EdgeInsets.symmetric(vertical: 10.0), | ||
child: TextField( | ||
controller: userName, | ||
autofillHints: const [AutofillHints.username], | ||
decoration: InputDecoration( | ||
border: const OutlineInputBorder(), | ||
labelText: AppLocalizations.of(context)!.username, | ||
), | ||
), | ||
), | ||
Padding( | ||
padding: const EdgeInsets.symmetric(vertical: 10.0), | ||
child: TextField( | ||
obscureText: true, | ||
autofillHints: const [AutofillHints.password], | ||
textInputAction: TextInputAction.go, | ||
controller: password, | ||
decoration: InputDecoration( | ||
border: const OutlineInputBorder(), | ||
labelText: AppLocalizations.of(context)!.password, | ||
), | ||
), | ||
), | ||
Padding( | ||
padding: const EdgeInsets.symmetric(vertical: 10.0), | ||
child: SizedBox( | ||
height: 45, | ||
width: 100, | ||
child: ValueListenableBuilder( | ||
valueListenable: loadingListenable, | ||
builder: (context, isLoading, _) { | ||
return isLoading | ||
? const Center( | ||
child: CircularProgressIndicator()) | ||
: FilledButton( | ||
onPressed: () async => await login( | ||
context, | ||
ref, | ||
loadingListenable, | ||
username: userName.text, | ||
serverAddress: serverAddress, | ||
password: password.text, | ||
), | ||
child: Text( | ||
AppLocalizations.of(context)! | ||
.login, | ||
), | ||
); | ||
}), | ||
)), | ||
kIsWeb | ||
? Text(AppLocalizations.of(context)!.webDemoNote) | ||
: const SizedBox(), | ||
], | ||
), | ||
), | ||
), | ||
), | ||
), | ||
), | ||
), | ||
), | ||
); | ||
} | ||
|
||
Future<void> login( | ||
BuildContext context, | ||
WidgetRef ref, | ||
ValueNotifier<bool> loadingListenable, { | ||
required String serverAddress, | ||
required String username, | ||
required String password, | ||
}) async { | ||
loadingListenable.value = true; | ||
try { | ||
final missingFields = formatMissingFields( | ||
context, | ||
username, | ||
serverAddress, | ||
); | ||
if (missingFields.isNotEmpty) { | ||
loadingListenable.value = false; | ||
await showInfoDialog( | ||
context, | ||
Text( | ||
AppLocalizations.of(context)!.emptyFields, | ||
), | ||
content: Text(missingFields), | ||
); | ||
|
||
return; | ||
} | ||
|
||
User user = User( | ||
name: username, | ||
password: password, | ||
serverAdress: serverAddress, | ||
); | ||
await ref.read(authProvider).login(user); | ||
loadingListenable.value = false; | ||
if (context.mounted) { | ||
context.go(ScreenPaths.home); | ||
} | ||
} on DioException catch (e) { | ||
if (!context.mounted) return; | ||
loadingListenable.value = false; | ||
await showHttpErrorDialog(context, e); | ||
return; | ||
} catch (e) { | ||
if (!context.mounted) return; | ||
loadingListenable.value = false; | ||
await showErrorDialog(context, e); | ||
return; | ||
} | ||
loadingListenable.value = false; | ||
} | ||
} | ||
|
||
String formatMissingFields( | ||
BuildContext context, | ||
String username, | ||
String serverAddress, | ||
) { | ||
var missingFields = ''; | ||
if (username.isEmpty) { | ||
missingFields += '${AppLocalizations.of(context)!.emptyUsername}\n\n'; | ||
} | ||
if (serverAddress.isEmpty) { | ||
missingFields += '${AppLocalizations.of(context)!.emptyAddress}\n\n'; | ||
} | ||
|
||
return missingFields; | ||
} |
Oops, something went wrong.