From 6b3dc6eb33d027f44f1e297e8e16966d80d1624d Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Sun, 12 Apr 2020 10:35:45 +0200 Subject: [PATCH] New set homeserver ux --- CHANGELOG.md | 6 +- lib/components/matrix.dart | 1 + lib/i18n/i18n.dart | 23 ++++++- lib/main.dart | 4 +- lib/views/homeserver_picker.dart | 110 +++++++++++++++++++++++++++++++ lib/views/login.dart | 83 ++++++++--------------- lib/views/settings.dart | 4 +- lib/views/sign_up.dart | 93 ++++++++++---------------- lib/views/sign_up_password.dart | 50 +++++++------- 9 files changed, 223 insertions(+), 151 deletions(-) create mode 100644 lib/views/homeserver_picker.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index c65360e..1787794 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Version 0.12.2 - 2020-04-12 -### Fixes: +### Changes: +- New set homeserver UX +### Fixed +- Fix toasts when switching views - Fix image flickering - Fix login without google services - Fix toasts @@ -65,7 +68,6 @@ - Replies on replies fixed # Version 0.7.1 - 2020-02-10 -### Fixed - Replies with correct sender id # Version 0.7.0 - 2020-02-10 diff --git a/lib/components/matrix.dart b/lib/components/matrix.dart index bf346ed..530dec5 100644 --- a/lib/components/matrix.dart +++ b/lib/components/matrix.dart @@ -24,6 +24,7 @@ import 'avatar.dart'; class Matrix extends StatefulWidget { static const String callNamespace = 'chat.fluffy.jitsi_call'; + static const String defaultHomeserver = 'tchncs.de'; final Widget child; diff --git a/lib/i18n/i18n.dart b/lib/i18n/i18n.dart index b15dbfb..99b02de 100644 --- a/lib/i18n/i18n.dart +++ b/lib/i18n/i18n.dart @@ -94,6 +94,12 @@ class I18n { args: [username, targetName], ); + String byDefaultYouWillBeConnectedTo(String homeserver) => Intl.message( + 'By default you will be connected to $homeserver', + name: 'byDefaultYouWillBeConnectedTo', + args: [homeserver], + ); + String get cancel => Intl.message("Cancel"); String changedTheChatAvatar(String username) => Intl.message( @@ -128,6 +134,8 @@ class I18n { args: [username, displayname], ); + String get changeTheHomeserver => Intl.message('Change the homeserver'); + String changedTheGuestAccessRules(String username) => Intl.message( "$username changed the guest access rules", name: "changedTheGuestAccessRules", @@ -199,12 +207,16 @@ class I18n { String get chatDetails => Intl.message('Chat details'); + String get chooseAStrongPassword => Intl.message("Choose a strong password"); + String get chooseAUsername => Intl.message("Choose a username"); String get close => Intl.message("Close"); String get confirm => Intl.message("Confirm"); + String get connect => Intl.message('Connect'); + String get connectionAttemptFailed => Intl.message("Connection attempt failed"); @@ -296,6 +308,8 @@ class I18n { String get enterAUsername => Intl.message("Enter a username"); + String get enterYourHomeserver => Intl.message('Enter your homeserver'); + String get fileName => Intl.message("File name"); String get fileSize => Intl.message("File size"); @@ -419,6 +433,12 @@ class I18n { String get login => Intl.message("Login"); + String logInTo(String homeserver) => Intl.message( + 'Log in to $homeserver', + name: 'logInTo', + args: [homeserver], + ); + String get makeAModerator => Intl.message("Make a moderator"); String get makeAnAdmin => Intl.message("Make an admin"); @@ -541,9 +561,6 @@ class I18n { String get searchForAChat => Intl.message("Search for a chat"); - String get secureYourAccountWithAPassword => - Intl.message("Secure your account with a password"); - String seenByUser(String username) => Intl.message( "Seen by $username", name: "seenByUser", diff --git a/lib/main.dart b/lib/main.dart index db9bf5f..09424e3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:famedlysdk/famedlysdk.dart'; +import 'package:fluffychat/views/homeserver_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -8,7 +9,6 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'i18n/i18n.dart'; -import 'views/sign_up.dart'; import 'components/theme_switcher.dart'; import 'components/matrix.dart'; import 'views/chat_list.dart'; @@ -63,7 +63,7 @@ class App extends StatelessWidget { if (Matrix.of(context).client.isLogged()) { return ChatListView(); } - return SignUp(); + return HomeserverPicker(); }, ), ), diff --git a/lib/views/homeserver_picker.dart b/lib/views/homeserver_picker.dart new file mode 100644 index 0000000..dc63435 --- /dev/null +++ b/lib/views/homeserver_picker.dart @@ -0,0 +1,110 @@ +import 'dart:math'; + +import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; +import 'package:fluffychat/components/matrix.dart'; +import 'package:fluffychat/i18n/i18n.dart'; +import 'package:fluffychat/utils/app_route.dart'; +import 'package:fluffychat/views/sign_up.dart'; +import 'package:flutter/material.dart'; + +class HomeserverPicker extends StatelessWidget { + _setHomeserverAction(BuildContext context) async { + final homeserver = await SimpleDialogs(context).enterText( + titleText: I18n.of(context).enterYourHomeserver, + hintText: Matrix.defaultHomeserver, + prefixText: 'https://'); + if (homeserver?.isEmpty ?? true) return; + _checkHomeserverAction(homeserver, context); + } + + _checkHomeserverAction(String homeserver, BuildContext context) async { + if (!homeserver.startsWith('https://')) { + homeserver = 'https://$homeserver'; + } + final success = await Matrix.of(context).tryRequestWithLoadingDialog( + Matrix.of(context).client.checkServer(homeserver)); + if (success != false) { + await Navigator.of(context).push(AppRoute(SignUp())); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: + max((MediaQuery.of(context).size.width - 600) / 2, 0)), + child: Column( + children: [ + Hero( + tag: 'loginBanner', + child: Image.asset("assets/fluffychat-banner.png"), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'Welcome to the cutest instant messaging solution for all platforms.', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + ), + ), + ), + Spacer(), + Hero( + tag: 'loginButton', + child: Container( + width: double.infinity, + height: 50, + padding: EdgeInsets.symmetric(horizontal: 12), + child: RaisedButton( + elevation: 7, + color: Theme.of(context).primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + child: Text( + I18n.of(context).connect, + style: TextStyle(color: Colors.white, fontSize: 16), + ), + onPressed: () => _checkHomeserverAction( + Matrix.defaultHomeserver, context), + ), + ), + ), + Padding( + padding: + const EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0), + child: Opacity( + opacity: 0.75, + child: Text( + I18n.of(context).byDefaultYouWillBeConnectedTo( + Matrix.defaultHomeserver), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + ), + ), + ), + ), + FlatButton( + child: Text( + I18n.of(context).changeTheHomeserver, + style: TextStyle( + decoration: TextDecoration.underline, + color: Colors.blue, + fontSize: 16, + ), + ), + onPressed: () => _setHomeserverAction(context), + ), + SizedBox(height: 16), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/login.dart b/lib/views/login.dart index dd1ee95..ba71fcb 100644 --- a/lib/views/login.dart +++ b/lib/views/login.dart @@ -17,11 +17,8 @@ class Login extends StatefulWidget { class _LoginState extends State { final TextEditingController usernameController = TextEditingController(); final TextEditingController passwordController = TextEditingController(); - final TextEditingController serverController = - TextEditingController(text: "tchncs.de"); String usernameError; String passwordError; - String serverError; bool loading = false; bool showPassword = false; @@ -37,30 +34,12 @@ class _LoginState extends State { } else { setState(() => passwordError = null); } - serverError = null; if (usernameController.text.isEmpty || passwordController.text.isEmpty) { return; } - String homeserver = serverController.text; - if (homeserver.isEmpty) homeserver = "tchncs.de"; - if (!homeserver.startsWith("https://")) { - homeserver = "https://" + homeserver; - } - - try { - setState(() => loading = true); - if (!await matrix.client.checkServer(homeserver)) { - setState( - () => serverError = I18n.of(context).homeserverIsNotCompatible); - - return setState(() => loading = false); - } - } catch (exception) { - setState(() => serverError = I18n.of(context).connectionAttemptFailed); - return setState(() => loading = false); - } + setState(() => loading = true); try { await matrix.client.login( usernameController.text, passwordController.text, @@ -92,16 +71,12 @@ class _LoginState extends State { return Scaffold( appBar: AppBar( leading: loading ? Container() : null, - title: TextField( - autocorrect: false, - controller: serverController, - decoration: InputDecoration( - icon: Icon(Icons.domain), - hintText: "matrix-client.matrix.org", - errorText: serverError, - errorMaxLines: 1, - prefixText: "https://", - labelText: serverError == null ? "Homeserver" : serverError), + elevation: 0, + title: Text( + I18n.of(context).logInTo(Matrix.of(context) + .client + .homeserver + .replaceFirst('https://', '')), ), ), body: Builder(builder: (context) { @@ -110,16 +85,6 @@ class _LoginState extends State { horizontal: max((MediaQuery.of(context).size.width - 600) / 2, 0)), children: [ - Container( - height: 150, - color: Theme.of(context).secondaryHeaderColor, - child: Center( - child: Icon( - Icons.vpn_key, - size: 60, - ), - ), - ), ListTile( leading: CircleAvatar( child: Icon(Icons.account_box, @@ -128,6 +93,7 @@ class _LoginState extends State { title: TextField( readOnly: loading, autocorrect: false, + autofocus: true, controller: usernameController, decoration: InputDecoration( hintText: @@ -163,22 +129,25 @@ class _LoginState extends State { ), ), SizedBox(height: 20), - Container( - height: 50, - padding: EdgeInsets.symmetric(horizontal: 12), - child: RaisedButton( - elevation: 7, - color: Theme.of(context).primaryColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), + Hero( + tag: 'loginButton', + child: Container( + height: 50, + padding: EdgeInsets.symmetric(horizontal: 12), + child: RaisedButton( + elevation: 7, + color: Theme.of(context).primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + child: loading + ? CircularProgressIndicator() + : Text( + I18n.of(context).login.toUpperCase(), + style: TextStyle(color: Colors.white, fontSize: 16), + ), + onPressed: () => loading ? null : login(context), ), - child: loading - ? CircularProgressIndicator() - : Text( - I18n.of(context).login.toUpperCase(), - style: TextStyle(color: Colors.white, fontSize: 16), - ), - onPressed: () => loading ? null : login(context), ), ), ], diff --git a/lib/views/settings.dart b/lib/views/settings.dart index 31d3aa7..7be7542 100644 --- a/lib/views/settings.dart +++ b/lib/views/settings.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:famedlysdk/famedlysdk.dart'; import 'package:fluffychat/components/settings_themes.dart'; +import 'package:fluffychat/views/homeserver_picker.dart'; import 'package:fluffychat/views/settings_devices.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -11,7 +12,6 @@ import 'package:url_launcher/url_launcher.dart'; import 'app_info.dart'; import 'chat_list.dart'; import '../components/adaptive_page_layout.dart'; -import 'sign_up.dart'; import '../components/dialogs/simple_dialogs.dart'; import '../components/content_banner.dart'; import '../components/matrix.dart'; @@ -46,7 +46,7 @@ class _SettingsState extends State { await matrix.tryRequestWithLoadingDialog(matrix.client.logout()); matrix.clean(); await Navigator.of(context).pushAndRemoveUntil( - AppRoute.defaultRoute(context, SignUp()), (r) => false); + AppRoute.defaultRoute(context, HomeserverPicker()), (r) => false); } void setJitsiInstanceAction(BuildContext context) async { diff --git a/lib/views/sign_up.dart b/lib/views/sign_up.dart index 52fb382..6df01dc 100644 --- a/lib/views/sign_up.dart +++ b/lib/views/sign_up.dart @@ -18,10 +18,7 @@ class SignUp extends StatefulWidget { class _SignUpState extends State { final TextEditingController usernameController = TextEditingController(); - final TextEditingController serverController = - TextEditingController(text: "tchncs.de"); String usernameError; - String serverError; bool loading = false; File avatar; @@ -42,34 +39,15 @@ class _SignUpState extends State { } else { setState(() => usernameError = null); } - serverError = null; if (usernameController.text.isEmpty) { return; } + setState(() => loading = true); final String preferredUsername = usernameController.text.toLowerCase().replaceAll(" ", "-"); - String homeserver = serverController.text; - if (homeserver.isEmpty) homeserver = "tchncs.de"; - if (!homeserver.startsWith("https://")) { - homeserver = "https://" + homeserver; - } - - try { - setState(() => loading = true); - if (!await matrix.client.checkServer(homeserver)) { - setState( - () => serverError = I18n.of(context).homeserverIsNotCompatible); - - return setState(() => loading = false); - } - } catch (exception) { - setState(() => serverError = I18n.of(context).connectionAttemptFailed); - return setState(() => loading = false); - } - try { await matrix.client.usernameAvailable(preferredUsername); } on MatrixException catch (exception) { @@ -81,8 +59,7 @@ class _SignUpState extends State { } setState(() => loading = false); await Navigator.of(context).push( - AppRoute.defaultRoute( - context, + AppRoute( SignUpPassword(preferredUsername, avatar: avatar, displayname: usernameController.text), ), @@ -93,17 +70,10 @@ class _SignUpState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: TextField( - autocorrect: false, - controller: serverController, - decoration: InputDecoration( - icon: Icon(Icons.domain), - hintText: "matrix-client.matrix.org", - errorText: serverError, - errorMaxLines: 1, - prefixText: "https://", - labelText: serverError == null ? "Homeserver" : serverError, - ), + elevation: 0, + leading: loading ? Container() : null, + title: Text( + Matrix.of(context).client.homeserver.replaceFirst('https://', ''), ), ), body: ListView( @@ -111,12 +81,17 @@ class _SignUpState extends State { horizontal: max((MediaQuery.of(context).size.width - 600) / 2, 0)), children: [ - Image.asset("assets/fluffychat-banner.png"), + Hero( + tag: 'loginBanner', + child: Image.asset("assets/fluffychat-banner.png"), + ), ListTile( leading: CircleAvatar( backgroundImage: avatar == null ? null : FileImage(avatar), backgroundColor: avatar == null - ? Theme.of(context).brightness == Brightness.dark ? Color(0xff121212) : Colors.white + ? Theme.of(context).brightness == Brightness.dark + ? Color(0xff121212) + : Colors.white : Theme.of(context).secondaryHeaderColor, child: avatar == null ? Icon(Icons.camera_alt, @@ -138,7 +113,9 @@ class _SignUpState extends State { ), ListTile( leading: CircleAvatar( - backgroundColor: Theme.of(context).brightness == Brightness.dark ? Color(0xff121212) : Colors.white, + backgroundColor: Theme.of(context).brightness == Brightness.dark + ? Color(0xff121212) + : Colors.white, child: Icon( Icons.account_circle, color: Theme.of(context).primaryColor, @@ -155,22 +132,25 @@ class _SignUpState extends State { ), ), SizedBox(height: 20), - Container( - height: 50, - padding: EdgeInsets.symmetric(horizontal: 12), - child: RaisedButton( - elevation: 7, - color: Theme.of(context).primaryColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), + Hero( + tag: 'loginButton', + child: Container( + height: 50, + padding: EdgeInsets.symmetric(horizontal: 12), + child: RaisedButton( + elevation: 7, + color: Theme.of(context).primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + child: loading + ? CircularProgressIndicator() + : Text( + I18n.of(context).signUp.toUpperCase(), + style: TextStyle(color: Colors.white, fontSize: 16), + ), + onPressed: () => loading ? null : signUpAction(context), ), - child: loading - ? CircularProgressIndicator() - : Text( - I18n.of(context).signUp.toUpperCase(), - style: TextStyle(color: Colors.white, fontSize: 16), - ), - onPressed: () => signUpAction(context), ), ), Center( @@ -184,10 +164,7 @@ class _SignUpState extends State { ), ), onPressed: () => Navigator.of(context).push( - AppRoute.defaultRoute( - context, - Login(), - ), + AppRoute(Login()), ), ), ), diff --git a/lib/views/sign_up_password.dart b/lib/views/sign_up_password.dart index 1c12c23..b66c225 100644 --- a/lib/views/sign_up_password.dart +++ b/lib/views/sign_up_password.dart @@ -119,23 +119,16 @@ class _SignUpPasswordState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(I18n.of(context).secureYourAccountWithAPassword), + elevation: 0, + leading: loading ? Container() : null, + title: Text( + I18n.of(context).chooseAStrongPassword, + ), ), body: ListView( padding: EdgeInsets.symmetric( horizontal: max((MediaQuery.of(context).size.width - 600) / 2, 0)), children: [ - Container( - height: 150, - color: Theme.of(context).secondaryHeaderColor, - child: Center( - child: Icon( - Icons.vpn_key, - color: Theme.of(context).primaryColor, - size: 40, - ), - ), - ), ListTile( leading: CircleAvatar( backgroundColor: Colors.white, @@ -160,22 +153,25 @@ class _SignUpPasswordState extends State { ), ), SizedBox(height: 20), - Container( - height: 50, - padding: EdgeInsets.symmetric(horizontal: 12), - child: RaisedButton( - elevation: 7, - color: Theme.of(context).primaryColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), + Hero( + tag: 'loginButton', + child: Container( + height: 50, + padding: EdgeInsets.symmetric(horizontal: 12), + child: RaisedButton( + elevation: 7, + color: Theme.of(context).primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + child: loading + ? CircularProgressIndicator() + : Text( + I18n.of(context).createAccountNow.toUpperCase(), + style: TextStyle(color: Colors.white, fontSize: 16), + ), + onPressed: () => loading ? null : _signUpAction(context), ), - child: loading - ? CircularProgressIndicator() - : Text( - I18n.of(context).createAccountNow.toUpperCase(), - style: TextStyle(color: Colors.white, fontSize: 16), - ), - onPressed: () => loading ? null : _signUpAction(context), ), ), ],