diff --git a/client/lib/ui/app/account_selector_web.dart b/client/lib/ui/app/account_selector_web.dart index 820ba5a4..46db6f93 100644 --- a/client/lib/ui/app/account_selector_web.dart +++ b/client/lib/ui/app/account_selector_web.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:js_interop'; import 'dart:math'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:offtheline/offtheline.dart'; import 'package:phylum/libphylum/phylum_account.dart'; @@ -79,7 +80,7 @@ class _AccountSelectorState extends State { context.select, PhylumAccount?>((state) => state.selectedAccount); if (account == null) { - return const LoginPage(); + return kDebugMode ? const LoginPage() : LoginPage(fixedInstanceUrl: Uri()); } return AppLayout.create(account); } diff --git a/client/lib/ui/login/instance_url_fragment.dart b/client/lib/ui/login/instance_url_fragment.dart new file mode 100644 index 00000000..0a558ac0 --- /dev/null +++ b/client/lib/ui/login/instance_url_fragment.dart @@ -0,0 +1,33 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +class InstanceUrlFragment extends StatefulWidget { + final Function(Uri) onSubmitted; + + const InstanceUrlFragment({super.key, required this.onSubmitted}); + + @override + State createState() => _InstanceUrlFragmentState(); +} + +class _InstanceUrlFragmentState extends State { + Uri? _url; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + TextField( + decoration: InputDecoration( + label: const Text('Instance URL'), + hintText: 'http://localhost:2448/', + ), + keyboardType: TextInputType.url, + onChanged: (value) => setState(() => _url = kIsWeb && value == '' ? Uri() : Uri.tryParse(value)), + onSubmitted: _url == null ? null : (value) => widget.onSubmitted(_url!), + ), + ElevatedButton(onPressed: _url == null ? null : () => widget.onSubmitted(_url!), child: Text('Next')) + ], + ); + } +} diff --git a/client/lib/ui/login/login_page.dart b/client/lib/ui/login/login_page.dart index 9dd8aa98..7e971144 100644 --- a/client/lib/ui/login/login_page.dart +++ b/client/lib/ui/login/login_page.dart @@ -1,25 +1,27 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:phylum/ui/common/logo_row.dart'; +import 'package:phylum/ui/login/instance_url_fragment.dart'; +import 'package:phylum/ui/login/password_login_fragment.dart'; import 'login_request.dart'; class LoginPage extends StatefulWidget { - const LoginPage({super.key}); + final Uri? fixedInstanceUrl; + + const LoginPage({super.key, this.fixedInstanceUrl}); @override State createState() => _LoginPageState(); } class _LoginPageState extends State { - String _email = ''; - String _password = ''; - Uri? serverUrl = kIsWeb ? Uri() : null; - String? urlError; - String? emailError; + Uri? _instanceUrl; - bool get serverUrlValid => - (kIsWeb && serverUrl == Uri()) || (serverUrl?.hasScheme == true && serverUrl?.hasAuthority == true); + @override + void initState() { + super.initState(); + _instanceUrl = widget.fixedInstanceUrl; + } @override Widget build(BuildContext context) { @@ -38,60 +40,7 @@ class _LoginPageState extends State { padding: EdgeInsets.only(bottom: 12.0), child: LogoRow(), ), - if (kDebugMode || !kIsWeb) - TextField( - decoration: InputDecoration( - label: const Text('Server URL'), - hintText: 'http://localhost:2448/', - errorText: urlError, - ), - keyboardType: TextInputType.url, - onChanged: (value) { - setState(() { - serverUrl = kIsWeb && value == '' ? Uri() : Uri.tryParse(value); - urlError = serverUrl == null ? 'Invalid URL' : null; - }); - }, - ), - TextField( - decoration: const InputDecoration( - label: Text('Email'), - ), - keyboardType: TextInputType.emailAddress, - onChanged: (value) { - setState(() { - _email = value.trim(); - }); - }, - ), - TextField( - decoration: const InputDecoration( - label: Text('Password'), - ), - obscureText: true, - keyboardType: TextInputType.visiblePassword, - onChanged: (value) { - setState(() { - _password = value.trim(); - }); - }, - onSubmitted: serverUrlValid - ? (value) { - performLogin(context, serverUrl ?? Uri(), _email, _password); - } - : null, - ), - Padding( - padding: const EdgeInsets.only(top: 16.0), - child: ElevatedButton( - onPressed: serverUrlValid && _email.isNotEmpty - ? () async { - performLogin(context, serverUrl ?? Uri(), _email, _password); - } - : null, - child: const Text('Login'), - ), - ), + _buildBody(context), ], ), ), @@ -100,4 +49,12 @@ class _LoginPageState extends State { ), ); } + + Widget _buildBody(BuildContext context) { + final instanceUrl = _instanceUrl; + if (instanceUrl == null) { + return InstanceUrlFragment(onSubmitted: (url) => setState(() => _instanceUrl = url)); + } + return PasswordLoginFragment(onSubmitted: (email, password) => performLogin(context, instanceUrl, email, password)); + } } diff --git a/client/lib/ui/login/password_login_fragment.dart b/client/lib/ui/login/password_login_fragment.dart new file mode 100644 index 00000000..bcfbed72 --- /dev/null +++ b/client/lib/ui/login/password_login_fragment.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; + +class PasswordLoginFragment extends StatefulWidget { + final Function(String, String) onSubmitted; + + const PasswordLoginFragment({super.key, required this.onSubmitted}); + + @override + State createState() => _PasswordLoginFragmentState(); +} + +class _PasswordLoginFragmentState extends State { + String _email = ''; + String _password = ''; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + TextField( + decoration: const InputDecoration( + label: Text('Email'), + ), + keyboardType: TextInputType.emailAddress, + onChanged: (value) => setState(() => _email = value.trim()), + ), + TextField( + decoration: const InputDecoration( + label: Text('Password'), + ), + obscureText: true, + keyboardType: TextInputType.visiblePassword, + onChanged: (value) => setState(() => _password = value), + onSubmitted: _email.isNotEmpty ? (value) => widget.onSubmitted(_email, _password) : null, + ), + TextButton(onPressed: () {}, child: Text('Forgot Password')), + ElevatedButton( + onPressed: _email.isEmpty || _password.isEmpty ? null : () => widget.onSubmitted(_email, _password), + child: Text('Login'), + ) + ], + ); + } +}