diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index aa9d3718..2ed0993a 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -81,7 +81,7 @@ class MessageContent extends StatelessWidget { mxContent: sender.avatarUrl, name: sender.calcDisplayname(), presenceUserId: sender.stateKey, - client: Matrix.of(context).client, + client: event.room.client, ), title: Text(sender.calcDisplayname()), subtitle: Text(event.originServerTs.localizedTime(context)), diff --git a/lib/pages/homeserver_picker/homeserver_app_bar.dart b/lib/pages/homeserver_picker/homeserver_app_bar.dart index 165d76ea..50058c20 100644 --- a/lib/pages/homeserver_picker/homeserver_app_bar.dart +++ b/lib/pages/homeserver_picker/homeserver_app_bar.dart @@ -4,6 +4,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/homeserver_picker/public_homeserver.dart'; import 'homeserver_bottom_sheet.dart'; import 'homeserver_picker.dart'; @@ -77,7 +78,9 @@ class HomeserverAppBar extends StatelessWidget { icon: const Icon(Icons.arrow_back), ) : null, - fillColor: Theme.of(context).colorScheme.onInverseSurface, + fillColor: FluffyThemes.isColumnMode(context) + ? Theme.of(context).colorScheme.background + : Theme.of(context).colorScheme.surfaceVariant, prefixText: '${L10n.of(context)!.homeserver}: ', hintText: L10n.of(context)!.enterYourHomeserver, suffixIcon: const Icon(Icons.search), diff --git a/lib/pages/homeserver_picker/homeserver_picker.dart b/lib/pages/homeserver_picker/homeserver_picker.dart index 5f42c325..3712a3d6 100644 --- a/lib/pages/homeserver_picker/homeserver_picker.dart +++ b/lib/pages/homeserver_picker/homeserver_picker.dart @@ -192,7 +192,7 @@ class HomeserverPickerController extends State { return cachedHomeservers = homeserverList; } - void login() => context.go('${GoRouterState.of(context).fullPath}/login'); + void login() => context.go('/home/login'); @override void initState() { diff --git a/lib/pages/homeserver_picker/homeserver_picker_view.dart b/lib/pages/homeserver_picker/homeserver_picker_view.dart index bd9e2d6d..d48b626a 100644 --- a/lib/pages/homeserver_picker/homeserver_picker_view.dart +++ b/lib/pages/homeserver_picker/homeserver_picker_view.dart @@ -32,199 +32,189 @@ class HomeserverPickerView extends StatelessWidget { appBar: AppBar( titleSpacing: 12, automaticallyImplyLeading: false, + surfaceTintColor: Theme.of(context).colorScheme.background, title: HomeserverAppBar(controller: controller), ), - 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: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, + 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: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + clipBehavior: Clip.hardEdge, + decoration: const BoxDecoration(), + child: Material( clipBehavior: Clip.hardEdge, - 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: controller.isLoading - ? const Center(child: CircularProgressIndicator.adaptive()) - : ListView( - children: [ + ), + Expanded( + child: controller.isLoading + ? const Center(child: CircularProgressIndicator.adaptive()) + : ListView( + children: [ + if (errorText != null) ...[ const SizedBox(height: 12), - if (errorText != null) ...[ - const Center( - child: Icon( - Icons.error_outline, - size: 48, - color: Colors.orange, - ), + const Center( + child: Icon( + Icons.error_outline, + size: 48, + color: Colors.orange, ), - const SizedBox(height: 12), - Center( - child: Text( - errorText, - textAlign: TextAlign.center, - style: TextStyle( - color: Theme.of(context).colorScheme.error, - fontSize: 18, - ), - ), - ), - Center( - child: Text( - L10n.of(context)! - .pleaseTryAgainLaterOrChooseDifferentServer, - textAlign: TextAlign.center, - style: TextStyle( - color: Theme.of(context).colorScheme.error, - fontSize: 12, - ), - ), - ), - const SizedBox(height: 36), - ] else - Padding( - padding: const EdgeInsets.only( - right: 8.0, - left: 8.0, - bottom: 16.0, - ), - child: FluffyThemes.isColumnMode(context) - ? Image.asset( - 'assets/info-logo.png', - height: 96, - ) - : Image.asset('assets/banner_transparent.png'), - ), - if (identityProviders != null) ...[ - ...identityProviders.map( - (provider) => _LoginButton( - icon: provider.icon == null - ? const Icon(Icons.open_in_new_outlined) - : Material( - color: Colors.white, - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, - ), - clipBehavior: Clip.hardEdge, - child: MxcImage( - placeholder: (_) => - const Icon(Icons.web_outlined), - uri: Uri.parse(provider.icon!), - width: 24, - height: 24, - ), - ), - label: L10n.of(context)!.signInWith( - provider.name ?? - provider.brand ?? - L10n.of(context)!.singlesignon, - ), - onPressed: () => - controller.ssoLoginAction(provider.id), - ), - ), - ], - if (regLink != null) - _LoginButton( - onPressed: () => launchUrlString(regLink), - icon: const Icon(Icons.open_in_new), - label: L10n.of(context)!.register, - ), - if (controller.supportsPasswordLogin) - _LoginButton( - onPressed: controller.login, - icon: const Icon(Icons.login_outlined), - label: L10n.of(context)!.signInWithPassword, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Center( - child: SizedBox( - width: 256, - child: TextButton( - style: TextButton.styleFrom( - padding: - const EdgeInsets.symmetric(vertical: 12), - foregroundColor: - Theme.of(context).colorScheme.secondary, - ), - onPressed: controller.restoreBackup, - child: Text( - L10n.of(context)!.hydrate, - textAlign: TextAlign.center, - ), - ), + ), + const SizedBox(height: 12), + Center( + child: Text( + errorText, + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context).colorScheme.error, + fontSize: 18, ), ), ), + Center( + child: Text( + L10n.of(context)! + .pleaseTryAgainLaterOrChooseDifferentServer, + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context).colorScheme.error, + fontSize: 12, + ), + ), + ), + const SizedBox(height: 36), + ] else + Padding( + padding: const EdgeInsets.only( + top: 0.0, + right: 8.0, + left: 8.0, + bottom: 16.0, + ), + child: Image.asset( + 'assets/banner_transparent.png', + ), + ), + if (identityProviders != null) ...[ + ...identityProviders.map( + (provider) => _LoginButton( + icon: provider.icon == null + ? const Icon( + Icons.open_in_new_outlined, + size: 16, + ) + : Material( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, + ), + clipBehavior: Clip.hardEdge, + child: MxcImage( + placeholder: (_) => const Icon( + Icons.open_in_new_outlined, + size: 16, + ), + uri: Uri.parse(provider.icon!), + width: 24, + height: 24, + isThumbnail: false, + //isThumbnail: false, + ), + ), + label: L10n.of(context)!.signInWith( + provider.name ?? + provider.brand ?? + L10n.of(context)!.singlesignon, + ), + onPressed: () => + controller.ssoLoginAction(provider.id), + ), + ), ], - ), - ), - ], - ), + if (controller.supportsPasswordLogin) + _LoginButton( + onPressed: controller.login, + label: L10n.of(context)!.signInWithPassword, + icon: const Icon(Icons.lock_open_outlined, size: 16), + ), + if (regLink != null) + _LoginButton( + onPressed: () => launchUrlString(regLink), + icon: const Icon( + Icons.open_in_new_outlined, + size: 16, + ), + label: L10n.of(context)!.register, + ), + _LoginButton( + onPressed: controller.restoreBackup, + label: L10n.of(context)!.hydrate, + withBorder: false, + ), + const SizedBox(height: 16), + ], + ), + ), + ], ), ); } } class _LoginButton extends StatelessWidget { - final Widget icon; + final Widget? icon; final String label; final void Function() onPressed; + final bool withBorder; const _LoginButton({ - required this.icon, + this.icon, required this.label, required this.onPressed, + this.withBorder = true, }); @override Widget build(BuildContext context) { + final icon = this.icon; return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.symmetric(horizontal: 16), alignment: Alignment.center, child: SizedBox( width: double.infinity, - child: OutlinedButton( + child: OutlinedButton.icon( style: OutlinedButton.styleFrom( side: BorderSide( - width: 1, - color: Theme.of(context).colorScheme.surfaceVariant, + width: withBorder ? 1 : 0, + color: withBorder + ? Theme.of(context).colorScheme.onBackground + : Colors.transparent, ), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(AppConfig.borderRadius), + borderRadius: BorderRadius.circular(99), ), foregroundColor: Theme.of(context).colorScheme.onBackground, + backgroundColor: withBorder + ? Theme.of(context).colorScheme.background + : Colors.transparent, ), onPressed: onPressed, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - icon, - const SizedBox(width: 16), - Text( - label, - overflow: TextOverflow.ellipsis, - ), - ], - ), + label: Text(label), + icon: icon ?? const SizedBox.shrink(), ), ), ); diff --git a/lib/pages/login/login_view.dart b/lib/pages/login/login_view.dart index 503ab6ac..2ee1b558 100644 --- a/lib/pages/login/login_view.dart +++ b/lib/pages/login/login_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/widgets/layouts/login_scaffold.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'login.dart'; @@ -13,29 +14,48 @@ class LoginView extends StatelessWidget { @override Widget build(BuildContext context) { + final homeserver = Matrix.of(context) + .getLoginClient() + .homeserver + .toString() + .replaceFirst('https://', ''); + final title = L10n.of(context)!.logInTo(homeserver); + final titleParts = title.split(homeserver); + + final textFieldFillColor = FluffyThemes.isColumnMode(context) + ? Theme.of(context).colorScheme.background + : Theme.of(context).colorScheme.surfaceVariant; + return LoginScaffold( enforceMobileMode: Matrix.of(context).client.isLogged(), appBar: AppBar( - leading: controller.loading ? null : const BackButton(), + leading: controller.loading ? null : const Center(child: BackButton()), automaticallyImplyLeading: !controller.loading, - centerTitle: true, - title: Text( - L10n.of(context)!.logInTo( - Matrix.of(context) - .getLoginClient() - .homeserver - .toString() - .replaceFirst('https://', ''), + titleSpacing: !controller.loading ? 0 : null, + title: Text.rich( + TextSpan( + children: [ + TextSpan(text: titleParts.first), + TextSpan( + text: homeserver, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan(text: titleParts.last), + ], ), + style: const TextStyle(fontSize: 18), ), ), body: Builder( builder: (context) { return AutofillGroup( child: ListView( + padding: const EdgeInsets.symmetric(horizontal: 8), children: [ + Image.asset('assets/banner_transparent.png'), + const SizedBox(height: 16), Padding( - padding: const EdgeInsets.all(12.0), + padding: const EdgeInsets.symmetric(horizontal: 8.0), child: TextField( readOnly: controller.loading, autocorrect: false, @@ -50,12 +70,14 @@ class LoginView extends StatelessWidget { prefixIcon: const Icon(Icons.account_box_outlined), errorText: controller.usernameError, errorStyle: const TextStyle(color: Colors.orange), + fillColor: textFieldFillColor, hintText: L10n.of(context)!.emailOrUsername, ), ), ), + const SizedBox(height: 16), Padding( - padding: const EdgeInsets.all(12.0), + padding: const EdgeInsets.symmetric(horizontal: 8.0), child: TextField( readOnly: controller.loading, autocorrect: false, @@ -69,6 +91,7 @@ class LoginView extends StatelessWidget { prefixIcon: const Icon(Icons.lock_outlined), errorText: controller.passwordError, errorStyle: const TextStyle(color: Colors.orange), + fillColor: textFieldFillColor, suffixIcon: IconButton( onPressed: controller.toggleShowPassword, icon: Icon( @@ -82,64 +105,36 @@ class LoginView extends StatelessWidget { ), ), ), - Hero( - tag: 'signinButton', - child: Padding( - padding: const EdgeInsets.all(12.0), - child: ElevatedButton.icon( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.primary, - foregroundColor: - Theme.of(context).colorScheme.onPrimary, - ), - onPressed: controller.loading ? null : controller.login, - icon: const Icon(Icons.login_outlined), - label: controller.loading - ? const LinearProgressIndicator() - : Text(L10n.of(context)!.login), - ), - ), - ), + const SizedBox(height: 16), Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Row( - children: [ - Expanded( - child: Divider( - thickness: 1, - color: Theme.of(context).dividerColor, - ), - ), - Padding( - padding: const EdgeInsets.all(12.0), - child: Text( - L10n.of(context)!.or, - style: const TextStyle(fontSize: 18), - ), - ), - Expanded( - child: Divider( - thickness: 1, - color: Theme.of(context).dividerColor, - ), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.all(12.0), + padding: const EdgeInsets.symmetric(horizontal: 8.0), child: ElevatedButton.icon( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + foregroundColor: Theme.of(context).colorScheme.onPrimary, + ), + onPressed: controller.loading ? null : controller.login, + icon: const Icon(Icons.login_outlined), + label: controller.loading + ? const LinearProgressIndicator() + : Text(L10n.of(context)!.login), + ), + ), + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: TextButton.icon( onPressed: controller.loading ? () {} : controller.passwordForgotten, - style: ElevatedButton.styleFrom( + style: TextButton.styleFrom( foregroundColor: Theme.of(context).colorScheme.error, - backgroundColor: Theme.of(context).colorScheme.onError, ), icon: const Icon(Icons.safety_check_outlined), label: Text(L10n.of(context)!.passwordForgotten), ), ), + const SizedBox(height: 16), ], ), ); diff --git a/lib/widgets/layouts/empty_page.dart b/lib/widgets/layouts/empty_page.dart index a43f96df..ce68a7d8 100644 --- a/lib/widgets/layouts/empty_page.dart +++ b/lib/widgets/layouts/empty_page.dart @@ -2,10 +2,11 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:fluffychat/config/themes.dart'; + class EmptyPage extends StatelessWidget { - final bool loading; - static const double _width = 300; - const EmptyPage({this.loading = false, super.key}); + static const double _width = 128; + const EmptyPage({super.key}); @override Widget build(BuildContext context) { final width = min(MediaQuery.of(context).size.width, EmptyPage._width) / 2; @@ -14,31 +15,20 @@ class EmptyPage extends StatelessWidget { appBar: AppBar( automaticallyImplyLeading: false, elevation: 0, - backgroundColor: Theme.of(context).scaffoldBackgroundColor, + backgroundColor: Colors.transparent, ), extendBodyBehindAppBar: true, - body: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Center( - child: Hero( - tag: 'info-logo', - child: Image.asset( - 'assets/favicon.png', - width: width, - height: width, - filterQuality: FilterQuality.medium, - ), - ), - ), - if (loading) - Center( - child: SizedBox( - width: width, - child: const LinearProgressIndicator(), - ), - ), - ], + body: Container( + decoration: BoxDecoration( + gradient: FluffyThemes.backgroundGradient(context, 128), + ), + alignment: Alignment.center, + child: Image.asset( + 'assets/favicon.png', + width: width, + height: width, + filterQuality: FilterQuality.medium, + ), ), ); } diff --git a/lib/widgets/layouts/login_scaffold.dart b/lib/widgets/layouts/login_scaffold.dart index 0ad8e93d..d039c996 100644 --- a/lib/widgets/layouts/login_scaffold.dart +++ b/lib/widgets/layouts/login_scaffold.dart @@ -37,9 +37,10 @@ class LoginScaffold extends StatelessWidget { actions: appBar?.actions, backgroundColor: isMobileMode ? null : Colors.transparent, ), - extendBodyBehindAppBar: true, - extendBody: true, body: body, + backgroundColor: isMobileMode + ? null + : Theme.of(context).colorScheme.background.withOpacity(0.9), bottomNavigationBar: isMobileMode ? Material( elevation: 4, @@ -52,8 +53,11 @@ class LoginScaffold extends StatelessWidget { ); if (isMobileMode) return scaffold; return Container( - decoration: BoxDecoration( - gradient: FluffyThemes.backgroundGradient(context, 255), + decoration: const BoxDecoration( + image: DecorationImage( + fit: BoxFit.cover, + image: AssetImage('assets/login_wallpaper.png'), + ), ), child: Column( children: [ @@ -63,7 +67,7 @@ class LoginScaffold extends StatelessWidget { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Material( - color: Theme.of(context).scaffoldBackgroundColor, + color: Colors.transparent, borderRadius: BorderRadius.circular(AppConfig.borderRadius), clipBehavior: Clip.hardEdge, elevation: @@ -72,34 +76,14 @@ class LoginScaffold extends StatelessWidget { child: ConstrainedBox( constraints: isMobileMode ? const BoxConstraints() - : const BoxConstraints(maxWidth: 960, maxHeight: 640), - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: Image.asset( - 'assets/login_wallpaper.png', - fit: BoxFit.cover, - ), - ), - Container( - width: 1, - color: Theme.of(context).dividerTheme.color, - ), - Expanded( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: scaffold, - ), - ), - ], - ), + : const BoxConstraints(maxWidth: 480, maxHeight: 720), + child: scaffold, ), ), ), ), ), - const _PrivacyButtons(mainAxisAlignment: MainAxisAlignment.end), + const _PrivacyButtons(mainAxisAlignment: MainAxisAlignment.center), ], ), ); @@ -112,6 +96,18 @@ class _PrivacyButtons extends StatelessWidget { @override Widget build(BuildContext context) { + final shadowTextStyle = FluffyThemes.isColumnMode(context) + ? const TextStyle( + color: Colors.white, + shadows: [ + Shadow( + offset: Offset(0.0, 0.0), + blurRadius: 3, + color: Colors.black, + ), + ], + ) + : null; return SizedBox( height: 64, child: Padding( @@ -121,11 +117,17 @@ class _PrivacyButtons extends StatelessWidget { children: [ TextButton( onPressed: () => PlatformInfos.showDialog(context), - child: Text(L10n.of(context)!.about), + child: Text( + L10n.of(context)!.about, + style: shadowTextStyle, + ), ), TextButton( onPressed: () => launchUrlString(AppConfig.privacyUrl), - child: Text(L10n.of(context)!.privacy), + child: Text( + L10n.of(context)!.privacy, + style: shadowTextStyle, + ), ), ], ),