diff --git a/client/lib/ui/app/router.dart b/client/lib/ui/app/router.dart index 3918f802..92f0949e 100644 --- a/client/lib/ui/app/router.dart +++ b/client/lib/ui/app/router.dart @@ -120,8 +120,8 @@ class PhylumRouteInformationParser extends RouteInformationParser { } else if (segments.length == 2) { if (segments[0] == 'login' && segments[1] == 'token') { return SynchronousFuture(TokenLoginRoute( - instanceUrl: uri.queryParameters['instance'] ?? '', - loginToken: uri.queryParameters['token'] ?? '', + instanceUrl: uri.queryParameters['instance'], + loginToken: uri.queryParameters['token'], )); } if (segments[0] == 'folder') { diff --git a/client/lib/ui/app/routes.dart b/client/lib/ui/app/routes.dart index a18422ce..5637afc9 100644 --- a/client/lib/ui/app/routes.dart +++ b/client/lib/ui/app/routes.dart @@ -29,7 +29,7 @@ class TokenLoginRoute extends PhylumRoute { final String? instanceUrl; final String? loginToken; - TokenLoginRoute({required this.instanceUrl, required this.loginToken}); + TokenLoginRoute({this.instanceUrl, this.loginToken}); @override Uri get uri => Uri(path: '/login/token'); diff --git a/client/lib/ui/login/login_fragment.dart b/client/lib/ui/login/login_fragment.dart index 0e48b6bd..5c1ca787 100644 --- a/client/lib/ui/login/login_fragment.dart +++ b/client/lib/ui/login/login_fragment.dart @@ -200,12 +200,8 @@ class _LoginFragmentState extends State { if ((response ?? true) || !context.mounted) return; - final token = await showInputDialog(context, title: 'Login Token', barrierDismissable: true); - if (token == null || !context.mounted) return; - Navigator.of(context).pop(false); context.read().go(TokenLoginRoute( instanceUrl: widget.instanceConfig.url.toString(), - loginToken: token, )); } } diff --git a/client/lib/ui/login/token_login_page.dart b/client/lib/ui/login/token_login_page.dart index 5f77d1a0..cc28ef49 100644 --- a/client/lib/ui/login/token_login_page.dart +++ b/client/lib/ui/login/token_login_page.dart @@ -1,13 +1,16 @@ import 'dart:convert'; -import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:http/http.dart'; import 'package:offtheline/offtheline.dart'; import 'package:phylum/libphylum/phylum_account.dart'; import 'package:phylum/libphylum/responses/responses.dart'; import 'package:phylum/ui/app/router.dart'; import 'package:phylum/ui/app/routes.dart'; +import 'package:phylum/ui/login/request.dart'; +import 'package:phylum/util/dialogs.dart'; +import 'package:phylum/util/logging.dart'; import 'package:provider/provider.dart'; import 'package:uri/uri.dart'; import 'package:phylum/ui/app/dialog_scaffold.dart'; @@ -27,87 +30,111 @@ class TokenLoginPage extends StatefulWidget { } class _TokenLoginPageState extends State { - String? error; + late final instanceUrlController = TextEditingController(text: widget.instanceUrl); + late final loginTokenController = TextEditingController(text: widget.loginToken); @override void initState() { super.initState(); - performLogin(context); + if (widget.instanceUrl != null && widget.loginToken != null) { + final instanceUrl = Uri.tryParse(widget.instanceUrl!); + if (instanceUrl != null) { + WidgetsBinding.instance.addPostFrameCallback((d) { + _performLogin(context, instanceUrl, widget.loginToken!); + }); + } + } + } + + @override + void dispose() { + instanceUrlController.dispose(); + loginTokenController.dispose(); + super.dispose(); } @override Widget build(BuildContext context) { return PhylumDialogScaffold( - child: error == null - ? const Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - spacing: 12.0, - children: [ - CircularProgressIndicator(), - Text('Logging In'), - ], - ) - : Column( - mainAxisSize: MainAxisSize.min, - spacing: 6.0, - children: [ - Text('Error: $error'), - ElevatedButton( - onPressed: () => context.read().go(ExplorerRouteHome()), - child: Text('OK'), - ), - ], + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: 6.0, + children: [ + TextField( + decoration: InputDecoration( + label: Text('Instance URL'), + hintText: 'https://phylum.example.com', ), + controller: instanceUrlController, + enabled: widget.instanceUrl == null, + keyboardType: TextInputType.url, + ), + TextField( + decoration: InputDecoration( + label: Text('Login Token'), + hintText: 'AG...', + ), + controller: loginTokenController, + keyboardType: TextInputType.url, + textInputAction: TextInputAction.go, + onSubmitted: (value) { + final instanceUrl = Uri.tryParse(instanceUrlController.value.text); + if (instanceUrl != null) { + _performLogin(context, instanceUrl, value); + } + }, + ), + ElevatedButton( + onPressed: () { + final instanceUrl = Uri.tryParse(instanceUrlController.value.text); + if (instanceUrl != null) { + _performLogin(context, instanceUrl, loginTokenController.value.text); + } + }, + child: Text('Log In'), + ), + ], + ), ); } - void performLogin(BuildContext context) async { - if (widget.instanceUrl == null || widget.loginToken == null) { - Future.microtask(() => setState(() => error = 'Missing Parameters')); - } - final instanceUrl = Uri.parse(widget.instanceUrl!); + Future _performLogin(BuildContext context, Uri instanceUrl, String token) async { final builder = UriBuilder.fromUri(instanceUrl); builder.path = '${builder.path}/api/v1/auth/token/login'; final request = MultipartRequest('post', builder.build()); - request.fields['token'] = widget.loginToken!; + request.fields['token'] = token; - try { - final response = await Client().send(request); - final responseString = await response.stream.bytesToString(); - final responseMap = (jsonDecode(responseString) as Map).cast(); - - if (response.statusCode >= 200 && response.statusCode < 300) { + final responseString = await sendRequest(context, 'Logging In', request); + if (responseString == null) return; + if (context.mounted) { + final routerDelegate = context.read(); + final accountManager = context.read>(); + final navigator = Navigator.of(context); + showProgressDialog(context, barrierDismissible: false, message: 'Logging In'); + try { + final response = + BootstrapLoginResponse.fromResponse((jsonDecode(responseString) as Map).cast()); + final account = await PhylumAccount.create( + serverUrl: instanceUrl, + apiToken: response.apiToken!, + user: response.user, + ); + await response.process(account); + await accountManager.addAccount(account); + navigator.pop(); + routerDelegate.go(const ExplorerRouteHome()); + } catch (e, stack) { + navigator.pop(); if (context.mounted) { - final routerDelegate = context.read(); - final accountManager = context.read>(); - - final response = BootstrapLoginResponse.fromResponse(responseMap); - final account = await PhylumAccount.create( - serverUrl: instanceUrl, - apiToken: response.apiToken!, - user: response.user, + showAlertDialog( + context, + barrierDismissible: false, + title: 'Error', + message: e.toString(), ); - await response.process(account); - await accountManager.addAccount(account); - routerDelegate.go(ExplorerRouteHome()); } - } else { - if (context.mounted) { - setState(() => error = responseMap['msg']); - } - return null; + logger.w('Login Error', error: e, stackTrace: stack); } - } on SocketException { - if (context.mounted) { - setState(() => error = 'unable to reach server'); - } - return null; - } catch (e) { - if (context.mounted) { - setState(() => error = e.toString()); - } - return null; } } }