From 9361b3deeea102a1ba536d970d04c9dc81bf82ac Mon Sep 17 00:00:00 2001 From: Krille Fear Date: Sun, 16 Oct 2022 12:37:38 +0200 Subject: [PATCH] design: Improve login design --- lib/pages/connect/connect_page_view.dart | 82 +++--- lib/pages/connect/sso_button.dart | 1 - .../homeserver_picker/homeserver_picker.dart | 5 +- .../homeserver_picker_view.dart | 267 +++++++++--------- lib/pages/login/login_view.dart | 63 +++-- lib/pages/sign_up/signup_view.dart | 28 +- lib/widgets/layouts/login_scaffold.dart | 69 +++-- 7 files changed, 275 insertions(+), 240 deletions(-) diff --git a/lib/pages/connect/connect_page_view.dart b/lib/pages/connect/connect_page_view.dart index e6c6a8fa..c4396503 100644 --- a/lib/pages/connect/connect_page_view.dart +++ b/lib/pages/connect/connect_page_view.dart @@ -19,13 +19,11 @@ class ConnectPageView extends StatelessWidget { final identityProviders = controller.identityProviders; return LoginScaffold( appBar: AppBar( - leading: - controller.loading ? null : const BackButton(color: Colors.white), + leading: controller.loading ? null : const BackButton(), automaticallyImplyLeading: !controller.loading, centerTitle: true, title: Text( Matrix.of(context).getLoginClient().homeserver?.host ?? '', - style: const TextStyle(color: Colors.white), ), ), body: ListView( @@ -38,15 +36,19 @@ class ConnectPageView extends StatelessWidget { children: [ Material( borderRadius: BorderRadius.circular(64), - elevation: 10, + elevation: Theme.of(context) + .appBarTheme + .scrolledUnderElevation ?? + 4, color: Colors.transparent, + shadowColor: Theme.of(context).appBarTheme.shadowColor, clipBehavior: Clip.hardEdge, child: CircleAvatar( radius: 64, - backgroundColor: Colors.white.withAlpha(200), + backgroundColor: Colors.white, child: avatar == null ? const Icon( - Icons.person_outlined, + Icons.person, color: Colors.black, size: 64, ) @@ -93,10 +95,7 @@ class ConnectPageView extends StatelessWidget { hintText: L10n.of(context)!.chooseAUsername, errorText: controller.signupError, errorStyle: const TextStyle(color: Colors.orange), - fillColor: Theme.of(context) - .colorScheme - .background - .withOpacity(0.75), + fillColor: Theme.of(context).colorScheme.background, ), ), ), @@ -105,6 +104,10 @@ class ConnectPageView extends StatelessWidget { child: Hero( tag: 'loginButton', child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + foregroundColor: Theme.of(context).colorScheme.onPrimary, + ), onPressed: controller.loading ? () {} : controller.signUp, child: controller.loading ? const LinearProgressIndicator() @@ -112,45 +115,52 @@ class ConnectPageView extends StatelessWidget { ), ), ), - Row( - children: [ - const Expanded( + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Row( + children: [ + Expanded( child: Divider( - color: Colors.white, - thickness: 1, - )), - Padding( - padding: const EdgeInsets.all(12.0), - child: Text( - L10n.of(context)!.or, - style: const TextStyle( - color: Colors.white, - fontSize: 18, + thickness: 1, + color: Theme.of(context).textTheme.subtitle1?.color, ), ), - ), - const Expanded( + Padding( + padding: const EdgeInsets.all(12.0), + child: Text( + L10n.of(context)!.or, + style: const TextStyle(fontSize: 18), + ), + ), + Expanded( child: Divider( - color: Colors.white, - thickness: 1, - )), - ], + thickness: 1, + color: Theme.of(context).textTheme.subtitle1?.color, + ), + ), + ], + ), ), ], if (controller.supportsSso) identityProviders == null ? const SizedBox( height: 74, - child: Center( - child: CircularProgressIndicator.adaptive( - backgroundColor: Colors.white, - )), + child: Center(child: CircularProgressIndicator.adaptive()), ) : Center( child: identityProviders.length == 1 ? Padding( padding: const EdgeInsets.all(12.0), child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context) + .colorScheme + .primaryContainer, + foregroundColor: Theme.of(context) + .colorScheme + .onPrimaryContainer, + ), onPressed: () => controller .ssoLoginAction(identityProviders.single.id!), child: Text(identityProviders.single.name ?? @@ -175,6 +185,12 @@ class ConnectPageView extends StatelessWidget { child: Hero( tag: 'signinButton', child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: + Theme.of(context).colorScheme.primaryContainer, + foregroundColor: + Theme.of(context).colorScheme.onPrimaryContainer, + ), onPressed: controller.loading ? () {} : controller.login, child: Text(L10n.of(context)!.login), ), diff --git a/lib/pages/connect/sso_button.dart b/lib/pages/connect/sso_button.dart index dc15e493..b0ec604b 100644 --- a/lib/pages/connect/sso_button.dart +++ b/lib/pages/connect/sso_button.dart @@ -52,7 +52,6 @@ class SsoButton extends StatelessWidget { style: const TextStyle( fontSize: 12, fontWeight: FontWeight.bold, - color: Colors.white, ), ), ], diff --git a/lib/pages/homeserver_picker/homeserver_picker.dart b/lib/pages/homeserver_picker/homeserver_picker.dart index bc1918cb..a9b7cd56 100644 --- a/lib/pages/homeserver_picker/homeserver_picker.dart +++ b/lib/pages/homeserver_picker/homeserver_picker.dart @@ -180,12 +180,13 @@ class HomeserverPickerController extends State { } Future restoreBackup() async { + final file = + await FilePickerCross.importFromStorage(fileExtension: '.fluffybackup'); + if (file.fileName == null) return; await showFutureLoadingDialog( context: context, future: () async { try { - final file = await FilePickerCross.importFromStorage( - fileExtension: '.fluffybackup'); final client = Matrix.of(context).getLoginClient(); await client.importDump(file.toString()); Matrix.of(context).initMatrix(); diff --git a/lib/pages/homeserver_picker/homeserver_picker_view.dart b/lib/pages/homeserver_picker/homeserver_picker_view.dart index a5cebfe3..7cabe029 100644 --- a/lib/pages/homeserver_picker/homeserver_picker_view.dart +++ b/lib/pages/homeserver_picker/homeserver_picker_view.dart @@ -4,7 +4,6 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/layouts/login_scaffold.dart'; import 'homeserver_picker.dart'; @@ -17,147 +16,157 @@ class HomeserverPickerView extends StatelessWidget { Widget build(BuildContext context) { final benchmarkResults = controller.benchmarkResults; return LoginScaffold( - appBar: AppBar( - actions: [ - IconButton( - onPressed: controller.restoreBackup, - tooltip: L10n.of(context)!.hydrate, - color: Colors.white, - icon: const Icon(Icons.restore_outlined), - ), - IconButton( - tooltip: L10n.of(context)!.privacy, - onPressed: () => launch(AppConfig.privacyUrl), - color: Colors.white, - icon: const Icon(Icons.shield_outlined), - ), - IconButton( - tooltip: L10n.of(context)!.about, - onPressed: () => PlatformInfos.showDialog(context), - color: Colors.white, - icon: const Icon(Icons.info_outlined), - ), - ], - ), - body: SafeArea( - child: Column( - children: [ - // display a prominent banner to import session for TOR browser - // users. This feature is just some UX sugar as TOR users are - // usually forced to logout as TOR browser is non-persistent - AnimatedContainer( - height: controller.isTorBrowser ? 64 : 0, - duration: const Duration(milliseconds: 300), + body: Column( + children: [ + // display a prominent banner to import session for TOR browser + // users. This feature is just some UX sugar as TOR users are + // usually forced to logout as TOR browser is non-persistent + AnimatedContainer( + height: controller.isTorBrowser ? 64 : 0, + duration: const Duration(milliseconds: 300), + clipBehavior: Clip.hardEdge, + curve: Curves.bounceInOut, + decoration: const BoxDecoration(), + child: Material( clipBehavior: Clip.hardEdge, - curve: Curves.bounceInOut, - decoration: const BoxDecoration(), - child: Material( - clipBehavior: Clip.hardEdge, - borderRadius: - const BorderRadius.vertical(bottom: Radius.circular(8)), - color: Theme.of(context).colorScheme.surface, - child: ListTile( - leading: const Icon(Icons.vpn_key), - title: Text(L10n.of(context)!.hydrateTor), - subtitle: Text(L10n.of(context)!.hydrateTorLong), - trailing: const Icon(Icons.chevron_right_outlined), - onTap: controller.restoreBackup, - ), + borderRadius: + const BorderRadius.vertical(bottom: Radius.circular(8)), + color: Theme.of(context).colorScheme.surface, + child: ListTile( + leading: const Icon(Icons.vpn_key), + title: Text(L10n.of(context)!.hydrateTor), + subtitle: Text(L10n.of(context)!.hydrateTorLong), + trailing: const Icon(Icons.chevron_right_outlined), + onTap: controller.restoreBackup, ), ), - Expanded( - child: ListView( - children: [ - Container( - alignment: Alignment.center, - height: 200, - child: Image.asset('assets/info-logo.png'), + ), + Expanded( + child: ListView( + children: [ + Container( + alignment: Alignment.center, + height: 190, + child: Image.asset('assets/info-logo.png'), + ), + Padding( + padding: const EdgeInsets.all(12.0), + child: TextField( + focusNode: controller.homeserverFocusNode, + controller: controller.homeserverController, + onChanged: controller.onChanged, + decoration: InputDecoration( + prefixText: '${L10n.of(context)!.homeserver}: ', + hintText: L10n.of(context)!.enterYourHomeserver, + suffixIcon: const Icon(Icons.search), + errorText: controller.error, + fillColor: Theme.of(context).backgroundColor, + ), + readOnly: !AppConfig.allowOtherHomeservers, + onSubmitted: (_) => controller.checkHomeserverAction(), + autocorrect: false, ), + ), + if (controller.displayServerList) Padding( padding: const EdgeInsets.all(12.0), - child: TextField( - focusNode: controller.homeserverFocusNode, - controller: controller.homeserverController, - onChanged: controller.onChanged, - decoration: InputDecoration( - prefixText: '${L10n.of(context)!.homeserver}: ', - hintText: L10n.of(context)!.enterYourHomeserver, - suffixIcon: const Icon(Icons.search), - errorText: controller.error, - fillColor: Theme.of(context) - .colorScheme - .background - .withOpacity(0.75), + child: Material( + borderRadius: + BorderRadius.circular(AppConfig.borderRadius), + color: Theme.of(context).colorScheme.onInverseSurface, + clipBehavior: Clip.hardEdge, + child: benchmarkResults == null + ? const Center( + child: Padding( + padding: EdgeInsets.all(12.0), + child: CircularProgressIndicator.adaptive(), + )) + : Column( + children: controller.filteredHomeservers + .map( + (server) => ListTile( + trailing: IconButton( + icon: const Icon( + Icons.info_outlined, + color: Colors.black, + ), + onPressed: () => + controller.showServerInfo(server), + ), + onTap: () => controller.setServer( + server.homeserver.baseUrl.host), + title: Text( + server.homeserver.baseUrl.host, + style: const TextStyle( + color: Colors.black), + ), + subtitle: Text( + server.homeserver.description ?? '', + style: TextStyle( + color: Colors.grey.shade700), + ), + ), + ) + .toList(), + ), + ), + ) + else + Padding( + padding: const EdgeInsets.all(16.0), + child: Center( + child: Text( + AppConfig.applicationWelcomeMessage ?? + L10n.of(context)!.welcomeText, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 20), ), - readOnly: !AppConfig.allowOtherHomeservers, - onSubmitted: (_) => controller.checkHomeserverAction(), - autocorrect: false, ), ), - if (controller.displayServerList) - Padding( - padding: const EdgeInsets.all(12.0), - child: Material( - borderRadius: - BorderRadius.circular(AppConfig.borderRadius), - color: Colors.white.withAlpha(200), - clipBehavior: Clip.hardEdge, - child: benchmarkResults == null - ? const Center( - child: Padding( - padding: EdgeInsets.all(12.0), - child: CircularProgressIndicator.adaptive(), - )) - : Column( - children: controller.filteredHomeservers - .map( - (server) => ListTile( - trailing: IconButton( - icon: const Icon( - Icons.info_outlined, - color: Colors.black, - ), - onPressed: () => - controller.showServerInfo(server), - ), - onTap: () => controller.setServer( - server.homeserver.baseUrl.host), - title: Text( - server.homeserver.baseUrl.host, - style: const TextStyle( - color: Colors.black), - ), - subtitle: Text( - server.homeserver.description ?? '', - style: TextStyle( - color: Colors.grey.shade700), - ), - ), - ) - .toList(), - ), - ), + ], + ), + ), + SafeArea( + child: Container( + padding: const EdgeInsets.all(12), + width: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextButton( + style: TextButton.styleFrom( + foregroundColor: + Theme.of(context).colorScheme.onSurfaceVariant, ), + onPressed: controller.restoreBackup, + child: Text(L10n.of(context)!.hydrate), + ), + TextButton( + onPressed: () => launch(AppConfig.privacyUrl), + child: Text(L10n.of(context)!.privacy), + ), + Hero( + tag: 'loginButton', + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + foregroundColor: + Theme.of(context).colorScheme.onPrimary, + ), + onPressed: controller.isLoading + ? null + : controller.checkHomeserverAction, + child: controller.isLoading + ? const LinearProgressIndicator() + : Text(L10n.of(context)!.connect), + ), + ), ], ), ), - Container( - padding: const EdgeInsets.all(12), - width: double.infinity, - child: Hero( - tag: 'loginButton', - child: ElevatedButton( - onPressed: controller.isLoading - ? null - : controller.checkHomeserverAction, - child: controller.isLoading - ? const LinearProgressIndicator() - : Text(L10n.of(context)!.connect), - ), - ), - ), - ], - ), + ), + ], ), ); } diff --git a/lib/pages/login/login_view.dart b/lib/pages/login/login_view.dart index f78717a4..8f33cc65 100644 --- a/lib/pages/login/login_view.dart +++ b/lib/pages/login/login_view.dart @@ -15,8 +15,7 @@ class LoginView extends StatelessWidget { Widget build(BuildContext context) { return LoginScaffold( appBar: AppBar( - leading: - controller.loading ? null : const BackButton(color: Colors.white), + leading: controller.loading ? null : const BackButton(), automaticallyImplyLeading: !controller.loading, centerTitle: true, title: Text( @@ -25,7 +24,6 @@ class LoginView extends StatelessWidget { .homeserver .toString() .replaceFirst('https://', '')), - style: const TextStyle(color: Colors.white), ), ), body: Builder(builder: (context) { @@ -49,10 +47,7 @@ class LoginView extends StatelessWidget { errorText: controller.usernameError, errorStyle: const TextStyle(color: Colors.orange), hintText: L10n.of(context)!.emailOrUsername, - fillColor: Theme.of(context) - .colorScheme - .background - .withOpacity(0.75), + fillColor: Theme.of(context).colorScheme.background, ), ), ), @@ -82,10 +77,7 @@ class LoginView extends StatelessWidget { onPressed: controller.toggleShowPassword, ), hintText: L10n.of(context)!.password, - fillColor: Theme.of(context) - .colorScheme - .background - .withOpacity(0.75), + fillColor: Theme.of(context).colorScheme.background, ), ), ), @@ -94,6 +86,10 @@ class LoginView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(12.0), child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + foregroundColor: Theme.of(context).colorScheme.onPrimary, + ), onPressed: controller.loading ? null : () => controller.login(context), @@ -103,36 +99,41 @@ class LoginView extends StatelessWidget { ), ), ), - Row( - children: [ - const Expanded( + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Row( + children: [ + Expanded( child: Divider( - color: Colors.white, - thickness: 1, - )), - Padding( - padding: const EdgeInsets.all(12.0), - child: Text( - L10n.of(context)!.or, - style: const TextStyle( - color: Colors.white, - fontSize: 18, + thickness: 1, + color: Theme.of(context).textTheme.subtitle1?.color, ), ), - ), - const Expanded( + Padding( + padding: const EdgeInsets.all(12.0), + child: Text( + L10n.of(context)!.or, + style: const TextStyle(fontSize: 18), + ), + ), + Expanded( child: Divider( - color: Colors.white, - thickness: 1, - )), - ], + thickness: 1, + color: Theme.of(context).textTheme.subtitle1?.color, + ), + ), + ], + ), ), Padding( padding: const EdgeInsets.all(12.0), child: ElevatedButton( onPressed: controller.loading ? () {} : controller.passwordForgotten, - style: ElevatedButton.styleFrom(foregroundColor: Colors.red), + style: ElevatedButton.styleFrom( + foregroundColor: Theme.of(context).colorScheme.error, + backgroundColor: Theme.of(context).colorScheme.onError, + ), child: Text(L10n.of(context)!.passwordForgotten), ), ), diff --git a/lib/pages/sign_up/signup_view.dart b/lib/pages/sign_up/signup_view.dart index f106d280..51e09269 100644 --- a/lib/pages/sign_up/signup_view.dart +++ b/lib/pages/sign_up/signup_view.dart @@ -13,13 +13,9 @@ class SignupPageView extends StatelessWidget { Widget build(BuildContext context) { return LoginScaffold( appBar: AppBar( - leading: - controller.loading ? null : const BackButton(color: Colors.white), + leading: controller.loading ? null : const BackButton(), automaticallyImplyLeading: !controller.loading, - title: Text( - L10n.of(context)!.signUp, - style: const TextStyle(color: Colors.white), - ), + title: Text(L10n.of(context)!.signUp), ), body: Form( key: controller.formKey, @@ -50,10 +46,7 @@ class SignupPageView extends StatelessWidget { ), errorStyle: const TextStyle(color: Colors.orange), hintText: L10n.of(context)!.chooseAStrongPassword, - fillColor: Theme.of(context) - .colorScheme - .background - .withOpacity(0.75), + fillColor: Theme.of(context).colorScheme.background, ), ), ), @@ -72,10 +65,7 @@ class SignupPageView extends StatelessWidget { prefixIcon: const Icon(Icons.repeat_outlined), hintText: L10n.of(context)!.repeatPassword, errorStyle: const TextStyle(color: Colors.orange), - fillColor: Theme.of(context) - .colorScheme - .background - .withOpacity(0.75), + fillColor: Theme.of(context).colorScheme.background, ), ), ), @@ -93,10 +83,8 @@ class SignupPageView extends StatelessWidget { prefixIcon: const Icon(Icons.mail_outlined), hintText: L10n.of(context)!.enterAnEmailAddress, errorText: controller.error, - fillColor: Theme.of(context) - .colorScheme - .background - .withOpacity(0.75), + fillColor: Theme.of(context).colorScheme.background, + errorMaxLines: 4, errorStyle: TextStyle( color: controller.emailController.text.isEmpty ? Colors.orangeAccent @@ -110,6 +98,10 @@ class SignupPageView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(12), child: ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: Theme.of(context).colorScheme.onPrimary, + backgroundColor: Theme.of(context).colorScheme.primary, + ), onPressed: controller.loading ? () {} : controller.signup, child: controller.loading ? const LinearProgressIndicator() diff --git a/lib/widgets/layouts/login_scaffold.dart b/lib/widgets/layouts/login_scaffold.dart index a47f11bd..f3a9a77c 100644 --- a/lib/widgets/layouts/login_scaffold.dart +++ b/lib/widgets/layouts/login_scaffold.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; + +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; class LoginScaffold extends StatelessWidget { final Widget body; @@ -13,32 +15,47 @@ class LoginScaffold extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - automaticallyImplyLeading: appBar?.automaticallyImplyLeading ?? true, - centerTitle: appBar?.centerTitle, - title: appBar?.title, - leading: appBar?.leading, - actions: appBar?.actions, - iconTheme: const IconThemeData(color: Colors.white), - elevation: 0, - scrolledUnderElevation: 0, - backgroundColor: Colors.transparent, - systemOverlayStyle: SystemUiOverlayStyle.light, - ), - extendBodyBehindAppBar: true, - extendBody: true, - body: Container( - decoration: const BoxDecoration( - image: DecorationImage( - fit: BoxFit.cover, - image: AssetImage('assets/login_wallpaper.png'), - ), + final isMobileMode = !FluffyThemes.isColumnMode(context); + return Container( + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/login_wallpaper.png'), + fit: BoxFit.cover, ), - alignment: Alignment.center, - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 480), - child: body, + ), + child: Center( + child: Material( + color: Theme.of(context).brightness == Brightness.light + ? Theme.of(context).scaffoldBackgroundColor.withOpacity(0.9) + : Theme.of(context).scaffoldBackgroundColor.withOpacity(0.75), + borderRadius: isMobileMode + ? null + : BorderRadius.circular(AppConfig.borderRadius), + elevation: isMobileMode ? 0 : 10, + clipBehavior: Clip.hardEdge, + shadowColor: Colors.black, + child: ConstrainedBox( + constraints: isMobileMode + ? const BoxConstraints() + : const BoxConstraints(maxWidth: 480, maxHeight: 640), + child: Scaffold( + backgroundColor: Colors.transparent, + appBar: appBar == null + ? null + : AppBar( + automaticallyImplyLeading: + appBar?.automaticallyImplyLeading ?? true, + centerTitle: appBar?.centerTitle, + title: appBar?.title, + leading: appBar?.leading, + actions: appBar?.actions, + backgroundColor: Colors.transparent, + ), + extendBodyBehindAppBar: true, + extendBody: true, + body: body, + ), + ), ), ), );