From dfeb027e3d1eeb90e9c785bd3b1559d178a4b4e4 Mon Sep 17 00:00:00 2001 From: Clement Guyon Date: Thu, 12 Mar 2026 11:09:04 +0100 Subject: [PATCH 1/3] TW-2991: Display recovery key in Privacy & Security settings --- analysis_options.yaml | 3 +- assets/images/ic_recovery_key.svg | 3 + assets/l10n/intl_en.arb | 4 + .../audio_message/audio_play_extension.dart | 3 + .../settings/settings_item_builder.dart | 51 +++--- .../settings/settings_view.dart | 93 +++++----- .../settings/settings_view_style.dart | 2 +- .../settings_security/settings_security.dart | 136 +++++++++----- .../settings_security_view.dart | 168 +++++++++--------- lib/resource/image_paths.dart | 2 + 10 files changed, 260 insertions(+), 205 deletions(-) create mode 100644 assets/images/ic_recovery_key.svg diff --git a/analysis_options.yaml b/analysis_options.yaml index 0f21033c9a..99a1885531 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -19,7 +19,8 @@ analyzer: exclude: - lib/generated_plugin_registrant.dart - lib/l10n/*.dart - - '**.g.dart' + - "**.g.dart" + - integration_test/test_bundle.dart dart_code_metrics: metrics: diff --git a/assets/images/ic_recovery_key.svg b/assets/images/ic_recovery_key.svg new file mode 100644 index 0000000000..fdc1a37307 --- /dev/null +++ b/assets/images/ic_recovery_key.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 433657ab67..9281286b49 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -1788,6 +1788,10 @@ }, "recoveryKey": "Recovery key", "@recoveryKey": {}, + "recoveryKeyWarningMessage": "This secret grants access to all your encrypted messages to whomever uses it. Keep it safe and do not share it.", + "@recoveryKeyWarningMessage": {}, + "recoveryKeyCopiedToClipboard": "Recovery key copied to clipboard", + "@recoveryKeyCopiedToClipboard": {}, "recoveryKeyLost": "Recovery key lost?", "@recoveryKeyLost": {}, "seenByUser": "Seen by {username}", diff --git a/lib/pages/chat/events/audio_message/audio_play_extension.dart b/lib/pages/chat/events/audio_message/audio_play_extension.dart index a45c5629d6..9687879c9c 100644 --- a/lib/pages/chat/events/audio_message/audio_play_extension.dart +++ b/lib/pages/chat/events/audio_message/audio_play_extension.dart @@ -1,6 +1,9 @@ +// ignore_for_file: experimental_member_use + import 'package:just_audio/just_audio.dart'; import 'package:matrix/matrix.dart'; +///TODO(clement): Remove the ignore linter when the experimental member use is no longer needed. class MatrixFileAudioSource extends StreamAudioSource { final MatrixFile file; diff --git a/lib/pages/settings_dashboard/settings/settings_item_builder.dart b/lib/pages/settings_dashboard/settings/settings_item_builder.dart index 5de47e3bd0..e4a870e7fd 100644 --- a/lib/pages/settings_dashboard/settings/settings_item_builder.dart +++ b/lib/pages/settings_dashboard/settings/settings_item_builder.dart @@ -36,6 +36,9 @@ class SettingsItemBuilder extends StatelessWidget { @override Widget build(BuildContext context) { + final textStyle = LinagoraTextStyle.material(); + final tertiary30 = LinagoraRefColors.material().tertiary[30]; + return TwakeInkWell( isSelected: isSelected, onTap: onTap, @@ -45,7 +48,7 @@ class SettingsItemBuilder extends StatelessWidget { child: Padding( padding: SettingsViewStyle.itemBuilderPadding, child: Row( - crossAxisAlignment: CrossAxisAlignment.center, + crossAxisAlignment: .center, children: [ Padding( padding: SettingsViewStyle.leadingItemBuilderPadding, @@ -59,22 +62,21 @@ class SettingsItemBuilder extends StatelessWidget { ), Expanded( child: Row( - crossAxisAlignment: CrossAxisAlignment.center, + crossAxisAlignment: .center, children: [ Expanded( child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: .min, + crossAxisAlignment: .start, children: [ Text( title, - style: LinagoraTextStyle.material().bodyMedium2 - .copyWith( - color: titleColor, - fontFamily: 'Inter', - ), + style: textStyle.bodyMedium2.copyWith( + color: titleColor, + fontFamily: 'Inter', + ), maxLines: 2, - overflow: TextOverflow.ellipsis, + overflow: .ellipsis, ), if (subtitle != null) Padding( @@ -83,27 +85,24 @@ class SettingsItemBuilder extends StatelessWidget { subtitle!, style: subtitleStyle ?? - LinagoraTextStyle.material().bodyMedium - .copyWith( - color: - subtitleColor ?? - LinagoraRefColors.material() - .tertiary[30], - fontFamily: 'Inter', - ), - overflow: TextOverflow.ellipsis, + textStyle.bodyMedium.copyWith( + color: subtitleColor ?? tertiary30, + fontFamily: 'Inter', + ), + overflow: .ellipsis, ), ), ], ), ), - if (!isHideTrailingIcon) - trailingWidget ?? - Icon( - Icons.chevron_right_outlined, - size: SettingsViewStyle.iconSize, - color: LinagoraRefColors.material().tertiary[30], - ), + if (trailingWidget != null) + trailingWidget! + else if (!isHideTrailingIcon) + Icon( + Icons.chevron_right_outlined, + size: SettingsViewStyle.iconSize, + color: tertiary30, + ), ], ), ), diff --git a/lib/pages/settings_dashboard/settings/settings_view.dart b/lib/pages/settings_dashboard/settings/settings_view.dart index 03c60c7aa6..4f6ad00ee4 100644 --- a/lib/pages/settings_dashboard/settings/settings_view.dart +++ b/lib/pages/settings_dashboard/settings/settings_view.dart @@ -22,10 +22,18 @@ class SettingsView extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = L10n.of(context)!; + final sysColor = LinagoraSysColors.material(); + final refColorTertiary30 = LinagoraRefColors.material().tertiary[30]; + final theme = Theme.of(context); + final appbarTheme = theme.appBarTheme; + final textTheme = theme.textTheme; + final colorScheme = theme.colorScheme; + return Scaffold( - backgroundColor: LinagoraSysColors.material().onPrimary, + backgroundColor: sysColor.onPrimary, appBar: TwakeAppBar( - title: L10n.of(context)!.settings, + title: l10n.settings, withDivider: responsiveUtils.isMobile(context), context: context, ), @@ -33,24 +41,22 @@ class SettingsView extends StatelessWidget { body: ListTileTheme( // TODO: change to colorSurface when its approved // ignore: deprecated_member_use - iconColor: Theme.of(context).colorScheme.onBackground, + iconColor: colorScheme.onBackground, child: ListView( key: const Key('SettingsListViewContent'), children: [ Padding( padding: SettingsViewStyle.bodySettingsScreenPadding, child: Material( - borderRadius: BorderRadius.circular( - SettingsViewStyle.borderRadius, - ), - clipBehavior: Clip.hardEdge, + borderRadius: .circular(SettingsViewStyle.borderRadius), + clipBehavior: .hardEdge, color: controller.optionsSelectNotifier.value == SettingEnum.profile - ? Theme.of(context).colorScheme.secondaryContainer - : LinagoraSysColors.material().onPrimary, + ? colorScheme.secondaryContainer + : sysColor.onPrimary, child: InkWell( - onTap: () => controller.goToSettingsProfile(), + onTap: controller.goToSettingsProfile, child: Padding( padding: SettingsViewStyle.itemBuilderPadding, child: Row( @@ -62,18 +68,11 @@ class SettingsView extends StatelessWidget { padding: SettingsViewStyle.avatarPadding, child: Material( elevation: - Theme.of( - context, - ).appBarTheme.scrolledUnderElevation ?? - 4, - shadowColor: Theme.of( - context, - ).appBarTheme.shadowColor, + appbarTheme.scrolledUnderElevation ?? 4, + shadowColor: appbarTheme.shadowColor, shape: RoundedRectangleBorder( - side: BorderSide( - color: Theme.of(context).dividerColor, - ), - borderRadius: BorderRadius.circular( + side: BorderSide(color: theme.dividerColor), + borderRadius: .circular( AvatarStyle.defaultSize, ), ), @@ -89,12 +88,12 @@ class SettingsView extends StatelessWidget { ), Expanded( child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: .spaceBetween, children: [ Expanded( child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: .center, + crossAxisAlignment: .start, children: [ ValueListenableBuilder( valueListenable: @@ -102,14 +101,9 @@ class SettingsView extends StatelessWidget { builder: (context, displayName, _) { return Text( displayName ?? controller.displayName, - style: Theme.of(context) - .textTheme - .titleLarge - ?.copyWith( - color: Theme.of( - context, - ).colorScheme.onSurface, - ), + style: textTheme.titleLarge?.copyWith( + color: colorScheme.onSurface, + ), maxLines: 1, overflow: TextOverflow.ellipsis, ); @@ -117,13 +111,9 @@ class SettingsView extends StatelessWidget { ), Text( controller.client.mxid(context), - style: Theme.of(context) - .textTheme - .labelLarge - ?.copyWith( - color: LinagoraRefColors.material() - .tertiary[30], - ), + style: textTheme.labelLarge?.copyWith( + color: refColorTertiary30, + ), maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -133,8 +123,7 @@ class SettingsView extends StatelessWidget { Icon( Icons.chevron_right_outlined, size: SettingsViewStyle.iconSize, - color: - LinagoraRefColors.material().tertiary[30], + color: refColorTertiary30, ), ], ), @@ -148,34 +137,32 @@ class SettingsView extends StatelessWidget { Padding( padding: SettingsViewStyle.profileItemDividerPadding(context), child: Divider( - color: LinagoraStateLayer( - LinagoraSysColors.material().surfaceTint, - ).opacityLayer3, - thickness: SettingsViewStyle.settingsItemDividerThikness, + color: LinagoraStateLayer(sysColor.surfaceTint).opacityLayer3, + thickness: SettingsViewStyle.settingsItemDividerThickness, height: SettingsViewStyle.settingsItemDividerHeight, ), ), if (!controller.matrix.twakeSupported) ValueListenableBuilder( valueListenable: controller.showChatBackupSwitch, - builder: (context, backUpAvailable, child) { + builder: (_, backUpAvailable, _) { return SwitchListTile( - controlAffinity: ListTileControlAffinity.trailing, + controlAffinity: .trailing, contentPadding: SettingsViewStyle.backupSwitchPadding, value: backUpAvailable == false, secondary: const Icon(Icons.backup_outlined), - title: Text(L10n.of(context)!.chatBackup), + title: Text(l10n.chatBackup), onChanged: controller.firstRunBootstrapAction, ); }, child: ListTile( leading: const Icon(Icons.backup_outlined), - title: Text(L10n.of(context)!.chatBackup), + title: Text(l10n.chatBackup), trailing: const CircularProgressIndicator.adaptive(), ), ), Column( - children: controller.getListSettingItem.map((item) { + children: controller.getListSettingItem.map((SettingEnum item) { return Column( children: [ Padding( @@ -198,10 +185,10 @@ class SettingsView extends StatelessWidget { SettingsViewStyle.settingsItemDividerPadding(), child: Divider( color: LinagoraStateLayer( - LinagoraSysColors.material().surfaceTint, + sysColor.surfaceTint, ).opacityLayer3, - thickness: - SettingsViewStyle.settingsItemDividerThikness, + thickness: SettingsViewStyle + .settingsItemDividerThickness, height: SettingsViewStyle.settingsItemDividerHeight, ), diff --git a/lib/pages/settings_dashboard/settings/settings_view_style.dart b/lib/pages/settings_dashboard/settings/settings_view_style.dart index f2c75c2d86..5432fe833d 100644 --- a/lib/pages/settings_dashboard/settings/settings_view_style.dart +++ b/lib/pages/settings_dashboard/settings/settings_view_style.dart @@ -31,7 +31,7 @@ class SettingsViewStyle { static const double borderRadius = 4.0; static const double settingsItemDividerHeight = 1.0; - static const double settingsItemDividerThikness = 1; + static const double settingsItemDividerThickness = 1; static EdgeInsets settingsItemDividerPadding() => const EdgeInsets.only(left: 48.0, right: 8.0); diff --git a/lib/pages/settings_dashboard/settings_security/settings_security.dart b/lib/pages/settings_dashboard/settings_security/settings_security.dart index 747333d80f..f5f7c4b2d9 100644 --- a/lib/pages/settings_dashboard/settings_security/settings_security.dart +++ b/lib/pages/settings_dashboard/settings_security/settings_security.dart @@ -1,9 +1,12 @@ import 'dart:async'; import 'dart:convert'; +import 'package:fluffychat/di/global/get_it_initializer.dart'; +import 'package:fluffychat/domain/usecase/recovery/get_recovery_words_interactor.dart'; import 'package:fluffychat/pages/bootstrap/bootstrap_dialog.dart'; import 'package:fluffychat/presentation/extensions/client_extension.dart'; import 'package:fluffychat/utils/beautify_string_extension.dart'; +import 'package:fluffychat/utils/clipboard.dart'; import 'package:fluffychat/utils/dialog/twake_dialog.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_file_extension.dart'; import 'package:fluffychat/utils/twake_snackbar.dart'; @@ -37,52 +40,113 @@ class SettingsSecurityController extends State { Client get client => Matrix.read(context).client; + /// Future that fetches the recovery key, created once in [initState] + /// and consumed by a [FutureBuilder] in the view. + late final Future recoveryKeyFuture; + + /// Cached recovery key value for the copy action. + String? _recoveryKey; + + @override + void initState() { + listenIgnoredUser(); + recoveryKeyFuture = _fetchRecoveryKey(); + super.initState(); + } + + @override + void dispose() { + ignoredUsersStreamSub?.cancel(); + ignoredUsersNotifier.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => SettingsSecurityView(this); + + /// Fetches the recovery key from the ToM server via [GetRecoveryWordsInteractor]. + /// Returns the key string or null on failure. + Future _fetchRecoveryKey() async { + final result = await getIt.get().execute(); + return result.fold((failure) => null, (success) { + _recoveryKey = success.words.words; + return _recoveryKey; + }); + } + + /// Copies the recovery key to clipboard after showing a security warning dialog. + /// The warning informs the user that this secret grants access to all + /// encrypted messages. + Future copyRecoveryKey() async { + final key = _recoveryKey; + if (key == null) return; + + final l10n = L10n.of(context)!; + final confirmed = await showOkCancelAlertDialog( + context: context, + title: l10n.recoveryKey, + message: l10n.recoveryKeyWarningMessage, + okLabel: l10n.copy, + cancelLabel: l10n.cancel, + isDestructiveAction: true, + ); + if (!mounted || confirmed != OkCancelResult.ok) return; + + TwakeClipboard.instance.copyText(key); + TwakeSnackBar.show(context, l10n.recoveryKeyCopiedToClipboard); + } + void changePasswordAccountAction() async { + final l10n = L10n.of(context)!; final input = await showTextInputDialog( useRootNavigator: false, context: context, - title: L10n.of(context)!.changePassword, - okLabel: L10n.of(context)!.ok, - cancelLabel: L10n.of(context)!.cancel, + title: l10n.changePassword, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, textFields: [ DialogTextField( - hintText: L10n.of(context)!.chooseAStrongPassword, + hintText: l10n.chooseAStrongPassword, obscureText: true, minLines: 1, maxLines: 1, ), DialogTextField( - hintText: L10n.of(context)!.repeatPassword, + hintText: l10n.repeatPassword, obscureText: true, minLines: 1, maxLines: 1, ), ], ); - if (input == null) return; + if (!mounted || input == null) return; final success = await TwakeDialog.showFutureLoadingDialogFullScreen( future: () => Matrix.of( context, ).client.changePassword(input.last, oldPassword: input.first), ); + if (!mounted) return; if (success.error == null) { - TwakeSnackBar.show(context, L10n.of(context)!.passwordHasBeenChanged); + TwakeSnackBar.show(context, l10n.passwordHasBeenChanged); } } void setAppLockAction() async { + final l10n = L10n.of(context)!; final currentLock = await const FlutterSecureStorage().read( key: SettingKeys.appLockKey, ); + if (!mounted) return; if (currentLock?.isNotEmpty ?? false) { await AppLock.of(context)!.showLockScreen(); } + if (!mounted) return; final newLock = await showTextInputDialog( useRootNavigator: false, context: context, - title: L10n.of(context)!.pleaseChooseAPasscode, - message: L10n.of(context)!.pleaseEnter4Digits, - cancelLabel: L10n.of(context)!.cancel, + title: l10n.pleaseChooseAPasscode, + message: l10n.pleaseEnter4Digits, + cancelLabel: l10n.cancel, textFields: [ DialogTextField( validator: (text) { @@ -90,7 +154,7 @@ class SettingsSecurityController extends State { (text.length == 4 && int.tryParse(text)! >= 0)) { return null; } - return L10n.of(context)!.pleaseEnter4Digits; + return l10n.pleaseEnter4Digits; }, keyboardType: TextInputType.number, obscureText: true, @@ -99,16 +163,16 @@ class SettingsSecurityController extends State { ), ], ); - if (newLock != null) { - await const FlutterSecureStorage().write( - key: SettingKeys.appLockKey, - value: newLock.single, - ); - if (newLock.single.isEmpty) { - AppLock.of(context)!.disable(); - } else { - AppLock.of(context)!.enable(); - } + if (!mounted || newLock == null) return; + await const FlutterSecureStorage().write( + key: SettingKeys.appLockKey, + value: newLock.single, + ); + if (!mounted) return; + if (newLock.single.isEmpty) { + AppLock.of(context)!.disable(); + } else { + AppLock.of(context)!.enable(); } } @@ -119,15 +183,15 @@ class SettingsSecurityController extends State { Future dehydrateAction() => dehydrateDevice(context); static Future dehydrateDevice(BuildContext context) async { + final l10n = L10n.of(context)!; final response = await showOkCancelAlertDialog( context: context, isDestructiveAction: true, - title: L10n.of(context)!.dehydrate, - message: L10n.of(context)!.dehydrateWarning, + title: l10n.dehydrate, + message: l10n.dehydrateWarning, ); - if (response != OkCancelResult.ok) { - return; - } + if (response != OkCancelResult.ok) return; + final file = await TwakeDialog.showFutureLoadingDialogFullScreen( future: () async { final export = await Matrix.of(context).client.exportDump(); @@ -148,8 +212,8 @@ class SettingsSecurityController extends State { } Future copyPublicKey() async { - Clipboard.setData( - ClipboardData(text: Matrix.of(context).client.fingerprintKey.beautified), + TwakeClipboard.instance.copyText( + Matrix.of(context).client.fingerprintKey.beautified, ); TwakeSnackBar.show(context, L10n.of(context)!.copiedPublicKeyToClipboard); } @@ -160,20 +224,4 @@ class SettingsSecurityController extends State { ignoredUsersNotifier.value = client.ignoredUsers; }); } - - @override - void initState() { - listenIgnoredUser(); - super.initState(); - } - - @override - void dispose() { - ignoredUsersStreamSub?.cancel(); - ignoredUsersNotifier.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) => SettingsSecurityView(this); } diff --git a/lib/pages/settings_dashboard/settings_security/settings_security_view.dart b/lib/pages/settings_dashboard/settings_security/settings_security_view.dart index a89e77da4e..08517b1d83 100644 --- a/lib/pages/settings_dashboard/settings_security/settings_security_view.dart +++ b/lib/pages/settings_dashboard/settings_security/settings_security_view.dart @@ -1,20 +1,21 @@ import 'package:fluffychat/config/go_routes/app_route_paths.dart'; import 'package:fluffychat/di/global/get_it_initializer.dart'; +import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:fluffychat/pages/settings_dashboard/settings/settings_item_builder.dart'; import 'package:fluffychat/pages/settings_dashboard/settings/settings_view_style.dart'; import 'package:fluffychat/resource/image_paths.dart'; +import 'package:fluffychat/utils/beautify_string_extension.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/responsive/responsive_utils.dart'; import 'package:fluffychat/widgets/app_bars/twake_app_bar.dart'; import 'package:fluffychat/widgets/app_bars/twake_app_bar_style.dart'; -import 'package:flutter/material.dart'; -import 'package:fluffychat/generated/l10n/app_localizations.dart'; -import 'package:fluffychat/utils/beautify_string_extension.dart'; -import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; + import 'settings_security.dart'; class SettingsSecurityView extends StatelessWidget { @@ -25,10 +26,16 @@ class SettingsSecurityView extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = L10n.of(context)!; + final colorScheme = Theme.of(context).colorScheme; + final refColorTertiary30 = LinagoraRefColors.material().tertiary[30]; + final sysColor = LinagoraSysColors.material(); + final linagoraTextStyleBodyMedium = LinagoraTextStyle.material().bodyMedium; + return Scaffold( - backgroundColor: LinagoraSysColors.material().onPrimary, + backgroundColor: sysColor.onPrimary, appBar: TwakeAppBar( - title: L10n.of(context)!.security, + title: l10n.security, context: context, centerTitle: true, withDivider: true, @@ -36,9 +43,9 @@ class SettingsSecurityView extends StatelessWidget { ? Padding( padding: TwakeAppBarStyle.leadingIconPadding, child: IconButton( - tooltip: L10n.of(context)!.back, + tooltip: l10n.back, icon: const Icon(Icons.arrow_back_ios), - onPressed: () => context.pop(), + onPressed: context.pop, iconSize: TwakeAppBarStyle.leadingIconSize, ), ) @@ -47,30 +54,22 @@ class SettingsSecurityView extends StatelessWidget { body: ListTileTheme( // TODO: remove when the color scheme is updated // ignore: deprecated_member_use - iconColor: Theme.of(context).colorScheme.onBackground, + iconColor: colorScheme.onBackground, child: MaxWidthBody( withScrolling: true, child: Column( children: [ - // #869 Hide privacy settings for now - // ListTile( - // leading: const Icon(Icons.camera_outlined), - // trailing: const Icon(Icons.chevron_right_outlined), - // title: Text(L10n.of(context)!.whoCanSeeMyStories), - // onTap: () => context.go('/stories'), - // ), Column( children: [ Padding( padding: SettingsViewStyle.bodySettingsScreenPadding, child: SettingsItemBuilder( key: const Key('contacts_visibility_settings_item'), - title: L10n.of(context)!.contactsVisibility, - titleColor: Theme.of(context).colorScheme.onBackground, - subtitle: L10n.of(context)!.whoCanFindMeByMyContacts, + title: l10n.contactsVisibility, + titleColor: colorScheme.onBackground, + subtitle: l10n.whoCanFindMeByMyContacts, leading: Icons.phone_outlined, - leadingIconColor: - LinagoraRefColors.material().tertiary[30], + leadingIconColor: refColorTertiary30, onTap: () { context.push(AppRoutePaths.contactsVisibilityFull); }, @@ -80,9 +79,9 @@ class SettingsSecurityView extends StatelessWidget { padding: SettingsViewStyle.settingsItemDividerPadding(), child: Divider( color: LinagoraStateLayer( - LinagoraSysColors.material().surfaceTint, + sysColor.surfaceTint, ).opacityLayer3, - thickness: SettingsViewStyle.settingsItemDividerThikness, + thickness: SettingsViewStyle.settingsItemDividerThickness, height: SettingsViewStyle.settingsItemDividerHeight, ), ), @@ -92,18 +91,15 @@ class SettingsSecurityView extends StatelessWidget { valueListenable: controller.ignoredUsersNotifier, builder: (context, ignoredUsers, _) { return SettingsItemBuilder( - title: L10n.of(context)!.blockedUsers, - titleColor: Theme.of( - context, - ).colorScheme.onBackground, + title: l10n.blockedUsers, + titleColor: colorScheme.onBackground, subtitle: ignoredUsers.isEmpty ? null : ignoredUsers.length.toString(), leadingWidget: SvgPicture.asset( ImagePaths.icFrontHand, colorFilter: ColorFilter.mode( - LinagoraRefColors.material().tertiary[30] ?? - LinagoraSysColors.material().onSurface, + refColorTertiary30 ?? sysColor.onSurface, BlendMode.srcIn, ), ), @@ -120,28 +116,63 @@ class SettingsSecurityView extends StatelessWidget { padding: SettingsViewStyle.settingsItemDividerPadding(), child: Divider( color: LinagoraStateLayer( - LinagoraSysColors.material().surfaceTint, + sysColor.surfaceTint, ).opacityLayer3, - thickness: SettingsViewStyle.settingsItemDividerThikness, + thickness: SettingsViewStyle.settingsItemDividerThickness, height: SettingsViewStyle.settingsItemDividerHeight, ), ), ], ), - // ListTile( - // leading: const Icon(Icons.password_outlined), - // trailing: const Icon(Icons.chevron_right_outlined), - // title: Text( - // L10n.of(context)!.changePassword, - // ), - // onTap: controller.changePasswordAccountAction, - // ), - // ListTile( - // leading: const Icon(Icons.mail_outlined), - // trailing: const Icon(Icons.chevron_right_outlined), - // title: Text(L10n.of(context)!.passwordRecovery), - // onTap: () => context.go('/3pid'), - // ), + // Recovery Key section - fetched from ToM server + FutureBuilder( + future: controller.recoveryKeyFuture, + builder: (context, snapshot) { + if (!snapshot.hasData || snapshot.data == null) { + return const SizedBox.shrink(); + } + return Column( + children: [ + Padding( + padding: SettingsViewStyle.bodySettingsScreenPadding, + child: SettingsItemBuilder( + title: l10n.recoveryKey, + titleColor: colorScheme.onBackground, + subtitle: '\u2022' * 32, + subtitleStyle: linagoraTextStyleBodyMedium.copyWith( + color: refColorTertiary30, + letterSpacing: 2, + ), + leadingWidget: SvgPicture.asset( + ImagePaths.icRecoveryKey, + colorFilter: ColorFilter.mode( + refColorTertiary30 ?? sysColor.onSurface, + BlendMode.srcIn, + ), + ), + isHideTrailingIcon: true, + trailingWidget: InkWell( + onTap: controller.copyRecoveryKey, + child: const Icon(Icons.content_copy), + ), + onTap: controller.copyRecoveryKey, + ), + ), + Padding( + padding: SettingsViewStyle.settingsItemDividerPadding(), + child: Divider( + color: LinagoraStateLayer( + sysColor.surfaceTint, + ).opacityLayer3, + thickness: + SettingsViewStyle.settingsItemDividerThickness, + height: SettingsViewStyle.settingsItemDividerHeight, + ), + ), + ], + ); + }, + ), if (Matrix.of(context).client.encryption != null) ...{ if (PlatformInfos.isMobile) Column( @@ -149,24 +180,21 @@ class SettingsSecurityView extends StatelessWidget { Padding( padding: SettingsViewStyle.bodySettingsScreenPadding, child: SettingsItemBuilder( - title: L10n.of(context)!.appLock, - titleColor: Theme.of( - context, - ).colorScheme.onBackground, + title: l10n.appLock, + titleColor: colorScheme.onBackground, leading: Icons.lock_outlined, onTap: controller.setAppLockAction, - leadingIconColor: - LinagoraRefColors.material().tertiary[30], + leadingIconColor: refColorTertiary30, ), ), Padding( padding: SettingsViewStyle.settingsItemDividerPadding(), child: Divider( color: LinagoraStateLayer( - LinagoraSysColors.material().surfaceTint, + sysColor.surfaceTint, ).opacityLayer3, thickness: - SettingsViewStyle.settingsItemDividerThikness, + SettingsViewStyle.settingsItemDividerThickness, height: SettingsViewStyle.settingsItemDividerHeight, ), ), @@ -176,19 +204,18 @@ class SettingsSecurityView extends StatelessWidget { padding: SettingsViewStyle.bodySettingsScreenPadding, child: SettingsItemBuilder( height: 116, - title: L10n.of(context)!.yourPublicKey, - titleColor: Theme.of(context).colorScheme.onBackground, + title: l10n.yourPublicKey, + titleColor: colorScheme.onBackground, subtitle: Matrix.of( context, ).client.fingerprintKey.beautified, - subtitleStyle: LinagoraTextStyle.material().bodyMedium - .copyWith( - color: LinagoraRefColors.material().tertiary[30], - fontFamily: 'monospace', - ), + subtitleStyle: linagoraTextStyleBodyMedium.copyWith( + color: refColorTertiary30, + fontFamily: 'monospace', + ), leading: Icons.notifications_outlined, onTap: controller.copyPublicKey, - leadingIconColor: LinagoraRefColors.material().tertiary[30], + leadingIconColor: refColorTertiary30, trailingWidget: InkWell( onTap: controller.copyPublicKey, child: const Icon(Icons.content_copy), @@ -196,25 +223,6 @@ class SettingsSecurityView extends StatelessWidget { ), ), }, - //TODO #1734: Remove dehydrate and delete account - // ListTile( - // leading: const Icon(Icons.tap_and_play), - // trailing: const Icon(Icons.chevron_right_outlined), - // title: Text( - // L10n.of(context)!.dehydrate, - // style: const TextStyle(color: Colors.red), - // ), - // onTap: controller.dehydrateAction, - // ), - // ListTile( - // leading: const Icon(Icons.delete_outlined), - // trailing: const Icon(Icons.chevron_right_outlined), - // title: Text( - // L10n.of(context)!.deleteAccount, - // style: const TextStyle(color: Colors.red), - // ), - // onTap: controller.deleteAccountAction, - // ), ], ), ), diff --git a/lib/resource/image_paths.dart b/lib/resource/image_paths.dart index 89d3042575..69988e5459 100644 --- a/lib/resource/image_paths.dart +++ b/lib/resource/image_paths.dart @@ -63,6 +63,8 @@ class ImagePaths { static String get icShieldLockFill => _getImagePath('ic_shield_lock_fill.svg'); + static String get icRecoveryKey => _getImagePath('ic_recovery_key.svg'); + static String get icBrandingPng => _getAssetPath('branding.png'); static String get lottieChat => _getAssetPath('lottie-chat.json'); static String get icGhost => _getImagePath('ic_ghost.svg'); From a624e0c7b5dc18f156a37ad27b3247f2473c765c Mon Sep 17 00:00:00 2001 From: Clement Guyon Date: Thu, 12 Mar 2026 13:08:12 +0100 Subject: [PATCH 2/3] refacto: settings, router code hygiene --- lib/config/go_routes/app_route_paths.dart | 53 +- lib/config/go_routes/go_router.dart | 32 +- lib/pages/chat_list/chat_list.dart | 3 +- lib/pages/contacts_tab/contacts_tab.dart | 3 +- .../multiple_accounts_picker.dart | 3 +- lib/pages/search/search.dart | 3 +- .../settings_dashboard/settings/settings.dart | 56 ++- .../settings/settings_item_builder.dart | 5 +- .../settings_3pid/settings_3pid.dart | 33 +- .../settings_3pid/settings_3pid_view.dart | 133 +++-- .../settings_app_language_view.dart | 23 +- .../settings_blocked_user.dart | 17 +- .../settings_blocked_users_view.dart | 42 +- .../settings_chat/settings_chat_view.dart | 35 +- .../settings_contacts_visibility.dart | 62 +-- .../settings_contacts_visibility_enum.dart | 19 +- .../settings_contacts_visibility_view.dart | 145 +++--- .../settings_emotes/settings_emotes.dart | 32 +- .../settings_emotes/settings_emotes_view.dart | 257 +++++----- .../settings_multiple_emotes_view.dart | 9 +- .../settings_notifications_view.dart | 52 +- .../settings_profile/settings_profile.dart | 40 +- ...settings_profile_context_menu_actions.dart | 15 +- .../settings_profile_item.dart | 35 +- .../settings_profile_item_style.dart | 7 +- ...tings_profile_redirection_edit_button.dart | 9 +- .../settings_profile_ui_state.dart | 3 - .../settings_profile_view.dart | 100 ++-- .../settings_profile_view_mobile.dart | 120 +++-- .../settings_profile_view_mobile_style.dart | 7 +- .../settings_profile_view_style.dart | 10 +- .../settings_profile_view_web.dart | 455 ++++++++++-------- .../settings_profile_view_web_style.dart | 23 +- .../settings_security/settings_security.dart | 35 +- .../settings_security_view.dart | 15 +- .../settings_stories/settings_stories.dart | 31 +- .../settings_stories_view.dart | 24 +- .../settings_style/settings_style.dart | 46 +- .../settings_style/settings_style_view.dart | 82 ++-- lib/pages/twake_welcome/twake_welcome.dart | 3 +- ...tive_scaffold_primary_navigation_view.dart | 3 +- 41 files changed, 1062 insertions(+), 1018 deletions(-) delete mode 100644 lib/pages/settings_dashboard/settings_profile/settings_profile_state/settings_profile_ui_state.dart diff --git a/lib/config/go_routes/app_route_paths.dart b/lib/config/go_routes/app_route_paths.dart index cfddc43fb8..3f82c912d3 100644 --- a/lib/config/go_routes/app_route_paths.dart +++ b/lib/config/go_routes/app_route_paths.dart @@ -3,9 +3,60 @@ /// This file contains all route paths used in go_router configuration /// and navigation calls to ensure consistency and prevent typos. abstract class AppRoutePaths { + // Settings prefix check + static const String roomsSettings = '/rooms/settings'; + + // Profile routes + static const String profileSegment = 'profile'; + static const String profileFull = '/rooms/$profileSegment'; + static const String profileQrSegment = 'qr'; + static const String profileQrFull = '$profileFull/$profileQrSegment'; + + // Chat settings routes + static const String chatSegment = 'chat'; + static const String chatFull = '/rooms/$chatSegment'; + static const String emotesSegment = 'emotes'; + static const String chatEmotesFull = '$chatFull/$emotesSegment'; + // Security routes - static const String roomsSecurityFull = '/rooms/security'; + static const String securitySegment = 'security'; + static const String roomsSecurityFull = '/rooms/$securitySegment'; static const String contactsVisibilitySegment = 'contactsVisibility'; static const String contactsVisibilityFull = '$roomsSecurityFull/$contactsVisibilitySegment'; + static const String storiesSegment = 'stories'; + static const String securityStoriesFull = + '$roomsSecurityFull/$storiesSegment'; + static const String blockedUsersSegment = 'blockedUsers'; + static const String securityBlockedUsersFull = + '$roomsSecurityFull/$blockedUsersSegment'; + static const String threePidSegment = '3pid'; + static const String securityThreePidFull = + '$roomsSecurityFull/$threePidSegment'; + + // Notifications routes + static const String notificationsSegment = 'notifications'; + static const String notificationsFull = '/rooms/$notificationsSegment'; + + // Style routes + static const String styleSegment = 'style'; + static const String styleFull = '/rooms/$styleSegment'; + + // App language routes + static const String appLanguageSegment = 'appLanguage'; + static const String appLanguageFull = '/rooms/$appLanguageSegment'; + + // Devices routes + static const String devicesSegment = 'devices'; + static const String devicesFull = '/rooms/$devicesSegment'; + + // Add account routes + static const String addAccountSegment = 'addaccount'; + static const String addAccountFull = '/rooms/$addAccountSegment'; + static const String addAccountLoginSegment = 'login'; + static const String addAccountLoginFull = + '$addAccountFull/$addAccountLoginSegment'; + static const String addAccountHomeserverPickerSegment = 'homeserverpicker'; + static const String addAccountHomeserverPickerFull = + '$addAccountFull/$addAccountHomeserverPickerSegment'; } diff --git a/lib/config/go_routes/go_router.dart b/lib/config/go_routes/go_router.dart index 8cb4c93530..9bc535dd51 100644 --- a/lib/config/go_routes/go_router.dart +++ b/lib/config/go_routes/go_router.dart @@ -143,7 +143,7 @@ abstract class AppRoutes { pageBuilder: (context, state, child) => defaultPageBuilder( context, !_responsive.isMobile(context) && - state.fullPath?.startsWith('/rooms/settings') == false + state.fullPath?.startsWith(AppRoutePaths.roomsSettings) == false ? AppAdaptiveScaffold( body: AppAdaptiveScaffoldBody( activeRoomId: state.pathParameters['roomid'], @@ -296,13 +296,13 @@ abstract class AppRoutes { }, ), GoRoute( - path: 'profile', + path: AppRoutePaths.profileSegment, pageBuilder: (context, state) => defaultPageBuilder(context, const SettingsProfile()), routes: [ if (PlatformInfos.isMobile) GoRoute( - path: 'qr', + path: AppRoutePaths.profileQrSegment, pageBuilder: (context, state) => defaultPageBuilder(context, const PersonalQr()), redirect: loggedOutRedirect, @@ -310,36 +310,36 @@ abstract class AppRoutes { ], ), GoRoute( - path: 'notifications', + path: AppRoutePaths.notificationsSegment, pageBuilder: (context, state) => defaultPageBuilder(context, const SettingsNotifications()), redirect: loggedOutRedirect, ), GoRoute( - path: 'style', + path: AppRoutePaths.styleSegment, pageBuilder: (context, state) => defaultPageBuilder(context, const SettingsStyle()), redirect: loggedOutRedirect, ), GoRoute( - path: 'devices', + path: AppRoutePaths.devicesSegment, pageBuilder: (context, state) => defaultPageBuilder(context, const DevicesSettings()), redirect: loggedOutRedirect, ), GoRoute( - path: 'appLanguage', + path: AppRoutePaths.appLanguageSegment, pageBuilder: (context, state) => defaultPageBuilder(context, const SettingsAppLanguage()), redirect: loggedOutRedirect, ), GoRoute( - path: 'chat', + path: AppRoutePaths.chatSegment, pageBuilder: (context, state) => defaultPageBuilder(context, const SettingsChat()), routes: [ GoRoute( - path: 'emotes', + path: AppRoutePaths.emotesSegment, pageBuilder: (context, state) => defaultPageBuilder(context, const EmotesSettings()), ), @@ -347,7 +347,7 @@ abstract class AppRoutes { redirect: loggedOutRedirect, ), GoRoute( - path: 'addaccount', + path: AppRoutePaths.addAccountSegment, redirect: loggedOutRedirect, pageBuilder: (context, state) => defaultPageBuilder( context, @@ -359,38 +359,38 @@ abstract class AppRoutes { ), routes: [ GoRoute( - path: 'login', + path: AppRoutePaths.addAccountLoginSegment, pageBuilder: (context, state) => defaultPageBuilder(context, const Login()), redirect: loggedOutRedirect, ), GoRoute( - path: 'homeserverpicker', + path: AppRoutePaths.addAccountHomeserverPickerSegment, pageBuilder: (context, state) => defaultPageBuilder(context, const HomeserverPicker()), ), ], ), GoRoute( - path: 'security', + path: AppRoutePaths.securitySegment, redirect: loggedOutRedirect, pageBuilder: (context, state) => defaultPageBuilder(context, const SettingsSecurity()), routes: [ GoRoute( - path: 'stories', + path: AppRoutePaths.storiesSegment, pageBuilder: (context, state) => defaultPageBuilder(context, const SettingsStories()), redirect: loggedOutRedirect, ), GoRoute( - path: 'blockedUsers', + path: AppRoutePaths.blockedUsersSegment, pageBuilder: (context, state) => defaultPageBuilder(context, const BlockedUsers()), redirect: loggedOutRedirect, ), GoRoute( - path: '3pid', + path: AppRoutePaths.threePidSegment, pageBuilder: (context, state) => defaultPageBuilder(context, const Settings3Pid()), redirect: loggedOutRedirect, diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index a0db3c9532..b3818eae5f 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:fluffychat/config/go_routes/app_route_paths.dart'; import 'package:collection/collection.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/first_column_inner_routes.dart'; @@ -741,7 +742,7 @@ class ChatListController extends State } void onClickAvatar() { - context.push('/rooms/profile'); + context.push(AppRoutePaths.profileFull); } void _handleRecovery() { diff --git a/lib/pages/contacts_tab/contacts_tab.dart b/lib/pages/contacts_tab/contacts_tab.dart index b1d558b869..bb364c238d 100644 --- a/lib/pages/contacts_tab/contacts_tab.dart +++ b/lib/pages/contacts_tab/contacts_tab.dart @@ -1,3 +1,4 @@ +import 'package:fluffychat/config/go_routes/app_route_paths.dart'; import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/presentation/mixins/address_book_mixin.dart'; import 'package:fluffychat/presentation/mixins/comparable_presentation_contact_mixin.dart'; @@ -89,7 +90,7 @@ class ContactsTabController extends State } void goToSettingsProfile() { - context.go('/rooms/profile'); + context.go(AppRoutePaths.profileFull); } void goToDraftChat({ diff --git a/lib/pages/multiple_accounts/multiple_accounts_picker.dart b/lib/pages/multiple_accounts/multiple_accounts_picker.dart index 4c8f5da74e..b6f518be23 100644 --- a/lib/pages/multiple_accounts/multiple_accounts_picker.dart +++ b/lib/pages/multiple_accounts/multiple_accounts_picker.dart @@ -1,4 +1,5 @@ import 'package:collection/collection.dart'; +import 'package:fluffychat/config/go_routes/app_route_paths.dart'; import 'package:fluffychat/pages/twake_welcome/twake_welcome.dart'; import 'package:fluffychat/presentation/multiple_account/twake_chat_presentation_account.dart'; import 'package:fluffychat/utils/dialog/twake_dialog.dart'; @@ -79,7 +80,7 @@ class MultipleAccountsPickerController { void _onAddAnotherAccount() { context.push( - '/rooms/addaccount', + AppRoutePaths.addAccountFull, extra: const TwakeWelcomeArg(twakeIdType: TwakeWelcomeType.otherAccounts), ); } diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index a182004730..d255fa00cd 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -1,4 +1,5 @@ import 'package:dartz/dartz.dart' hide State; +import 'package:fluffychat/config/go_routes/app_route_paths.dart'; import 'package:fluffychat/app_state/failure.dart'; import 'package:fluffychat/app_state/success.dart'; import 'package:fluffychat/di/global/get_it_initializer.dart'; @@ -103,7 +104,7 @@ class SearchController extends State with WidgetsBindingObserver { } void goToSettingsProfile() { - context.go('/rooms/profile'); + context.go(AppRoutePaths.profileFull); } void onContactTap(ContactPresentationSearch contactPresentationSearch) { diff --git a/lib/pages/settings_dashboard/settings/settings.dart b/lib/pages/settings_dashboard/settings/settings.dart index d6066aeb39..c8e36a43fe 100644 --- a/lib/pages/settings_dashboard/settings/settings.dart +++ b/lib/pages/settings_dashboard/settings/settings.dart @@ -1,26 +1,25 @@ import 'dart:async'; +import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/go_routes/app_route_paths.dart'; import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/domain/model/extensions/common_settings/common_settings_extensions.dart'; import 'package:fluffychat/domain/model/extensions/homeserver_summary_extensions.dart'; import 'package:fluffychat/domain/repository/federation_configurations_repository.dart'; import 'package:fluffychat/domain/repository/tom_configurations_repository.dart'; import 'package:fluffychat/event/twake_inapp_event_types.dart'; +import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:fluffychat/pages/bootstrap/bootstrap_dialog.dart'; -import 'package:fluffychat/presentation/mixins/connect_page_mixin.dart'; import 'package:fluffychat/presentation/enum/settings/settings_enum.dart'; import 'package:fluffychat/presentation/extensions/client_extension.dart'; +import 'package:fluffychat/presentation/mixins/connect_page_mixin.dart'; import 'package:fluffychat/utils/dialog/twake_dialog.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/url_launcher.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/twake_app.dart'; import 'package:flutter/material.dart'; - -import 'package:adaptive_dialog/adaptive_dialog.dart'; -import 'package:fluffychat/generated/l10n/app_localizations.dart'; - import 'package:go_router/go_router.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; import 'package:matrix/matrix.dart'; @@ -81,13 +80,14 @@ class SettingsController extends State with ConnectPageMixin { if (twakeContext == null) { Logs().e('SettingsController()::logoutAction - Twake context is null'); } + final l10n = L10n.of(context)!; if (await showConfirmAlertDialog( useRootNavigator: false, context: twakeContext!, - title: L10n.of(context)!.areYouSureYouWantToLogout, - message: L10n.of(context)!.logoutDialogWarning, - okLabel: L10n.of(context)!.logout, - cancelLabel: L10n.of(context)!.cancel, + title: l10n.areYouSureYouWantToLogout, + message: l10n.logoutDialogWarning, + okLabel: l10n.logout, + cancelLabel: l10n.cancel, ) == ConfirmResult.cancel) { return; @@ -199,12 +199,13 @@ class SettingsController extends State with ConnectPageMixin { } void firstRunBootstrapAction([_]) async { + final l10n = L10n.of(context)!; if (showChatBackupSwitch.value != true) { showOkAlertDialog( context: context, - title: L10n.of(context)!.chatBackup, - message: L10n.of(context)!.onlineKeyBackupEnabled, - okLabel: L10n.of(context)!.close, + title: l10n.chatBackup, + message: l10n.onlineKeyBackupEnabled, + okLabel: l10n.close, ); return; } @@ -214,7 +215,7 @@ class SettingsController extends State with ConnectPageMixin { void goToSettingsProfile() async { optionsSelectNotifier.value = SettingEnum.profile; - final result = await context.push('/rooms/profile'); + final result = await context.push(AppRoutePaths.profileFull); if (result == null) { optionsSelectNotifier.value = null; } @@ -224,19 +225,19 @@ class SettingsController extends State with ConnectPageMixin { optionsSelectNotifier.value = settingEnum; switch (settingEnum) { case SettingEnum.chatSettings: - final result = await context.push('/rooms/chat'); + final result = await context.push(AppRoutePaths.chatFull); if (result == null) { optionsSelectNotifier.value = null; } break; case SettingEnum.privacyAndSecurity: - final result = await context.push('/rooms/security'); + final result = await context.push(AppRoutePaths.roomsSecurityFull); if (result == null) { optionsSelectNotifier.value = null; } break; case SettingEnum.notificationAndSounds: - final result = await context.push('/rooms/notifications'); + final result = await context.push(AppRoutePaths.notificationsFull); if (result == null) { optionsSelectNotifier.value = null; } @@ -244,13 +245,13 @@ class SettingsController extends State with ConnectPageMixin { case SettingEnum.chatFolders: break; case SettingEnum.appLanguage: - final result = await context.push('/rooms/appLanguage'); + final result = await context.push(AppRoutePaths.appLanguageFull); if (result == null) { optionsSelectNotifier.value = null; } break; case SettingEnum.devices: - final result = await context.push('/rooms/devices'); + final result = await context.push(AppRoutePaths.devicesFull); if (result == null) { optionsSelectNotifier.value = null; } @@ -277,17 +278,22 @@ class SettingsController extends State with ConnectPageMixin { launchUrl(Uri.parse(commonSettingsUrl), webOnlyWindowName: '_blank'); return; } + + final l10n = L10n.of(context)!; + final sysColor = LinagoraSysColors.material(); + final primaryColor = sysColor.primary; + if (await showConfirmAlertDialog( useRootNavigator: false, context: context, - title: L10n.of(context)!.areYouSureYouWantToDeleteAccount, - message: L10n.of(context)!.deleteAccountMessage, - okLabel: L10n.of(context)!.continueProcess, + title: l10n.areYouSureYouWantToDeleteAccount, + message: l10n.deleteAccountMessage, + okLabel: l10n.continueProcess, okLabelButtonColor: Colors.transparent, - okTextColor: LinagoraSysColors.material().primary, - cancelLabel: L10n.of(context)!.deleteLater, - cancelLabelButtonColor: LinagoraSysColors.material().primary, - cancelTextColor: LinagoraSysColors.material().onPrimary, + okTextColor: primaryColor, + cancelLabel: l10n.deleteLater, + cancelLabelButtonColor: primaryColor, + cancelTextColor: sysColor.onPrimary, ) == ConfirmResult.cancel) { return; diff --git a/lib/pages/settings_dashboard/settings/settings_item_builder.dart b/lib/pages/settings_dashboard/settings/settings_item_builder.dart index e4a870e7fd..7a5bed2836 100644 --- a/lib/pages/settings_dashboard/settings/settings_item_builder.dart +++ b/lib/pages/settings_dashboard/settings/settings_item_builder.dart @@ -38,6 +38,7 @@ class SettingsItemBuilder extends StatelessWidget { Widget build(BuildContext context) { final textStyle = LinagoraTextStyle.material(); final tertiary30 = LinagoraRefColors.material().tertiary[30]; + const fontFamily = 'Inter'; return TwakeInkWell( isSelected: isSelected, @@ -73,7 +74,7 @@ class SettingsItemBuilder extends StatelessWidget { title, style: textStyle.bodyMedium2.copyWith( color: titleColor, - fontFamily: 'Inter', + fontFamily: fontFamily, ), maxLines: 2, overflow: .ellipsis, @@ -87,7 +88,7 @@ class SettingsItemBuilder extends StatelessWidget { subtitleStyle ?? textStyle.bodyMedium.copyWith( color: subtitleColor ?? tertiary30, - fontFamily: 'Inter', + fontFamily: fontFamily, ), overflow: .ellipsis, ), diff --git a/lib/pages/settings_dashboard/settings_3pid/settings_3pid.dart b/lib/pages/settings_dashboard/settings_3pid/settings_3pid.dart index 59419da85f..5bdc6d222f 100644 --- a/lib/pages/settings_dashboard/settings_3pid/settings_3pid.dart +++ b/lib/pages/settings_dashboard/settings_3pid/settings_3pid.dart @@ -1,12 +1,10 @@ -import 'package:fluffychat/utils/dialog/twake_dialog.dart'; -import 'package:flutter/material.dart'; - import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:fluffychat/generated/l10n/app_localizations.dart'; - +import 'package:fluffychat/utils/dialog/twake_dialog.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/widgets/matrix.dart'; import 'settings_3pid_view.dart'; class Settings3Pid extends StatefulWidget { @@ -20,16 +18,18 @@ class Settings3Pid extends StatefulWidget { class Settings3PidController extends State { void add3PidAction() async { + final l10n = L10n.of(context)!; + final input = await showTextInputDialog( useRootNavigator: false, context: context, - title: L10n.of(context)!.enterAnEmailAddress, - okLabel: L10n.of(context)!.ok, - cancelLabel: L10n.of(context)!.cancel, + title: l10n.enterAnEmailAddress, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, textFields: [ DialogTextField( - hintText: L10n.of(context)!.enterAnEmailAddress, - keyboardType: TextInputType.emailAddress, + hintText: l10n.enterAnEmailAddress, + keyboardType: .emailAddress, ), ], ); @@ -46,9 +46,9 @@ class Settings3PidController extends State { final ok = await showOkAlertDialog( useRootNavigator: false, context: context, - title: L10n.of(context)!.weSentYouAnEmail, - message: L10n.of(context)!.pleaseClickOnLink, - okLabel: L10n.of(context)!.iHaveClickedOnLink, + title: l10n.weSentYouAnEmail, + message: l10n.pleaseClickOnLink, + okLabel: l10n.iHaveClickedOnLink, ); if (ok != OkCancelResult.ok) return; final success = await TwakeDialog.showFutureLoadingDialogFullScreen( @@ -65,12 +65,13 @@ class Settings3PidController extends State { Future?>? request; void delete3Pid(ThirdPartyIdentifier identifier) async { + final l10n = L10n.of(context)!; if (await showOkCancelAlertDialog( useRootNavigator: false, context: context, - title: L10n.of(context)!.areYouSure, - okLabel: L10n.of(context)!.yes, - cancelLabel: L10n.of(context)!.cancel, + title: l10n.areYouSure, + okLabel: l10n.yes, + cancelLabel: l10n.cancel, ) != OkCancelResult.ok) { return; diff --git a/lib/pages/settings_dashboard/settings_3pid/settings_3pid_view.dart b/lib/pages/settings_dashboard/settings_3pid/settings_3pid_view.dart index 98ecf97751..707b6803e5 100644 --- a/lib/pages/settings_dashboard/settings_3pid/settings_3pid_view.dart +++ b/lib/pages/settings_dashboard/settings_3pid/settings_3pid_view.dart @@ -1,14 +1,12 @@ +import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_3pid/settings_3pid.dart'; import 'package:fluffychat/widgets/app_bars/twake_app_bar.dart'; +import 'package:fluffychat/widgets/layouts/max_width_body.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; - -import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/widgets/layouts/max_width_body.dart'; -import 'package:fluffychat/widgets/matrix.dart'; - class Settings3PidView extends StatelessWidget { final Settings3PidController controller; @@ -17,15 +15,18 @@ class Settings3PidView extends StatelessWidget { @override Widget build(BuildContext context) { controller.request ??= Matrix.of(context).client.getAccount3PIDs(); + final l10n = L10n.of(context)!; + final bgColor = Theme.of(context).scaffoldBackgroundColor; + return Scaffold( backgroundColor: LinagoraSysColors.material().onPrimary, appBar: TwakeAppBar( - title: L10n.of(context)!.passwordRecovery, + title: l10n.passwordRecovery, actions: [ IconButton( icon: const Icon(Icons.add_outlined), onPressed: controller.add3PidAction, - tooltip: L10n.of(context)!.addEmail, + tooltip: l10n.addEmail, ), ], context: context, @@ -33,75 +34,61 @@ class Settings3PidView extends StatelessWidget { body: MaxWidthBody( child: FutureBuilder?>( future: controller.request, - builder: - ( - BuildContext context, - AsyncSnapshot?> snapshot, - ) { - if (snapshot.hasError) { - return Center( - child: Text( - snapshot.error.toString(), - textAlign: TextAlign.center, + builder: (_, AsyncSnapshot?> snapshot) { + if (snapshot.hasError) { + return Center( + child: Text(snapshot.error.toString(), textAlign: .center), + ); + } + if (!snapshot.hasData) { + return const Center( + child: CircularProgressIndicator.adaptive(strokeWidth: 2), + ); + } + final identifier = snapshot.data!; + return Column( + children: [ + ListTile( + leading: CircleAvatar( + backgroundColor: bgColor, + foregroundColor: identifier.isEmpty + ? Colors.orange + : Colors.grey, + child: Icon( + identifier.isEmpty + ? Icons.warning_outlined + : Icons.info_outlined, ), - ); - } - if (!snapshot.hasData) { - return const Center( - child: CircularProgressIndicator.adaptive(strokeWidth: 2), - ); - } - final identifier = snapshot.data!; - return Column( - children: [ - ListTile( + ), + title: Text( + identifier.isEmpty + ? l10n.noPasswordRecoveryDescription + : l10n.withTheseAddressesRecoveryDescription, + ), + ), + const Divider(height: 1), + Expanded( + child: ListView.builder( + itemCount: identifier.length, + itemBuilder: (_, int i) => ListTile( leading: CircleAvatar( - backgroundColor: Theme.of( - context, - ).scaffoldBackgroundColor, - foregroundColor: identifier.isEmpty - ? Colors.orange - : Colors.grey, - child: Icon( - identifier.isEmpty - ? Icons.warning_outlined - : Icons.info_outlined, - ), + backgroundColor: bgColor, + foregroundColor: Colors.grey, + child: Icon(identifier[i].iconData), ), - title: Text( - identifier.isEmpty - ? L10n.of(context)!.noPasswordRecoveryDescription - : L10n.of( - context, - )!.withTheseAddressesRecoveryDescription, - ), - ), - const Divider(height: 1), - Expanded( - child: ListView.builder( - itemCount: identifier.length, - itemBuilder: (BuildContext context, int i) => ListTile( - leading: CircleAvatar( - backgroundColor: Theme.of( - context, - ).scaffoldBackgroundColor, - foregroundColor: Colors.grey, - child: Icon(identifier[i].iconData), - ), - title: Text(identifier[i].address), - trailing: IconButton( - tooltip: L10n.of(context)!.delete, - icon: const Icon(Icons.delete_forever_outlined), - color: Colors.red, - onPressed: () => - controller.delete3Pid(identifier[i]), - ), - ), + title: Text(identifier[i].address), + trailing: IconButton( + tooltip: l10n.delete, + icon: const Icon(Icons.delete_forever_outlined), + color: Colors.red, + onPressed: () => controller.delete3Pid(identifier[i]), ), ), - ], - ); - }, + ), + ), + ], + ); + }, ), ), ); @@ -111,9 +98,9 @@ class Settings3PidView extends StatelessWidget { extension on ThirdPartyIdentifier { IconData get iconData { switch (medium) { - case ThirdPartyIdentifierMedium.email: + case .email: return Icons.mail_outline_rounded; - case ThirdPartyIdentifierMedium.msisdn: + case .msisdn: return Icons.phone_android_outlined; } } diff --git a/lib/pages/settings_dashboard/settings_app_language/settings_app_language_view.dart b/lib/pages/settings_dashboard/settings_app_language/settings_app_language_view.dart index b3e1c22868..21beab6d40 100644 --- a/lib/pages/settings_dashboard/settings_app_language/settings_app_language_view.dart +++ b/lib/pages/settings_dashboard/settings_app_language/settings_app_language_view.dart @@ -1,4 +1,5 @@ import 'package:fluffychat/di/global/get_it_initializer.dart'; +import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_app_language/settings_app_language.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_app_language/settings_app_language_view_style.dart'; import 'package:fluffychat/presentation/extensions/localizations/locale_extension.dart'; @@ -7,7 +8,6 @@ import 'package:fluffychat/utils/responsive/responsive_utils.dart'; import 'package:fluffychat/widgets/app_bars/twake_app_bar.dart'; import 'package:fluffychat/widgets/app_bars/twake_app_bar_style.dart'; import 'package:flutter/material.dart'; -import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:go_router/go_router.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; @@ -19,18 +19,21 @@ class SettingsAppLanguageView extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = L10n.of(context)!; + final textTheme = Theme.of(context).textTheme; + return Scaffold( backgroundColor: LinagoraSysColors.material().onPrimary, appBar: TwakeAppBar( - title: L10n.of(context)!.appLanguage, + title: l10n.appLanguage, context: context, leading: responsiveUtils.isMobile(context) ? Padding( padding: TwakeAppBarStyle.leadingIconPadding, child: IconButton( - tooltip: L10n.of(context)!.back, + tooltip: l10n.back, icon: const Icon(Icons.arrow_back_ios), - onPressed: () => context.pop(), + onPressed: context.pop, iconSize: TwakeAppBarStyle.leadingIconSize, ), ) @@ -42,15 +45,15 @@ class SettingsAppLanguageView extends StatelessWidget { padding: SettingsAppLanguageViewStyle.paddingBody, physics: const ClampingScrollPhysics(), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: .start, children: [ ListView.separated( padding: SettingsAppLanguageViewStyle.paddingListItems, shrinkWrap: true, - itemBuilder: (context, index) { + itemBuilder: (_, int index) { return ValueListenableBuilder( valueListenable: controller.currentLocale, - builder: (context, locale, child) { + builder: (context, locale, _) { return ListTile( splashColor: Colors.transparent, shape: RoundedRectangleBorder( @@ -62,12 +65,12 @@ class SettingsAppLanguageView extends StatelessWidget { controller.supportedLocales[index] .getLanguageNameByCurrentLocale(context) .capitalize(), - style: Theme.of(context).textTheme.titleMedium, + style: textTheme.titleMedium, ), subtitle: Text( controller.supportedLocales[index] .getSourceLanguageName(), - style: Theme.of(context).textTheme.bodySmall!.copyWith( + style: textTheme.bodySmall!.copyWith( color: LinagoraRefColors.material().neutral[40], ), ), @@ -89,7 +92,7 @@ class SettingsAppLanguageView extends StatelessWidget { }, ); }, - separatorBuilder: (context, index) => const Divider(), + separatorBuilder: (_, _) => const Divider(), itemCount: controller.supportedLocales.length, ), ], diff --git a/lib/pages/settings_dashboard/settings_blocked_users/settings_blocked_user.dart b/lib/pages/settings_dashboard/settings_blocked_users/settings_blocked_user.dart index 50af4cc6a6..61093c8f51 100644 --- a/lib/pages/settings_dashboard/settings_blocked_users/settings_blocked_user.dart +++ b/lib/pages/settings_dashboard/settings_blocked_users/settings_blocked_user.dart @@ -1,5 +1,9 @@ import 'dart:async'; +import 'package:dartz/dartz.dart' hide State; +import 'package:fluffychat/app_state/failure.dart'; +import 'package:fluffychat/app_state/success.dart'; +import 'package:fluffychat/config/go_routes/app_route_paths.dart'; import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/pages/search/search_debouncer_mixin.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_blocked_users/settings_blocked_users_search_state.dart'; @@ -8,11 +12,9 @@ import 'package:fluffychat/utils/dialog/twake_dialog.dart'; import 'package:fluffychat/utils/responsive/responsive_utils.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; -import 'package:dartz/dartz.dart' hide State; -import 'package:fluffychat/app_state/failure.dart'; -import 'package:fluffychat/app_state/success.dart'; import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; + import 'settings_blocked_users_view.dart'; class BlockedUsers extends StatefulWidget { @@ -36,15 +38,14 @@ class SettingsIgnoreListController extends State final inputFocus = FocusNode(); - final ValueNotifier> searchUserResults = - ValueNotifier>( - Right(BlockedUsersSearchInitialState()), - ); + final searchUserResults = ValueNotifier>( + Right(BlockedUsersSearchInitialState()), + ); final List blockedUsers = []; void onBack() { - context.go('/rooms/security'); + context.go(AppRoutePaths.roomsSecurityFull); } Future initialBlockedUsers() async { diff --git a/lib/pages/settings_dashboard/settings_blocked_users/settings_blocked_users_view.dart b/lib/pages/settings_dashboard/settings_blocked_users/settings_blocked_users_view.dart index 66cf28cd53..9533e14d01 100644 --- a/lib/pages/settings_dashboard/settings_blocked_users/settings_blocked_users_view.dart +++ b/lib/pages/settings_dashboard/settings_blocked_users/settings_blocked_users_view.dart @@ -1,3 +1,4 @@ +import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:fluffychat/pages/chat_list/chat_list_header_style.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_blocked_users/settings_blocked_users_search_state.dart'; import 'package:fluffychat/presentation/model/contact/presentation_contact_constant.dart'; @@ -10,10 +11,10 @@ import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/search/empty_search_widget.dart'; import 'package:fluffychat/widgets/twake_components/twake_icon_button.dart'; import 'package:flutter/material.dart'; -import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:go_router/go_router.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; import 'package:matrix/matrix.dart'; + import 'settings_blocked_user.dart'; class SettingsBlockedUsersView extends StatelessWidget { @@ -23,11 +24,14 @@ class SettingsBlockedUsersView extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = L10n.of(context)!; + final tertiaryColor = LinagoraSysColors.material().tertiary; + return Scaffold( backgroundColor: LinagoraSysColors.material().onPrimary, resizeToAvoidBottomInset: false, appBar: TwakeAppBar( - title: L10n.of(context)!.blockedUsers, + title: l10n.blockedUsers, centerTitle: true, withDivider: true, context: context, @@ -48,27 +52,25 @@ class SettingsBlockedUsersView extends StatelessWidget { child: Column( children: [ Padding( - padding: const EdgeInsets.all(16.0), + padding: const .all(16.0), child: TextField( controller: controller.textEditingController, contextMenuBuilder: mobileTwakeContextMenuBuilder, focusNode: controller.inputFocus, - textInputAction: TextInputAction.search, + textInputAction: .search, autofocus: true, decoration: ChatListHeaderStyle.searchInputDecoration( context, - prefixIconColor: LinagoraSysColors.material().tertiary, + prefixIconColor: tertiaryColor, ).copyWith( - hintStyle: Theme.of(context).textTheme.titleMedium - ?.copyWith( - color: LinagoraSysColors.material().tertiary, - ), - hintText: L10n.of(context)!.enterAnEmailAddress, + hintStyle: Theme.of( + context, + ).textTheme.titleMedium?.copyWith(color: tertiaryColor), + hintText: l10n.enterAnEmailAddress, suffixIcon: ValueListenableBuilder( valueListenable: controller.textEditingController, - builder: (context, value, child) => - value.text.isNotEmpty + builder: (_, value, _) => value.text.isNotEmpty ? IconButton( onPressed: () { controller.textEditingController.clear(); @@ -82,7 +84,7 @@ class SettingsBlockedUsersView extends StatelessWidget { ), ValueListenableBuilder( valueListenable: controller.searchUserResults, - builder: (context, searchResults, child) { + builder: (_, searchResults, _) { return searchResults.fold( (failure) { if (failure is BlockedUsersSearchEmptyState) { @@ -96,7 +98,7 @@ class SettingsBlockedUsersView extends StatelessWidget { shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: success.blockedUsers.length, - padding: const EdgeInsets.symmetric(horizontal: 12.0), + padding: const .symmetric(horizontal: 12.0), itemBuilder: (context, index) { final profile = success.blockedUsers[index]; @@ -130,11 +132,11 @@ class SettingsBlockedUsersView extends StatelessWidget { }) { return TwakeInkWell( onTap: () { - final roomId = Matrix.of( - context, - ).client.getDirectChatFromUserId(profile.userId); + final client = Matrix.of(context).client; + final roomId = client.getDirectChatFromUserId(profile.userId); + if (roomId == null) { - if (profile.userId != Matrix.of(context).client.userID) { + if (profile.userId != client.userID) { Router.neglect( context, () => context.go( @@ -157,7 +159,7 @@ class SettingsBlockedUsersView extends StatelessWidget { } }, child: TwakeListItem( - padding: const EdgeInsets.all(8), + padding: const .all(8), child: Row( children: [ Avatar( @@ -167,7 +169,7 @@ class SettingsBlockedUsersView extends StatelessWidget { const SizedBox(width: 8.0), Expanded( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: .start, children: [ Row( children: [ diff --git a/lib/pages/settings_dashboard/settings_chat/settings_chat_view.dart b/lib/pages/settings_dashboard/settings_chat/settings_chat_view.dart index f30dfb2687..4f02618b88 100644 --- a/lib/pages/settings_dashboard/settings_chat/settings_chat_view.dart +++ b/lib/pages/settings_dashboard/settings_chat/settings_chat_view.dart @@ -1,7 +1,9 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/di/global/get_it_initializer.dart'; +import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/utils/responsive/responsive_utils.dart'; import 'package:fluffychat/widgets/app_bars/twake_app_bar.dart'; import 'package:fluffychat/widgets/app_bars/twake_app_bar_style.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; @@ -9,10 +11,8 @@ import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/settings_switch_list_tile.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:go_router/go_router.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; -import 'package:fluffychat/utils/responsive/responsive_utils.dart'; import 'settings_chat.dart'; @@ -23,10 +23,13 @@ class SettingsChatView extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = L10n.of(context)!; + final isWebRTCSupported = Matrix.of(context).webrtcIsSupported; + return Scaffold( backgroundColor: LinagoraSysColors.material().onPrimary, appBar: TwakeAppBar( - title: L10n.of(context)!.chat, + title: l10n.chat, context: context, withDivider: true, centerTitle: true, @@ -34,7 +37,7 @@ class SettingsChatView extends StatelessWidget { ? Padding( padding: TwakeAppBarStyle.leadingIconPadding, child: IconButton( - tooltip: L10n.of(context)!.back, + tooltip: l10n.back, icon: const Icon(Icons.arrow_back_ios), onPressed: () => context.pop(), iconSize: TwakeAppBarStyle.leadingIconSize, @@ -49,39 +52,39 @@ class SettingsChatView extends StatelessWidget { child: Column( children: [ SettingsSwitchListTile.adaptive( - title: L10n.of(context)!.renderRichContent, + title: l10n.renderRichContent, onChanged: (b) => AppConfig.renderHtml = b, storeKey: SettingKeys.renderHtml, defaultValue: AppConfig.renderHtml, ), SettingsSwitchListTile.adaptive( - title: L10n.of(context)!.hideRedactedEvents, + title: l10n.hideRedactedEvents, onChanged: (b) => AppConfig.hideRedactedEvents = b, storeKey: SettingKeys.hideRedactedEvents, defaultValue: AppConfig.hideRedactedEvents, ), SettingsSwitchListTile.adaptive( - title: L10n.of(context)!.hideUnknownEvents, + title: l10n.hideUnknownEvents, onChanged: (b) => AppConfig.hideUnknownEvents = b, storeKey: SettingKeys.hideUnknownEvents, defaultValue: AppConfig.hideUnknownEvents, ), SettingsSwitchListTile.adaptive( - title: L10n.of(context)!.hideUnimportantStateEvents, + title: l10n.hideUnimportantStateEvents, onChanged: (b) => AppConfig.hideUnimportantStateEvents = b, storeKey: SettingKeys.hideUnimportantStateEvents, defaultValue: AppConfig.hideUnimportantStateEvents, ), if (PlatformInfos.isMobile) SettingsSwitchListTile.adaptive( - title: L10n.of(context)!.autoplayImages, + title: l10n.autoplayImages, onChanged: (b) => AppConfig.autoplayImages = b, storeKey: SettingKeys.autoplayImages, defaultValue: AppConfig.autoplayImages, ), if (!responsive.isMobile(context)) SettingsSwitchListTile.adaptive( - title: L10n.of(context)!.enableRightAndLeftMessageAlignment, + title: l10n.enableRightAndLeftMessageAlignment, onChanged: (value) => AppConfig.enableRightAndLeftMessageAlignmentOnWeb = value, storeKey: SettingKeys.enableRightAndLeftMessageAlignmentOnWeb, @@ -89,9 +92,9 @@ class SettingsChatView extends StatelessWidget { AppConfig.enableRightAndLeftMessageAlignmentOnWeb, ), const Divider(), - if (Matrix.of(context).webrtcIsSupported) + if (isWebRTCSupported) SettingsSwitchListTile.adaptive( - title: L10n.of(context)!.experimentalVideoCalls, + title: l10n.experimentalVideoCalls, onChanged: (b) { AppConfig.experimentalVoip = b; Matrix.of(context).createVoipPlugin(); @@ -100,13 +103,11 @@ class SettingsChatView extends StatelessWidget { storeKey: SettingKeys.experimentalVoip, defaultValue: AppConfig.experimentalVoip, ), - if (Matrix.of(context).webrtcIsSupported && !kIsWeb) + if (isWebRTCSupported && !kIsWeb) ListTile( - title: Text(L10n.of(context)!.callingPermissions), - // onTap: () => - // CallKeepManager().checkoutPhoneAccountSetting(context), + title: Text(l10n.callingPermissions), trailing: const Padding( - padding: EdgeInsets.all(16.0), + padding: .all(16.0), child: Icon(Icons.call), ), ), diff --git a/lib/pages/settings_dashboard/settings_contacts_visibility/settings_contacts_visibility.dart b/lib/pages/settings_dashboard/settings_contacts_visibility/settings_contacts_visibility.dart index 5dcc147f4f..33835dbe6d 100644 --- a/lib/pages/settings_dashboard/settings_contacts_visibility/settings_contacts_visibility.dart +++ b/lib/pages/settings_dashboard/settings_contacts_visibility/settings_contacts_visibility.dart @@ -1,7 +1,9 @@ import 'dart:async'; + import 'package:dartz/dartz.dart' hide State; import 'package:fluffychat/app_state/failure.dart'; import 'package:fluffychat/app_state/success.dart'; +import 'package:fluffychat/config/go_routes/app_route_paths.dart'; import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/domain/app_state/user_info/get_user_info_visibility_state.dart'; import 'package:fluffychat/domain/app_state/user_info/update_user_info_visibility_state.dart'; @@ -9,11 +11,11 @@ import 'package:fluffychat/domain/model/user_info/user_info_visibility.dart'; import 'package:fluffychat/domain/model/user_info/user_info_visibility_request.dart'; import 'package:fluffychat/domain/usecase/user_info/get_user_info_visibility_interactor.dart'; import 'package:fluffychat/domain/usecase/user_info/update_user_info_visibility_interactor.dart'; +import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_contacts_visibility/settings_contacts_visibility_enum.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_contacts_visibility/settings_contacts_visibility_view.dart'; import 'package:fluffychat/utils/dialog/twake_dialog.dart'; import 'package:fluffychat/utils/twake_snackbar.dart'; -import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -32,53 +34,41 @@ class SettingsContactsVisibilityController Client get client => Matrix.read(context).client; void onBack() { - context.go('/rooms/security'); + context.go(AppRoutePaths.roomsSecurityFull); } StreamSubscription? getUserInfoVisibilityStreamSub; StreamSubscription? updateUserInfoVisibilityStreamSub; - final getUserInfoVisibilityInteractor = getIt - .get(); - - final updateUserInfoVisibilityInteractor = getIt - .get(); + final getUserInfoVisibilityNotifier = ValueNotifier>( + Right(GettingUserInfoVisibility()), + ); - final ValueNotifier> getUserInfoVisibilityNotifier = + final updateUserInfoVisibilityNotifier = ValueNotifier>( - Right(GettingUserInfoVisibility()), + Right(UpdatingUserInfoVisibility()), ); - final ValueNotifier> - updateUserInfoVisibilityNotifier = ValueNotifier>( - Right(UpdatingUserInfoVisibility()), - ); - - final List visibilityOptions = [ + final visibilityOptions = [ SettingsContactsVisibilityEnum.public, SettingsContactsVisibilityEnum.contacts, SettingsContactsVisibilityEnum.private, ]; - final List visibleFieldsOptions = [ - VisibleEnum.phone, - VisibleEnum.email, - ]; + final visibleFieldsOptions = [VisibleEnum.phone, VisibleEnum.email]; - final ValueNotifier - selectedVisibilityOptionNotifier = + final selectedVisibilityOptionNotifier = ValueNotifier(null); - final ValueNotifier> selectedVisibleFieldNotifier = - ValueNotifier>([]); + final selectedVisibleFieldNotifier = ValueNotifier>([]); void onSelectVisibilityOption(SettingsContactsVisibilityEnum option) { if (option == selectedVisibilityOptionNotifier.value) { return; } switch (option) { - case SettingsContactsVisibilityEnum.private: + case .private: updateUserInfoVisibility( userInfoVisibility: UserInfoVisibilityRequest( visibility: option.name, @@ -86,8 +76,8 @@ class SettingsContactsVisibilityController ), ); break; - case SettingsContactsVisibilityEnum.public: - case SettingsContactsVisibilityEnum.contacts: + case .public: + case .contacts: updateUserInfoVisibility( userInfoVisibility: UserInfoVisibilityRequest( visibility: option.name, @@ -120,15 +110,14 @@ class SettingsContactsVisibilityController void updateUserInfoVisibility({ required UserInfoVisibilityRequest userInfoVisibility, }) { + final l10n = L10n.of(context)!; if (client.userID == null) { - TwakeSnackBar.show( - context, - L10n.of(context)!.failedToChangeContactsVisibility, - ); + TwakeSnackBar.show(context, l10n.failedToChangeContactsVisibility); return; } updateUserInfoVisibilityStreamSub?.cancel(); - updateUserInfoVisibilityStreamSub = updateUserInfoVisibilityInteractor + updateUserInfoVisibilityStreamSub = getIt + .get() .execute(userId: client.userID!, body: userInfoVisibility) .listen((either) { if (!mounted) return; @@ -140,7 +129,7 @@ class SettingsContactsVisibilityController TwakeDialog.hideLoadingDialog(context); TwakeSnackBar.show( context, - L10n.of(context)!.failedToChangeContactsVisibility, + l10n.failedToChangeContactsVisibility, ); } }, @@ -150,7 +139,7 @@ class SettingsContactsVisibilityController SettingsContactsVisibilityEnum.values.firstWhere( (option) => option.name == success.userInfoVisibility.visibility, - orElse: () => SettingsContactsVisibilityEnum.contacts, + orElse: () => .contacts, ); selectedVisibleFieldNotifier.value = success.userInfoVisibility.visibleFields ?? []; @@ -170,7 +159,8 @@ class SettingsContactsVisibilityController return; } getUserInfoVisibilityStreamSub?.cancel(); - getUserInfoVisibilityStreamSub = getUserInfoVisibilityInteractor + getUserInfoVisibilityStreamSub = getIt + .get() .execute(userId: client.userID!) .listen((either) { if (!mounted) return; @@ -196,7 +186,7 @@ class SettingsContactsVisibilityController SettingsContactsVisibilityEnum.values.firstWhere( (option) => option.name == success.userInfoVisibility.visibility, - orElse: () => SettingsContactsVisibilityEnum.contacts, + orElse: () => .contacts, ); selectedVisibleFieldNotifier.value = success.userInfoVisibility.visibleFields ?? []; @@ -225,7 +215,7 @@ class SettingsContactsVisibilityController } @override - Widget build(BuildContext context) { + Widget build(_) { return SettingsContactsVisibilityView(controller: this); } } diff --git a/lib/pages/settings_dashboard/settings_contacts_visibility/settings_contacts_visibility_enum.dart b/lib/pages/settings_dashboard/settings_contacts_visibility/settings_contacts_visibility_enum.dart index cfa7c8327b..d746022278 100644 --- a/lib/pages/settings_dashboard/settings_contacts_visibility/settings_contacts_visibility_enum.dart +++ b/lib/pages/settings_dashboard/settings_contacts_visibility/settings_contacts_visibility_enum.dart @@ -7,23 +7,24 @@ enum SettingsContactsVisibilityEnum { contacts; String title(BuildContext context) { + final l10n = L10n.of(context)!; switch (this) { - case SettingsContactsVisibilityEnum.public: - return L10n.of(context)!.everyOne; - case SettingsContactsVisibilityEnum.contacts: - return L10n.of(context)!.myContacts; - case SettingsContactsVisibilityEnum.private: - return L10n.of(context)!.nobody; + case .public: + return l10n.everyOne; + case .contacts: + return l10n.myContacts; + case .private: + return l10n.nobody; } } bool enableDivider() { switch (this) { - case SettingsContactsVisibilityEnum.public: + case .public: return true; - case SettingsContactsVisibilityEnum.contacts: + case .contacts: return true; - case SettingsContactsVisibilityEnum.private: + case .private: return false; } } diff --git a/lib/pages/settings_dashboard/settings_contacts_visibility/settings_contacts_visibility_view.dart b/lib/pages/settings_dashboard/settings_contacts_visibility/settings_contacts_visibility_view.dart index fb8345baa9..96299d2cc0 100644 --- a/lib/pages/settings_dashboard/settings_contacts_visibility/settings_contacts_visibility_view.dart +++ b/lib/pages/settings_dashboard/settings_contacts_visibility/settings_contacts_visibility_view.dart @@ -1,10 +1,10 @@ import 'package:fluffychat/domain/model/user_info/user_info_visibility.dart'; +import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_contacts_visibility/settings_contacts_visibility.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_contacts_visibility/settings_contacts_visibility_enum.dart'; import 'package:fluffychat/presentation/extensions/settings/user_info_visibility_extension.dart'; import 'package:fluffychat/widgets/app_bars/twake_app_bar.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; -import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:fluffychat/widgets/twake_components/twake_icon_button.dart'; import 'package:flutter/material.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; @@ -16,11 +16,16 @@ class SettingsContactsVisibilityView extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = L10n.of(context)!; + final sysColor = LinagoraSysColors.material(); + final refColor90 = LinagoraRefColors.material().neutral[90]; + final textTheme = Theme.of(context).textTheme; + return Scaffold( - backgroundColor: LinagoraSysColors.material().onPrimary, + backgroundColor: sysColor.onPrimary, resizeToAvoidBottomInset: false, appBar: TwakeAppBar( - title: L10n.of(context)!.contactsVisibility, + title: l10n.contactsVisibility, centerTitle: true, withDivider: true, context: context, @@ -40,33 +45,31 @@ class SettingsContactsVisibilityView extends StatelessWidget { child: SizedBox( width: double.infinity, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), + padding: const .symmetric(horizontal: 16), child: Column( - mainAxisSize: MainAxisSize.max, + mainAxisSize: .max, children: [ Padding( - padding: const EdgeInsets.only( + padding: const .only( left: 32, right: 32, top: 24, bottom: 16, ), child: Text( - L10n.of(context)!.whoCanSeeMyPhoneEmail, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: LinagoraSysColors.material().tertiary, + l10n.whoCanSeeMyPhoneEmail, + style: textTheme.bodyMedium?.copyWith( + color: sysColor.tertiary, ), - textAlign: TextAlign.center, + textAlign: .center, ), ), Container( width: double.infinity, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: - LinagoraRefColors.material().neutral[90] ?? - Colors.transparent, + borderRadius: .circular(16), + border: .all( + color: refColor90 ?? Colors.transparent, width: 1, ), ), @@ -110,35 +113,30 @@ class SettingsContactsVisibilityView extends StatelessWidget { SettingsContactsVisibilityEnum.private) { return const SizedBox.shrink(); } + return Column( children: [ Padding( - padding: const EdgeInsets.only( + padding: const .only( left: 32, right: 32, top: 32, bottom: 8, ), child: Text( - L10n.of( - context, - )!.chooseWhichDetailsAreVisibleToOtherUsers, - style: Theme.of(context).textTheme.bodyMedium - ?.copyWith( - color: - LinagoraSysColors.material().tertiary, - ), - textAlign: TextAlign.center, + l10n.chooseWhichDetailsAreVisibleToOtherUsers, + style: textTheme.bodyMedium?.copyWith( + color: sysColor.tertiary, + ), + textAlign: .center, ), ), Container( width: double.infinity, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: - LinagoraRefColors.material().neutral[90] ?? - Colors.transparent, + borderRadius: .circular(16), + border: .all( + color: refColor90 ?? Colors.transparent, width: 1, ), ), @@ -189,17 +187,14 @@ class SettingsContactsVisibilityView extends StatelessWidget { required SettingsContactsVisibilityEnum option, }) { switch (option) { - case SettingsContactsVisibilityEnum.public: - return const BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - ); - case SettingsContactsVisibilityEnum.contacts: + case .public: + return const .only(topLeft: .circular(16), topRight: .circular(16)); + case .contacts: return null; - case SettingsContactsVisibilityEnum.private: - return const BorderRadius.only( - bottomLeft: Radius.circular(16), - bottomRight: Radius.circular(16), + case .private: + return const .only( + bottomLeft: .circular(16), + bottomRight: .circular(16), ); } } @@ -211,6 +206,8 @@ class SettingsContactsVisibilityView extends StatelessWidget { bool enableDivider = true, bool isSelected = false, }) { + final sysColor = LinagoraSysColors.material(); + return InkWell( key: Key('visibility_option_${option.name}'), onTap: () => onTap?.call(option), @@ -219,19 +216,19 @@ class SettingsContactsVisibilityView extends StatelessWidget { children: [ Expanded( child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: .spaceAround, + crossAxisAlignment: .start, children: [ Padding( - padding: const EdgeInsets.all(16), + padding: const .all(16), child: Text( option.title(context), style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: LinagoraSysColors.material().onSurface, + color: sysColor.onSurface, ), - overflow: TextOverflow.ellipsis, + overflow: .ellipsis, maxLines: 1, - textAlign: TextAlign.center, + textAlign: .center, ), ), if (enableDivider) @@ -243,7 +240,7 @@ class SettingsContactsVisibilityView extends StatelessWidget { ), ), Padding( - padding: const EdgeInsets.only(left: 8, right: 16), + padding: const .only(left: 8, right: 16), child: SizedBox( width: 24, height: 24, @@ -251,7 +248,7 @@ class SettingsContactsVisibilityView extends StatelessWidget { ? Icon( key: Key('visibility_option_selected_${option.name}'), Icons.check, - color: LinagoraSysColors.material().primary, + color: sysColor.primary, size: 24, ) : null, @@ -264,15 +261,12 @@ class SettingsContactsVisibilityView extends StatelessWidget { BorderRadius? _buildVisibleFieldBorderRadius({required VisibleEnum option}) { switch (option) { - case VisibleEnum.phone: - return const BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - ); - case VisibleEnum.email: - return const BorderRadius.only( - bottomLeft: Radius.circular(16), - bottomRight: Radius.circular(16), + case .phone: + return const .only(topLeft: .circular(16), topRight: .circular(16)); + case .email: + return const .only( + bottomLeft: .circular(16), + bottomRight: .circular(16), ); } } @@ -284,6 +278,9 @@ class SettingsContactsVisibilityView extends StatelessWidget { bool enableDivider = true, bool isSelected = false, }) { + final sysColor = LinagoraSysColors.material(); + final textTheme = Theme.of(context).textTheme; + return InkWell( key: Key('visible_field_option_${option.name}'), onTap: () => onTap?.call(option), @@ -292,33 +289,31 @@ class SettingsContactsVisibilityView extends StatelessWidget { children: [ Expanded( child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: .spaceAround, + crossAxisAlignment: .start, children: [ Padding( - padding: const EdgeInsets.all(16), + padding: const .all(16), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: .start, children: [ Text( option.title(context), - style: Theme.of(context).textTheme.titleMedium - ?.copyWith( - color: LinagoraSysColors.material().onSurface, - ), - overflow: TextOverflow.ellipsis, + style: textTheme.titleMedium?.copyWith( + color: sysColor.onSurface, + ), + overflow: .ellipsis, maxLines: 1, - textAlign: TextAlign.center, + textAlign: .center, ), Text( option.subtitle(context), - style: Theme.of(context).textTheme.labelMedium - ?.copyWith( - color: LinagoraSysColors.material().tertiary, - ), - overflow: TextOverflow.ellipsis, + style: textTheme.labelMedium?.copyWith( + color: sysColor.tertiary, + ), + overflow: .ellipsis, maxLines: 2, - textAlign: TextAlign.left, + textAlign: .left, ), ], ), @@ -332,7 +327,7 @@ class SettingsContactsVisibilityView extends StatelessWidget { ), ), Padding( - padding: const EdgeInsets.only(left: 8, right: 16), + padding: const .only(left: 8, right: 16), child: SizedBox( width: 24, height: 24, @@ -340,7 +335,7 @@ class SettingsContactsVisibilityView extends StatelessWidget { ? Icon( key: Key('visible_field_option_selected_${option.name}'), Icons.check, - color: LinagoraSysColors.material().primary, + color: sysColor.primary, size: 24, ) : null, diff --git a/lib/pages/settings_dashboard/settings_emotes/settings_emotes.dart b/lib/pages/settings_dashboard/settings_emotes/settings_emotes.dart index da53065a78..49ff838c1d 100644 --- a/lib/pages/settings_dashboard/settings_emotes/settings_emotes.dart +++ b/lib/pages/settings_dashboard/settings_emotes/settings_emotes.dart @@ -1,15 +1,14 @@ +import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:collection/collection.dart'; import 'package:file_picker/file_picker.dart'; +import 'package:fluffychat/generated/l10n/app_localizations.dart'; +import 'package:fluffychat/utils/client_manager.dart'; import 'package:fluffychat/utils/dialog/twake_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; - -import 'package:adaptive_dialog/adaptive_dialog.dart'; -import 'package:fluffychat/generated/l10n/app_localizations.dart'; - import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/utils/client_manager.dart'; + import 'settings_emotes_view.dart'; class EmotesSettings extends StatefulWidget { @@ -121,13 +120,14 @@ class EmotesSettingsController extends State { ImagePackImageContent image, TextEditingController controller, ) { + final l10n = L10n.of(context)!; if (pack!.images.keys.any((k) => k == imageCode && k != oldImageCode)) { controller.text = oldImageCode; showOkAlertDialog( useRootNavigator: false, context: context, - message: L10n.of(context)!.emoteExists, - okLabel: L10n.of(context)!.ok, + message: l10n.emoteExists, + okLabel: l10n.ok, ); return; } @@ -136,8 +136,8 @@ class EmotesSettingsController extends State { showOkAlertDialog( useRootNavigator: false, context: context, - message: L10n.of(context)!.emoteInvalid, - okLabel: L10n.of(context)!.ok, + message: l10n.emoteInvalid, + okLabel: l10n.ok, ); return; } @@ -176,13 +176,15 @@ class EmotesSettingsController extends State { } void addImageAction() async { + final l10n = L10n.of(context)!; + if (newImageCodeController.text.isEmpty || newImageController.value == null) { await showOkAlertDialog( useRootNavigator: false, context: context, - message: L10n.of(context)!.emoteWarnNeedToPick, - okLabel: L10n.of(context)!.ok, + message: l10n.emoteWarnNeedToPick, + okLabel: l10n.ok, ); return; } @@ -191,8 +193,8 @@ class EmotesSettingsController extends State { await showOkAlertDialog( useRootNavigator: false, context: context, - message: L10n.of(context)!.emoteExists, - okLabel: L10n.of(context)!.ok, + message: l10n.emoteExists, + okLabel: l10n.ok, ); return; } @@ -200,8 +202,8 @@ class EmotesSettingsController extends State { await showOkAlertDialog( useRootNavigator: false, context: context, - message: L10n.of(context)!.emoteInvalid, - okLabel: L10n.of(context)!.ok, + message: l10n.emoteInvalid, + okLabel: l10n.ok, ); return; } diff --git a/lib/pages/settings_dashboard/settings_emotes/settings_emotes_view.dart b/lib/pages/settings_dashboard/settings_emotes/settings_emotes_view.dart index 03b620de8f..df282076b6 100644 --- a/lib/pages/settings_dashboard/settings_emotes/settings_emotes_view.dart +++ b/lib/pages/settings_dashboard/settings_emotes/settings_emotes_view.dart @@ -1,16 +1,15 @@ +import 'package:fluffychat/generated/l10n/app_localizations.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/app_bars/twake_app_bar.dart'; import 'package:fluffychat/widgets/context_menu_builder_ios_paste_without_permission.dart'; +import 'package:fluffychat/widgets/layouts/max_width_body.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:fluffychat/widgets/mxc_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; - -import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/utils/platform_infos.dart'; -import 'package:fluffychat/widgets/layouts/max_width_body.dart'; -import 'package:fluffychat/widgets/mxc_image.dart'; import 'settings_emotes.dart'; class EmotesSettingsView extends StatelessWidget { @@ -22,12 +21,16 @@ class EmotesSettingsView extends StatelessWidget { Widget build(BuildContext context) { final client = Matrix.of(context).client; final imageKeys = controller.pack!.images.keys.toList(); + final l10n = L10n.of(context)!; + final theme = Theme.of(context); + final textTheme = TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + ); + return Scaffold( backgroundColor: LinagoraSysColors.material().onPrimary, - appBar: TwakeAppBar( - title: L10n.of(context)!.emoteSettings, - context: context, - ), + appBar: TwakeAppBar(title: l10n.emoteSettings, context: context), floatingActionButton: controller.showSave ? FloatingActionButton( onPressed: controller.saveAction, @@ -39,15 +42,15 @@ class EmotesSettingsView extends StatelessWidget { children: [ if (!controller.readonly) Container( - padding: const EdgeInsets.symmetric(vertical: 8.0), + padding: const .symmetric(vertical: 8.0), child: ListTile( leading: Container( width: 180.0, height: 38, - padding: const EdgeInsets.symmetric(horizontal: 8), + padding: const .symmetric(horizontal: 8), decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(10)), - color: Theme.of(context).secondaryHeaderColor, + borderRadius: const .all(.circular(10)), + color: theme.secondaryHeaderColor, ), child: TextField( controller: controller.newImageCodeController, @@ -56,18 +59,12 @@ class EmotesSettingsView extends StatelessWidget { minLines: 1, maxLines: 1, decoration: InputDecoration( - hintText: L10n.of(context)!.emoteShortcode, + hintText: l10n.emoteShortcode, prefixText: ': ', suffixText: ':', - prefixStyle: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - suffixStyle: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - border: InputBorder.none, + prefixStyle: textTheme, + suffixStyle: textTheme, + border: .none, ), ), ), @@ -87,133 +84,126 @@ class EmotesSettingsView extends StatelessWidget { ), if (controller.room != null) SwitchListTile.adaptive( - title: Text(L10n.of(context)!.enableEmotesGlobally), + title: Text(l10n.enableEmotesGlobally), value: controller.isGloballyActive(client), onChanged: controller.setIsGloballyActive, ), if (!controller.readonly || controller.room != null) - Divider( - height: 2, - thickness: 2, - color: Theme.of(context).primaryColor, - ), + Divider(height: 2, thickness: 2, color: theme.primaryColor), Expanded( child: imageKeys.isEmpty ? Center( child: Padding( - padding: const EdgeInsets.all(16), + padding: const .all(16), child: Text( - L10n.of(context)!.noEmotesFound, + l10n.noEmotesFound, style: const TextStyle(fontSize: 20), ), ), ) - : ListView.separated( - separatorBuilder: (BuildContext context, int i) => - Container(), - itemCount: imageKeys.length + 1, - itemBuilder: (BuildContext context, int i) { - if (i >= imageKeys.length) { - return Container(height: 70); - } - final imageCode = imageKeys[i]; - final image = controller.pack!.images[imageCode]!; - final textEditingController = TextEditingController(); - textEditingController.text = imageCode; - final useShortCuts = - (PlatformInfos.isWeb || PlatformInfos.isDesktop); - return ListTile( - leading: Container( - width: 180.0, - height: 38, - padding: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - borderRadius: const BorderRadius.all( - Radius.circular(10), - ), - color: Theme.of(context).secondaryHeaderColor, - ), - child: Shortcuts( - shortcuts: !useShortCuts - ? {} - : { - LogicalKeySet(LogicalKeyboardKey.enter): - SubmitLineIntent(), - }, - child: Actions( - actions: !useShortCuts - ? {} - : { - SubmitLineIntent: CallbackAction( - onInvoke: (i) { - controller.submitImageAction( - imageCode, - textEditingController.text, - image, - textEditingController, - ); - return null; - }, - ), - }, - child: TextField( - readOnly: controller.readonly, - controller: textEditingController, - contextMenuBuilder: - mobileTwakeContextMenuBuilder, - autocorrect: false, - minLines: 1, - maxLines: 1, - decoration: InputDecoration( - hintText: L10n.of(context)!.emoteShortcode, - prefixText: ': ', - suffixText: ':', - prefixStyle: TextStyle( - color: Theme.of( - context, - ).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - suffixStyle: TextStyle( - color: Theme.of( - context, - ).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - border: InputBorder.none, - ), - onSubmitted: (s) => - controller.submitImageAction( - imageCode, - s, - image, - textEditingController, - ), - ), - ), - ), - ), - title: _EmoteImage(image.url), - trailing: controller.readonly - ? null - : InkWell( - onTap: () => - controller.removeImageAction(imageCode), - child: const Icon( - Icons.delete_outlined, - color: Colors.red, - size: 32.0, - ), - ), - ); - }, - ), + : _buildImageKeysNotEmptyResult(imageKeys), ), ], ), ), ); } + + ListView _buildImageKeysNotEmptyResult(List imageKeys) { + return ListView.separated( + separatorBuilder: (_, _) => Container(), + itemCount: imageKeys.length + 1, + itemBuilder: (BuildContext context, int i) { + final l10n = L10n.of(context)!; + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; + + if (i >= imageKeys.length) return Container(height: 70); + + final imageCode = imageKeys[i]; + final image = controller.pack!.images[imageCode]!; + final textEditingController = TextEditingController(); + textEditingController.text = imageCode; + final useShortCuts = (PlatformInfos.isWeb || PlatformInfos.isDesktop); + + return ListTile( + leading: Container( + width: 180.0, + height: 38, + padding: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + borderRadius: const .all(.circular(10)), + color: theme.secondaryHeaderColor, + ), + child: Shortcuts( + shortcuts: !useShortCuts + ? {} + : { + LogicalKeySet(LogicalKeyboardKey.enter): + SubmitLineIntent(), + }, + child: Actions( + actions: !useShortCuts + ? {} + : { + SubmitLineIntent: CallbackAction( + onInvoke: (i) { + controller.submitImageAction( + imageCode, + textEditingController.text, + image, + textEditingController, + ); + return null; + }, + ), + }, + child: TextField( + readOnly: controller.readonly, + controller: textEditingController, + contextMenuBuilder: mobileTwakeContextMenuBuilder, + autocorrect: false, + minLines: 1, + maxLines: 1, + decoration: InputDecoration( + hintText: l10n.emoteShortcode, + prefixText: ': ', + suffixText: ':', + prefixStyle: TextStyle( + color: colorScheme.secondary, + fontWeight: .bold, + ), + suffixStyle: TextStyle( + color: colorScheme.secondary, + fontWeight: .bold, + ), + border: .none, + ), + onSubmitted: (s) => controller.submitImageAction( + imageCode, + s, + image, + textEditingController, + ), + ), + ), + ), + ), + title: _EmoteImage(image.url), + trailing: controller.readonly + ? null + : InkWell( + onTap: () => controller.removeImageAction(imageCode), + child: const Icon( + Icons.delete_outlined, + color: Colors.red, + size: 32.0, + ), + ), + ); + }, + ); + } } class _EmoteImage extends StatelessWidget { @@ -223,7 +213,7 @@ class _EmoteImage extends StatelessWidget { @override Widget build(BuildContext context) { const size = 38.0; - return MxcImage(uri: mxc, fit: BoxFit.contain, width: size, height: size); + return MxcImage(uri: mxc, fit: .contain, width: size, height: size); } } @@ -246,9 +236,8 @@ class _ImagePickerState extends State<_ImagePicker> { onPressed: () => widget.onPressed(widget.controller), child: Text(L10n.of(context)!.pickImage), ); - } else { - return _EmoteImage(widget.controller.value!.url); } + return _EmoteImage(widget.controller.value!.url); } } diff --git a/lib/pages/settings_dashboard/settings_multiple_emotes/settings_multiple_emotes_view.dart b/lib/pages/settings_dashboard/settings_multiple_emotes/settings_multiple_emotes_view.dart index 53ba7b223b..ca64c599dc 100644 --- a/lib/pages/settings_dashboard/settings_multiple_emotes/settings_multiple_emotes_view.dart +++ b/lib/pages/settings_dashboard/settings_multiple_emotes/settings_multiple_emotes_view.dart @@ -1,14 +1,12 @@ +import 'package:fluffychat/generated/l10n/app_localizations.dart'; +import 'package:fluffychat/pages/settings_dashboard/settings_multiple_emotes/settings_multiple_emotes.dart'; import 'package:fluffychat/widgets/app_bars/twake_app_bar.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; - -import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:go_router/go_router.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/pages/settings_dashboard/settings_multiple_emotes/settings_multiple_emotes.dart'; -import 'package:fluffychat/widgets/matrix.dart'; - class MultipleEmotesSettingsView extends StatelessWidget { final MultipleEmotesSettingsController controller; @@ -17,6 +15,7 @@ class MultipleEmotesSettingsView extends StatelessWidget { @override Widget build(BuildContext context) { final room = Matrix.of(context).client.getRoomById(controller.roomId!)!; + return Scaffold( backgroundColor: LinagoraSysColors.material().onPrimary, appBar: TwakeAppBar( diff --git a/lib/pages/settings_dashboard/settings_notifications/settings_notifications_view.dart b/lib/pages/settings_dashboard/settings_notifications/settings_notifications_view.dart index 971ae7e5ce..a86e26075e 100644 --- a/lib/pages/settings_dashboard/settings_notifications/settings_notifications_view.dart +++ b/lib/pages/settings_dashboard/settings_notifications/settings_notifications_view.dart @@ -1,14 +1,15 @@ import 'package:fluffychat/di/global/get_it_initializer.dart'; +import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:fluffychat/utils/dialog/twake_dialog.dart'; import 'package:fluffychat/utils/responsive/responsive_utils.dart'; import 'package:fluffychat/widgets/app_bars/twake_app_bar.dart'; import 'package:fluffychat/widgets/app_bars/twake_app_bar_style.dart'; +import 'package:fluffychat/widgets/layouts/max_width_body.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; -import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:go_router/go_router.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; -import 'package:fluffychat/widgets/layouts/max_width_body.dart'; + import 'settings_notifications.dart'; class SettingsNotificationsView extends StatelessWidget { @@ -19,10 +20,16 @@ class SettingsNotificationsView extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = L10n.of(context)!; + final client = Matrix.of(context).client; + final theme = Theme.of(context); + final textTheme = theme.textTheme; + final colorScheme = theme.colorScheme; + return Scaffold( backgroundColor: LinagoraSysColors.material().onPrimary, appBar: TwakeAppBar( - title: L10n.of(context)!.notifications, + title: l10n.notifications, context: context, centerTitle: true, withDivider: true, @@ -30,9 +37,9 @@ class SettingsNotificationsView extends StatelessWidget { ? Padding( padding: TwakeAppBarStyle.leadingIconPadding, child: IconButton( - tooltip: L10n.of(context)!.back, + tooltip: l10n.back, icon: const Icon(Icons.arrow_back_ios), - onPressed: () => context.pop(), + onPressed: context.pop, iconSize: TwakeAppBarStyle.leadingIconSize, ), ) @@ -41,39 +48,36 @@ class SettingsNotificationsView extends StatelessWidget { body: MaxWidthBody( withScrolling: true, child: StreamBuilder( - stream: Matrix.of(context).client.onAccountData.stream.where( + stream: client.onAccountData.stream.where( (event) => event.type == 'm.push_rules', ), builder: (BuildContext context, _) { return Column( children: [ SwitchListTile.adaptive( - value: !Matrix.of(context).client.allPushNotificationsMuted, + value: !client.allPushNotificationsMuted, title: Text( - Matrix.of(context).client.allPushNotificationsMuted - ? L10n.of(context)!.enable_notifications - : L10n.of(context)!.disable_notifications, - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: Theme.of(context).colorScheme.onSurface, + client.allPushNotificationsMuted + ? l10n.enable_notifications + : l10n.disable_notifications, + style: textTheme.bodyLarge?.copyWith( + color: colorScheme.onSurface, ), ), onChanged: (_) => TwakeDialog.showFutureLoadingDialogFullScreen( - future: () => Matrix.of(context).client - .setMuteAllPushNotifications( - !Matrix.of( - context, - ).client.allPushNotificationsMuted, - ), + future: () => client.setMuteAllPushNotifications( + !client.allPushNotificationsMuted, + ), ), ), - if (!Matrix.of(context).client.allPushNotificationsMuted) ...{ + if (!client.allPushNotificationsMuted) ...{ const Divider(thickness: 1), ListTile( title: Text( - L10n.of(context)!.pushRules, - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: Theme.of(context).colorScheme.onSurface, + l10n.pushRules, + style: textTheme.bodyLarge?.copyWith( + color: colorScheme.onSurface, fontWeight: FontWeight.w700, ), ), @@ -83,8 +87,8 @@ class SettingsNotificationsView extends StatelessWidget { value: controller.getNotificationSetting(item) ?? true, title: Text( item.title(context), - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: Theme.of(context).colorScheme.onSurface, + style: textTheme.bodyLarge?.copyWith( + color: colorScheme.onSurface, ), ), onChanged: (bool enabled) => diff --git a/lib/pages/settings_dashboard/settings_profile/settings_profile.dart b/lib/pages/settings_dashboard/settings_profile/settings_profile.dart index 26d47c6654..da357c0823 100644 --- a/lib/pages/settings_dashboard/settings_profile/settings_profile.dart +++ b/lib/pages/settings_dashboard/settings_profile/settings_profile.dart @@ -4,6 +4,7 @@ import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:dartz/dartz.dart' hide State; import 'package:fluffychat/app_state/failure.dart'; import 'package:fluffychat/app_state/success.dart'; +import 'package:fluffychat/config/go_routes/app_route_paths.dart'; import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/domain/app_state/room/upload_content_state.dart'; import 'package:fluffychat/domain/app_state/settings/update_profile_failure.dart'; @@ -13,19 +14,20 @@ import 'package:fluffychat/domain/usecase/room/upload_content_interactor.dart'; import 'package:fluffychat/domain/usecase/settings/update_profile_interactor.dart'; import 'package:fluffychat/event/twake_event_dispatcher.dart'; import 'package:fluffychat/event/twake_inapp_event_types.dart'; +import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:fluffychat/pages/multiple_accounts/multiple_accounts_picker.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_profile/settings_profile_capability_check.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_profile/settings_profile_context_menu_actions.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_profile/settings_profile_state/get_clients_ui_state.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_profile/settings_profile_view.dart'; -import 'package:fluffychat/presentation/extensions/multiple_accounts/client_profile_extension.dart'; -import 'package:fluffychat/presentation/mixins/pick_avatar_mixin.dart'; -import 'package:fluffychat/presentation/model/pick_avatar_state.dart'; -import 'package:fluffychat/presentation/multiple_account/client_profile_presentation.dart'; import 'package:fluffychat/presentation/enum/settings/settings_profile_enum.dart'; import 'package:fluffychat/presentation/extensions/client_extension.dart'; +import 'package:fluffychat/presentation/extensions/multiple_accounts/client_profile_extension.dart'; import 'package:fluffychat/presentation/mixins/common_media_picker_mixin.dart'; +import 'package:fluffychat/presentation/mixins/pick_avatar_mixin.dart'; import 'package:fluffychat/presentation/mixins/single_image_picker_mixin.dart'; +import 'package:fluffychat/presentation/model/pick_avatar_state.dart'; +import 'package:fluffychat/presentation/multiple_account/client_profile_presentation.dart'; import 'package:fluffychat/presentation/multiple_account/twake_chat_presentation_account.dart'; import 'package:fluffychat/utils/client_manager.dart'; import 'package:fluffychat/utils/dialog/twake_dialog.dart'; @@ -43,7 +45,6 @@ import 'package:go_router/go_router.dart'; import 'package:linagora_design_flutter/images_picker/asset_counter.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:wechat_camera_picker/wechat_camera_picker.dart'; class SettingsProfile extends StatefulWidget { @@ -138,9 +139,9 @@ class SettingsProfileController extends State SettingsProfileEnum settingsProfileEnum, ) { switch (settingsProfileEnum) { - case SettingsProfileEnum.displayName: + case .displayName: return displayNameEditingController; - case SettingsProfileEnum.matrixId: + case .matrixId: return matrixIdEditingController; default: return null; @@ -149,7 +150,7 @@ class SettingsProfileController extends State FocusNode? getFocusNode(SettingsProfileEnum settingsProfileEnum) { switch (settingsProfileEnum) { - case SettingsProfileEnum.displayName: + case .displayName: return displayNameFocusNode; default: return null; @@ -221,7 +222,7 @@ class SettingsProfileController extends State actions: actions(), ); if (action == null) return; - if (action == AvatarAction.remove) { + if (action == .remove) { _handleRemoveAvatarAction(); return; } @@ -229,10 +230,8 @@ class SettingsProfileController extends State } List listContextMenuBuilder(BuildContext context) { - final listAction = [ - SettingsProfileContextMenuActions.edit, - SettingsProfileContextMenuActions.delete, - ]; + final List listAction = [.edit, .delete]; + final items = listAction.map((action) { return popupItemByTwakeAppRouter( context, @@ -261,10 +260,10 @@ class SettingsProfileController extends State void _handleActionContextMenu(SettingsProfileContextMenuActions action) { switch (action) { - case SettingsProfileContextMenuActions.edit: + case .edit: _showImagesPickerAction(); break; - case SettingsProfileContextMenuActions.delete: + case .delete: pickAvatarUIState.value = Right( GetAvatarInitialUIState(), ); @@ -487,7 +486,7 @@ class SettingsProfileController extends State void copyEventsAction(SettingsProfileEnum settingsProfileEnum) { switch (settingsProfileEnum) { - case SettingsProfileEnum.matrixId: + case .matrixId: Clipboard.setData(ClipboardData(text: client.mxid(context))); TwakeSnackBar.show( context, @@ -556,7 +555,7 @@ class SettingsProfileController extends State ).showMultipleAccountsPicker( client, onGoToAccountSettings: () { - context.go('/rooms/profile'); + context.go(AppRoutePaths.profileFull); }, ); } @@ -663,8 +662,9 @@ class SettingsProfileController extends State } void updateNewProfileForAccount() { + final client = Matrix.of(context).client; listenOnProfileChangeStream( - client: Matrix.of(context).client, + client: client, currentProfile: currentProfile.value, onProfileChanged: (newProfile) { final indexOldAccount = _multipleAccounts.indexWhere( @@ -674,9 +674,9 @@ class SettingsProfileController extends State return; } final newAccount = ClientProfilePresentation( - client: Matrix.of(context).client, + client: client, profile: newProfile, - ).toTwakeChatPresentationAccount(Matrix.of(context).client); + ).toTwakeChatPresentationAccount(client); _multipleAccounts[indexOldAccount] = newAccount; settingsMultiAccountsUIState.value = Right( GetClientsSuccessUIState(multipleAccounts: _multipleAccounts), diff --git a/lib/pages/settings_dashboard/settings_profile/settings_profile_context_menu_actions.dart b/lib/pages/settings_dashboard/settings_profile/settings_profile_context_menu_actions.dart index 2369327b1c..daa423f114 100644 --- a/lib/pages/settings_dashboard/settings_profile/settings_profile_context_menu_actions.dart +++ b/lib/pages/settings_dashboard/settings_profile/settings_profile_context_menu_actions.dart @@ -1,24 +1,25 @@ -import 'package:flutter/material.dart'; import 'package:fluffychat/generated/l10n/app_localizations.dart'; +import 'package:flutter/material.dart'; enum SettingsProfileContextMenuActions { edit, delete; String getTitle(BuildContext context) { + final l10n = L10n.of(context)!; switch (this) { - case SettingsProfileContextMenuActions.edit: - return L10n.of(context)!.changeProfileAvatar; - case SettingsProfileContextMenuActions.delete: - return L10n.of(context)!.removeYourAvatar; + case .edit: + return l10n.changeProfileAvatar; + case .delete: + return l10n.removeYourAvatar; } } IconData getIcon() { switch (this) { - case SettingsProfileContextMenuActions.edit: + case .edit: return Icons.camera_alt_outlined; - case SettingsProfileContextMenuActions.delete: + case .delete: return Icons.delete; } } diff --git a/lib/pages/settings_dashboard/settings_profile/settings_profile_item.dart b/lib/pages/settings_dashboard/settings_profile/settings_profile_item.dart index 296ad8b06b..06d20aa52f 100644 --- a/lib/pages/settings_dashboard/settings_profile/settings_profile_item.dart +++ b/lib/pages/settings_dashboard/settings_profile/settings_profile_item.dart @@ -32,6 +32,10 @@ class SettingsProfileItemBuilder extends StatelessWidget { @override Widget build(BuildContext context) { + final sysColor = LinagoraSysColors.material(); + final refColor = LinagoraRefColors.material(); + final textTheme = Theme.of(context).textTheme; + return Column( children: [ Row( @@ -42,22 +46,22 @@ class SettingsProfileItemBuilder extends StatelessWidget { child: Icon( leadingIcon, size: SettingsProfileItemStyle.iconSize, - color: LinagoraSysColors.material().tertiary, + color: sysColor.tertiary, ), ), Expanded( child: Padding( padding: SettingsProfileItemStyle.textPadding, child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: .start, children: [ Text( title, - style: Theme.of(context).textTheme.labelMedium?.copyWith( - color: LinagoraRefColors.material().neutral[40], + style: textTheme.labelMedium?.copyWith( + color: refColor.neutral[40], ), maxLines: 1, - overflow: TextOverflow.ellipsis, + overflow: .ellipsis, ), if (textEditingController != null) ValueListenableBuilder( @@ -65,12 +69,11 @@ class SettingsProfileItemBuilder extends StatelessWidget { builder: (context, value, _) { return Text( value.text, - style: Theme.of(context).textTheme.bodyLarge - ?.copyWith( - color: LinagoraSysColors.material().onSurface, - ), + style: textTheme.bodyLarge?.copyWith( + color: sysColor.onSurface, + ), maxLines: 1, - overflow: TextOverflow.ellipsis, + overflow: .ellipsis, ); }, ), @@ -86,7 +89,7 @@ class SettingsProfileItemBuilder extends StatelessWidget { icon: Icon( suffixIcon, size: SettingsProfileItemStyle.copyIconSize, - color: LinagoraRefColors.material().tertiary[40], + color: refColor.tertiary[40], ), ), ], @@ -94,12 +97,10 @@ class SettingsProfileItemBuilder extends StatelessWidget { const SizedBox(height: 8), if (enableDivider) Container( - width: double.infinity, + width: .infinity, height: 1, - margin: const EdgeInsets.only(left: 40), - color: LinagoraStateLayer( - LinagoraSysColors.material().surfaceTint, - ).opacityLayer3, + margin: const .only(left: 40), + color: LinagoraStateLayer(sysColor.surfaceTint).opacityLayer3, ), ], ); @@ -107,7 +108,7 @@ class SettingsProfileItemBuilder extends StatelessWidget { bool get hasSuffixIcon { return switch (settingsProfileEnum) { - SettingsProfileEnum.displayName => canEditDisplayName, + .displayName => canEditDisplayName, _ => true, }; } diff --git a/lib/pages/settings_dashboard/settings_profile/settings_profile_item_style.dart b/lib/pages/settings_dashboard/settings_profile/settings_profile_item_style.dart index 48c2e62c77..31196b2c56 100644 --- a/lib/pages/settings_dashboard/settings_profile/settings_profile_item_style.dart +++ b/lib/pages/settings_dashboard/settings_profile/settings_profile_item_style.dart @@ -4,10 +4,7 @@ class SettingsProfileItemStyle { static const double iconSize = 24.0; static const double copyIconSize = 20.0; - static const EdgeInsetsDirectional itemBuilderPadding = - EdgeInsetsDirectional.only(end: 8.0); + static const EdgeInsetsDirectional itemBuilderPadding = .only(end: 8.0); - static const EdgeInsetsGeometry textPadding = EdgeInsets.symmetric( - horizontal: 8.0, - ); + static const EdgeInsetsGeometry textPadding = .symmetric(horizontal: 8.0); } diff --git a/lib/pages/settings_dashboard/settings_profile/settings_profile_redirection_edit_button.dart b/lib/pages/settings_dashboard/settings_profile/settings_profile_redirection_edit_button.dart index 27ad190055..1f4a7fd1f1 100644 --- a/lib/pages/settings_dashboard/settings_profile/settings_profile_redirection_edit_button.dart +++ b/lib/pages/settings_dashboard/settings_profile/settings_profile_redirection_edit_button.dart @@ -19,6 +19,7 @@ class SettingsProfileRedirectionEditButton extends StatelessWidget { @override Widget build(BuildContext context) { final textTheme = Theme.of(context).textTheme; + final sysColor = LinagoraSysColors.material(); final matrix = Matrix.of(context); final userId = matrix.client.userID; final commonSettingsInformation = matrix @@ -40,14 +41,12 @@ class SettingsProfileRedirectionEditButton extends StatelessWidget { padding: const EdgeInsetsDirectional.only(end: 16), child: TextButton( style: TextButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12), - overlayColor: LinagoraSysColors.material().shadow.withValues( - alpha: 0.2, - ), + padding: const .symmetric(vertical: 10, horizontal: 12), + overlayColor: sysColor.shadow.withValues(alpha: 0.2), textStyle: textTheme.labelLarge?.copyWith( fontSize: 14, height: 20 / 14, - color: LinagoraSysColors.material().primary, + color: sysColor.primary, ), ), onPressed: () { diff --git a/lib/pages/settings_dashboard/settings_profile/settings_profile_state/settings_profile_ui_state.dart b/lib/pages/settings_dashboard/settings_profile/settings_profile_state/settings_profile_ui_state.dart deleted file mode 100644 index 54f00f7e64..0000000000 --- a/lib/pages/settings_dashboard/settings_profile/settings_profile_state/settings_profile_ui_state.dart +++ /dev/null @@ -1,3 +0,0 @@ -import 'package:fluffychat/presentation/state/success.dart'; - -abstract class SettingsProfileUIState extends UIState {} diff --git a/lib/pages/settings_dashboard/settings_profile/settings_profile_view.dart b/lib/pages/settings_dashboard/settings_profile/settings_profile_view.dart index 314e29adcb..84c252164d 100644 --- a/lib/pages/settings_dashboard/settings_profile/settings_profile_view.dart +++ b/lib/pages/settings_dashboard/settings_profile/settings_profile_view.dart @@ -1,5 +1,7 @@ +import 'package:fluffychat/config/go_routes/app_route_paths.dart'; import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/domain/model/capabilities/capabilities_extension.dart'; +import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_profile/settings_profile.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_profile/settings_profile_item.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_profile/settings_profile_redirection_edit_button.dart'; @@ -13,7 +15,6 @@ import 'package:fluffychat/widgets/app_bars/twake_app_bar.dart'; import 'package:fluffychat/widgets/twake_components/twake_icon_button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; -import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:go_router/go_router.dart'; import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; import 'package:matrix/matrix.dart'; @@ -39,16 +40,18 @@ class SettingsProfileView extends StatelessWidget { @override Widget build(BuildContext context) { final responsive = getIt.get(); + final l10n = L10n.of(context)!; + return Scaffold( appBar: TwakeAppBar( - title: L10n.of(context)!.profile, + title: l10n.profile, leading: responsive.isMobile(context) ? IconButton( icon: const Icon( Icons.arrow_back_ios, size: SettingsProfileViewStyle.sizeIcon, ), - onPressed: () => context.pop(), + onPressed: context.pop, ) : const SizedBox.shrink(), actions: [ @@ -56,7 +59,7 @@ class SettingsProfileView extends StatelessWidget { TwakeIconButton( icon: Icons.qr_code, iconColor: LinagoraSysColors.material().primary, - onTap: () => context.go('/rooms/profile/qr'), + onTap: () => context.go(AppRoutePaths.profileQrFull), ), ValueListenableBuilder( valueListenable: controller.isEditedProfileNotifier, @@ -69,14 +72,14 @@ class SettingsProfileView extends StatelessWidget { return Padding( padding: SettingsProfileViewStyle.actionButtonPadding, child: InkWell( - borderRadius: BorderRadius.circular( + borderRadius: .circular( SettingsProfileViewStyle.borderRadius, ), onTap: () => controller.onUploadProfileAction(), child: Padding( padding: SettingsProfileViewStyle.paddingTextButton, child: Text( - L10n.of(context)!.done, + l10n.done, style: Theme.of(context).textTheme.labelLarge?.copyWith( color: Theme.of(context).colorScheme.primary, ), @@ -118,40 +121,36 @@ class SettingsProfileView extends StatelessWidget { padding: EdgeInsets.zero, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) { + final settingsProfile = + controller.getListProfileMobile[index]; + return SettingsProfileItemBuilder( - settingsProfileEnum: - controller.getListProfileMobile[index], - title: controller.getListProfileMobile[index].getTitle( - context, - ), + settingsProfileEnum: settingsProfile, + title: settingsProfile.getTitle(context), settingsProfilePresentation: SettingsProfilePresentation( - settingsProfileType: controller - .getListProfileMobile[index] + settingsProfileType: settingsProfile .getSettingsProfileType(), ), - suffixIcon: controller.getListProfileMobile[index] - .getTrailingIcon(), - leadingIcon: controller.getListProfileMobile[index] - .getLeadingIcon(), + suffixIcon: settingsProfile.getTrailingIcon(), + leadingIcon: settingsProfile.getLeadingIcon(), onEditRequested: () { final focusNode = controller.getFocusNode( - controller.getListProfileMobile[index], + settingsProfile, ); focusNode?.requestFocus(); }, textEditingController: controller.getController( - controller.getListProfileMobile[index], - ), - onCopyAction: () => controller.copyEventsAction( - controller.getListProfileMobile[index], + settingsProfile, ), + onCopyAction: () => + controller.copyEventsAction(settingsProfile), canEditDisplayName: capabilities?.canEditDisplayName == true, enableDivider: index != (controller.getListProfileMobile.length - 1), ); }, - separatorBuilder: (context, index) { + separatorBuilder: (_, _) { return const SizedBox(height: 8); }, itemCount: controller.getListProfileMobile.length, @@ -177,33 +176,31 @@ class SettingsProfileView extends StatelessWidget { shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) { + final settingsProfile = + controller.getListProfileBasicInfo[index]; + return SettingsProfileItemBuilder( - settingsProfileEnum: - controller.getListProfileBasicInfo[index], - title: controller.getListProfileBasicInfo[index].getTitle( - context, - ), + settingsProfileEnum: settingsProfile, + title: settingsProfile.getTitle(context), settingsProfilePresentation: SettingsProfilePresentation( - settingsProfileType: controller - .getListProfileBasicInfo[index] + settingsProfileType: settingsProfile .getSettingsProfileType(), ), - suffixIcon: controller.getListProfileBasicInfo[index] - .getTrailingIcon(), + suffixIcon: settingsProfile.getTrailingIcon(), onEditRequested: () { final focusNode = controller.getFocusNode( - controller.getListProfileBasicInfo[index], + settingsProfile, ); focusNode?.requestFocus(); }, textEditingController: controller.getController( - controller.getListProfileBasicInfo[index], + settingsProfile, ), canEditDisplayName: capabilities?.canEditDisplayName == true, ); }, - separatorBuilder: (context, index) { + separatorBuilder: (_, _) { return const SizedBox(height: 16); }, itemCount: controller.getListProfileBasicInfo.length, @@ -213,36 +210,33 @@ class SettingsProfileView extends StatelessWidget { shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) { + final workIdentityInfo = + controller.getListProfileWorkIdentitiesInfo[index]; + return SettingsProfileItemBuilder( - settingsProfileEnum: - controller.getListProfileWorkIdentitiesInfo[index], - title: controller.getListProfileWorkIdentitiesInfo[index] - .getTitle(context), + settingsProfileEnum: workIdentityInfo, + title: workIdentityInfo.getTitle(context), settingsProfilePresentation: SettingsProfilePresentation( - settingsProfileType: controller - .getListProfileWorkIdentitiesInfo[index] + settingsProfileType: workIdentityInfo .getSettingsProfileType(), ), - suffixIcon: controller - .getListProfileWorkIdentitiesInfo[index] - .getTrailingIcon(), + suffixIcon: workIdentityInfo.getTrailingIcon(), onEditRequested: () { final focusNode = controller.getFocusNode( - controller.getListProfileWorkIdentitiesInfo[index], + workIdentityInfo, ); focusNode?.requestFocus(); }, textEditingController: controller.getController( - controller.getListProfileWorkIdentitiesInfo[index], - ), - onCopyAction: () => controller.copyEventsAction( - controller.getListProfileWorkIdentitiesInfo[index], + workIdentityInfo, ), + onCopyAction: () => + controller.copyEventsAction(workIdentityInfo), canEditDisplayName: capabilities?.canEditDisplayName == true, ); }, - separatorBuilder: (context, index) { + separatorBuilder: (_, _) { return const SizedBox(height: 16); }, itemCount: controller.getListProfileWorkIdentitiesInfo.length, @@ -257,11 +251,13 @@ class SettingsProfileView extends StatelessWidget { } Color backgroundColor(ResponsiveUtils responsive, BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + if (PlatformInfos.isMobile) { - return Theme.of(context).colorScheme.surface; + return colorScheme.surface; } else { return responsive.isWebDesktop(context) - ? Theme.of(context).colorScheme.surface + ? colorScheme.surface : LinagoraSysColors.material().onPrimary; } } diff --git a/lib/pages/settings_dashboard/settings_profile/settings_profile_view_mobile.dart b/lib/pages/settings_dashboard/settings_profile/settings_profile_view_mobile.dart index 779e5da845..1f28ddbdcf 100644 --- a/lib/pages/settings_dashboard/settings_profile/settings_profile_view_mobile.dart +++ b/lib/pages/settings_dashboard/settings_profile/settings_profile_view_mobile.dart @@ -1,6 +1,7 @@ import 'package:dartz/dartz.dart' hide State; import 'package:fluffychat/app_state/failure.dart'; import 'package:fluffychat/app_state/success.dart'; +import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_profile/settings_profile_state/get_clients_ui_state.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_profile/settings_profile_view_mobile_style.dart'; import 'package:fluffychat/presentation/extensions/client_extension.dart'; @@ -12,7 +13,6 @@ import 'package:fluffychat/widgets/mixins/popup_menu_widget_style.dart'; import 'package:flutter/material.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/generated/l10n/app_localizations.dart'; typedef OnTapMultipleAccountsButton = void Function(List multipleAccounts); @@ -63,6 +63,10 @@ class _SettingsProfileViewMobileState extends State { @override Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + final l10n = L10n.of(context)!; + return Column( children: [ Column( @@ -75,9 +79,9 @@ class _SettingsProfileViewMobileState extends State { builder: (context, _) { return Stack( children: [ - Positioned.fill(child: _buildAvatarBackground(context)), - Positioned.fill(child: _buildGradientOverlay(context)), - Column(children: [_buildProfileInformation(context)]), + Positioned.fill(child: _buildAvatarBackground()), + Positioned.fill(child: _buildGradientOverlay()), + Column(children: [_buildProfileInformation()]), if (widget.canEditAvatar && !isExpanded) Positioned( bottom: SettingsProfileViewMobileStyle @@ -87,9 +91,7 @@ class _SettingsProfileViewMobileState extends State { child: MenuAnchor( controller: widget.menuController, style: MenuStyle( - padding: const WidgetStatePropertyAll( - EdgeInsets.zero, - ), + padding: const WidgetStatePropertyAll(.zero), backgroundColor: WidgetStatePropertyAll( PopupMenuWidgetStyle.defaultMenuColor( context, @@ -107,7 +109,7 @@ class _SettingsProfileViewMobileState extends State { ( BuildContext context, MenuController menuController, - Widget? child, + _, ) { return GestureDetector( onTap: () { @@ -121,17 +123,13 @@ class _SettingsProfileViewMobileState extends State { }, child: Container( decoration: BoxDecoration( - color: Theme.of( - context, - ).colorScheme.primary, - borderRadius: BorderRadius.circular( + color: colorScheme.primary, + borderRadius: .circular( SettingsProfileViewMobileStyle .avatarSize, ), - border: Border.all( - color: Theme.of( - context, - ).colorScheme.onPrimary, + border: .all( + color: colorScheme.onPrimary, width: SettingsProfileViewMobileStyle .iconEditBorderWidth, @@ -143,9 +141,7 @@ class _SettingsProfileViewMobileState extends State { Icons.edit, size: SettingsProfileViewMobileStyle .iconEditSize, - color: Theme.of( - context, - ).colorScheme.onPrimary, + color: colorScheme.onPrimary, ), ), ); @@ -160,16 +156,16 @@ class _SettingsProfileViewMobileState extends State { }, ), Container( - margin: const EdgeInsets.only(left: 12, right: 12, top: 12), - padding: const EdgeInsets.all(8), + margin: const .only(left: 12, right: 12, top: 12), + padding: const .all(8), decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(8)), - border: Border.all( + borderRadius: const .all(.circular(8)), + border: .all( color: LinagoraRefColors.material().neutral[90] ?? Colors.black, ), color: widget.responsive.isWebDesktop(context) - ? Theme.of(context).colorScheme.surface + ? colorScheme.surface : LinagoraSysColors.material().onPrimary, ), child: widget.settingsProfileOptions, @@ -185,35 +181,34 @@ class _SettingsProfileViewMobileState extends State { (success) { if (success is GetClientsLoadingUIState) { return Container( - width: double.infinity, + width: .infinity, height: SettingsProfileViewMobileStyle.bottomButtonHeight, padding: SettingsProfileViewMobileStyle.paddingBottomButton, margin: SettingsProfileViewMobileStyle.marginBottomButton, decoration: BoxDecoration( - borderRadius: BorderRadius.circular( + borderRadius: .circular( SettingsProfileViewMobileStyle.bottomButtonRadius, ), - color: Theme.of(context).colorScheme.primary, + color: colorScheme.primary, ), - alignment: Alignment.center, + alignment: .center, child: Row( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: .center, children: [ Transform.scale( scale: SettingsProfileViewMobileStyle.indicatorScale, child: CircularProgressIndicator( - color: Theme.of(context).colorScheme.onPrimary, + color: colorScheme.onPrimary, strokeWidth: SettingsProfileViewMobileStyle .indicatorStrokeWidth, ), ), SettingsProfileViewMobileStyle.paddingIconAndText, Text( - L10n.of(context)!.loadingPleaseWait, - style: Theme.of(context).textTheme.labelLarge - ?.copyWith( - color: Theme.of(context).colorScheme.onPrimary, - ), + l10n.loadingPleaseWait, + style: textTheme.labelLarge?.copyWith( + color: colorScheme.onPrimary, + ), ), ], ), @@ -229,39 +224,36 @@ class _SettingsProfileViewMobileState extends State { splashColor: Colors.transparent, hoverColor: Colors.transparent, child: Container( - width: double.infinity, + width: .infinity, height: SettingsProfileViewMobileStyle.bottomButtonHeight, padding: SettingsProfileViewMobileStyle.paddingBottomButton, margin: SettingsProfileViewMobileStyle.marginBottomButton, decoration: BoxDecoration( - borderRadius: BorderRadius.circular( + borderRadius: .circular( SettingsProfileViewMobileStyle.bottomButtonRadius, ), - color: Theme.of(context).colorScheme.primary, + color: colorScheme.primary, ), - alignment: Alignment.center, + alignment: .center, child: Row( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: .center, children: [ Icon( success.haveMultipleAccounts ? Icons.group_outlined : Icons.person_add_alt_outlined, size: SettingsProfileViewMobileStyle.iconSize, - color: Theme.of(context).colorScheme.onPrimary, + color: colorScheme.onPrimary, ), SettingsProfileViewMobileStyle.paddingIconAndText, Text( success.haveMultipleAccounts - ? L10n.of(context)!.switchAccounts - : L10n.of(context)!.addAnotherAccount, - style: Theme.of(context).textTheme.labelLarge - ?.copyWith( - color: Theme.of( - context, - ).colorScheme.onPrimary, - ), + ? l10n.switchAccounts + : l10n.addAnotherAccount, + style: textTheme.labelLarge?.copyWith( + color: colorScheme.onPrimary, + ), ), ], ), @@ -278,7 +270,7 @@ class _SettingsProfileViewMobileState extends State { ); } - Widget _buildAvatarBackground(BuildContext context) { + Widget _buildAvatarBackground() { return ValueListenableBuilder( valueListenable: widget.currentProfile, builder: (context, profile, _) { @@ -296,16 +288,15 @@ class _SettingsProfileViewMobileState extends State { ); } - Widget _buildGradientOverlay(BuildContext context) { - final sysColor = LinagoraSysColors.material(); + Widget _buildGradientOverlay() { return DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, + begin: .topCenter, + end: .bottomCenter, colors: [ Colors.transparent, - sysColor.onTertiaryContainer.withValues( + LinagoraSysColors.material().onTertiaryContainer.withValues( alpha: widget.animationController.value, ), ], @@ -314,10 +305,10 @@ class _SettingsProfileViewMobileState extends State { ); } - Widget _buildProfileInformation(BuildContext context) { + Widget _buildProfileInformation() { return ValueListenableBuilder( valueListenable: widget.currentProfile, - builder: (context, profile, _) { + builder: (context, Profile? profile, _) { final displayName = profile?.displayName ?? widget.client.mxid(context).localpart ?? @@ -332,13 +323,12 @@ class _SettingsProfileViewMobileState extends State { } }, child: GestureDetector( - behavior: HitTestBehavior.opaque, + behavior: .opaque, onTap: () { if (!isTextSelected) { widget.onAvatarInfoTap.call(); return; } - FocusScope.of(context).unfocus(); }, child: Container( @@ -346,9 +336,9 @@ class _SettingsProfileViewMobileState extends State { begin: SettingsProfileViewMobileStyle.minAvatarBackgroundHeight, end: SettingsProfileViewMobileStyle.maxAvatarBackgroundHeight, ).transform(widget.animationController.value), - padding: const EdgeInsets.symmetric(horizontal: 12), + padding: const .symmetric(horizontal: 12), child: Column( - mainAxisAlignment: MainAxisAlignment.end, + mainAxisAlignment: .end, children: [ const SizedBox( height: SettingsProfileViewMobileStyle.avatarSize, @@ -357,8 +347,8 @@ class _SettingsProfileViewMobileState extends State { const SizedBox(height: 8), Align( alignment: Tween( - begin: Alignment.center, - end: Alignment.centerLeft, + begin: .center, + end: .centerLeft, ).transform(widget.animationController.value), child: Text( displayName, @@ -367,10 +357,10 @@ class _SettingsProfileViewMobileState extends State { begin: sysColors.onSurface, end: sysColors.onPrimary, ).transform(widget.animationController.value), - fontWeight: FontWeight.bold, + fontWeight: .bold, ), maxLines: 2, - overflow: TextOverflow.ellipsis, + overflow: .ellipsis, ), ), const SizedBox(height: 12), diff --git a/lib/pages/settings_dashboard/settings_profile/settings_profile_view_mobile_style.dart b/lib/pages/settings_dashboard/settings_profile/settings_profile_view_mobile_style.dart index a8d1eb1ce3..67f9038d47 100644 --- a/lib/pages/settings_dashboard/settings_profile/settings_profile_view_mobile_style.dart +++ b/lib/pages/settings_dashboard/settings_profile/settings_profile_view_mobile_style.dart @@ -13,12 +13,11 @@ class SettingsProfileViewMobileStyle { static const double dividerHeight = 2; static const int thumbnailSize = 28; - static EdgeInsetsDirectional editIconPadding = - const EdgeInsetsDirectional.all(8); + static EdgeInsetsDirectional editIconPadding = const .all(8); static const bottomButtonHeight = 48.0; - static const paddingBottomButton = EdgeInsets.only(left: 16.0, right: 16.0); - static const marginBottomButton = EdgeInsets.only( + static const EdgeInsets paddingBottomButton = .only(left: 16.0, right: 16.0); + static const EdgeInsets marginBottomButton = .only( bottom: 32.0, left: 16, right: 16, diff --git a/lib/pages/settings_dashboard/settings_profile/settings_profile_view_style.dart b/lib/pages/settings_dashboard/settings_profile/settings_profile_view_style.dart index fa2b32a241..a39c4025a5 100644 --- a/lib/pages/settings_dashboard/settings_profile/settings_profile_view_style.dart +++ b/lib/pages/settings_dashboard/settings_profile/settings_profile_view_style.dart @@ -4,10 +4,10 @@ class SettingsProfileViewStyle { static const sizeIcon = 24.0; static const borderRadius = 20.0; - static const EdgeInsetsDirectional paddingTextButton = - EdgeInsetsDirectional.symmetric(horizontal: 12); + static const EdgeInsetsDirectional paddingTextButton = .symmetric( + horizontal: 12, + ); - static const EdgeInsetsDirectional paddingBody = - EdgeInsetsDirectional.symmetric(horizontal: 16); - static const actionButtonPadding = EdgeInsets.only(top: 8); + static const EdgeInsetsDirectional paddingBody = .symmetric(horizontal: 16); + static const EdgeInsets actionButtonPadding = .only(top: 8); } diff --git a/lib/pages/settings_dashboard/settings_profile/settings_profile_view_web.dart b/lib/pages/settings_dashboard/settings_profile/settings_profile_view_web.dart index 688f7fd573..5c150178d2 100644 --- a/lib/pages/settings_dashboard/settings_profile/settings_profile_view_web.dart +++ b/lib/pages/settings_dashboard/settings_profile/settings_profile_view_web.dart @@ -1,6 +1,7 @@ import 'package:dartz/dartz.dart'; import 'package:fluffychat/app_state/failure.dart'; import 'package:fluffychat/app_state/success.dart'; +import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_profile/settings_profile_view_web_style.dart'; import 'package:fluffychat/presentation/extensions/client_extension.dart'; import 'package:fluffychat/presentation/model/pick_avatar_state.dart'; @@ -10,7 +11,6 @@ import 'package:fluffychat/widgets/mixins/popup_menu_widget_style.dart'; import 'package:fluffychat/widgets/stream_image_view.dart'; import 'package:flutter/material.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; -import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:matrix/matrix.dart'; class SettingsProfileViewWeb extends StatelessWidget { @@ -39,6 +39,10 @@ class SettingsProfileViewWeb extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = L10n.of(context)!; + final theme = Theme.of(context); + final textTheme = theme.textTheme; + return Padding( padding: SettingsProfileViewWebStyle.paddingBody, child: Align( @@ -46,233 +50,268 @@ class SettingsProfileViewWeb extends StatelessWidget { child: SingleChildScrollView( physics: const NeverScrollableScrollPhysics(), child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: .min, + crossAxisAlignment: .start, children: [ - Container( - width: SettingsProfileViewWebStyle.bodyWidth, - padding: SettingsProfileViewWebStyle.paddingWidgetBasicInfo, - clipBehavior: Clip.antiAlias, - decoration: ShapeDecoration( - color: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - SettingsProfileViewWebStyle.radiusCircular, - ), - ), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: - SettingsProfileViewWebStyle.paddingBasicInfoTitle, - child: Text( - L10n.of(context)!.basicInfo, - style: Theme.of(context).textTheme.labelLarge?.copyWith( - color: Theme.of(context).colorScheme.onSurface, - ), - ), - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: SettingsProfileViewWebStyle - .paddingWidgetBasicInfo, - child: Stack( - alignment: AlignmentDirectional.center, - children: [ - const SizedBox( - width: SettingsProfileViewWebStyle.widthSize, - ), - ValueListenableBuilder( - valueListenable: settingsProfileUIState, - builder: (context, uiState, child) => uiState - .fold((failure) => child!, (success) { - if (success - is GetAvatarOnWebUIStateSuccess) { - return ClipOval( - child: SizedBox.fromSize( - size: const Size.fromRadius( - SettingsProfileViewWebStyle - .radiusImageMemory, - ), - child: StreamImageViewer( - matrixFile: success.matrixFile!, - onImageLoaded: onImageLoaded, - ), - ), - ); - } - return child!; - }), - child: ValueListenableBuilder( - valueListenable: currentProfile, - builder: (context, profile, _) { - final displayName = - profile?.displayName ?? - client.mxid(context).localpart ?? - client.mxid(context); - return Material( - elevation: - Theme.of(context) - .appBarTheme - .scrolledUnderElevation ?? - 4, - shadowColor: Theme.of( - context, - ).appBarTheme.shadowColor, - shape: RoundedRectangleBorder( - side: BorderSide( - color: Theme.of(context).dividerColor, - ), - borderRadius: BorderRadius.circular( - AvatarStyle.defaultSize, - ), - ), - child: Avatar( - mxContent: profile?.avatarUrl, - name: displayName, - size: SettingsProfileViewWebStyle - .avatarSize, - fontSize: SettingsProfileViewWebStyle - .avatarFontSize, - ), - ); - }, - ), - ), - if (canEditAvatar) - Positioned( - bottom: SettingsProfileViewWebStyle - .positionedBottomSize, - right: SettingsProfileViewWebStyle - .positionedRightSize, - child: MenuAnchor( - controller: menuController, - style: MenuStyle( - padding: const WidgetStatePropertyAll( - EdgeInsets.zero, - ), - shape: WidgetStatePropertyAll( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - PopupMenuWidgetStyle - .menuBorderRadius, - ), - ), - ), - backgroundColor: WidgetStatePropertyAll( - PopupMenuWidgetStyle.defaultMenuColor( - context, - ), - ), - ), - builder: - ( - BuildContext context, - MenuController menuController, - Widget? child, - ) { - return GestureDetector( - onTap: () => menuController.isOpen - ? menuController.close() - : menuController.open(), - child: Container( - decoration: BoxDecoration( - color: Theme.of( - context, - ).colorScheme.primary, - borderRadius: - BorderRadius.circular( - SettingsProfileViewWebStyle - .avatarSize, - ), - border: Border.all( - color: Theme.of( - context, - ).colorScheme.onPrimary, - width: - SettingsProfileViewWebStyle - .iconEditBorderWidth, - ), - ), - padding: - SettingsProfileViewWebStyle - .paddingEditIcon, - child: Icon( - Icons.edit, - size: - SettingsProfileViewWebStyle - .iconEditSize, - color: Theme.of( - context, - ).colorScheme.onPrimary, - ), - ), - ); - }, - menuChildren: menuChildren ?? [], - ), - ), - ], - ), - ), - Expanded(child: basicInfoWidget), - ], - ), - ], - ), + AvatarAndNameRow( + settingsProfileUIState: settingsProfileUIState, + onImageLoaded: onImageLoaded, + currentProfile: currentProfile, + client: client, + canEditAvatar: canEditAvatar, + menuController: menuController, + menuChildren: menuChildren, + basicInfoWidget: basicInfoWidget, ), Padding( padding: SettingsProfileViewWebStyle.paddingWidgetEditProfileInfo, child: Text( - L10n.of(context)!.editProfileDescriptions, - style: Theme.of(context).textTheme.labelLarge?.copyWith( + l10n.editProfileDescriptions, + style: textTheme.labelLarge?.copyWith( color: LinagoraRefColors.material().tertiary[30], ), ), ), - Container( - width: SettingsProfileViewWebStyle.bodyWidth, + PersonalInfosColumn( + workIdentitiesInfoWidget: workIdentitiesInfoWidget, + ), + ], + ), + ), + ), + ); + } +} + +class PersonalInfosColumn extends StatelessWidget { + const PersonalInfosColumn({ + super.key, + required this.workIdentitiesInfoWidget, + }); + + final Widget workIdentitiesInfoWidget; + + @override + Widget build(BuildContext context) { + final l10n = L10n.of(context)!; + final theme = Theme.of(context); + + return Container( + width: SettingsProfileViewWebStyle.bodyWidth, + padding: SettingsProfileViewWebStyle.paddingWidgetBasicInfo, + clipBehavior: .antiAlias, + decoration: ShapeDecoration( + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: .circular(SettingsProfileViewWebStyle.radiusCircular), + ), + ), + child: Column( + mainAxisSize: .min, + crossAxisAlignment: .start, + children: [ + Padding( + padding: SettingsProfileViewWebStyle.paddingBasicInfoTitle, + child: Text( + l10n.workIdentitiesInfo, + style: theme.textTheme.labelLarge?.copyWith( + color: theme.colorScheme.onSurface, + ), + ), + ), + Padding( + padding: + SettingsProfileViewWebStyle.paddingWorkIdentitiesInfoWidget, + child: workIdentitiesInfoWidget, + ), + ], + ), + ); + } +} + +class AvatarAndNameRow extends StatelessWidget { + const AvatarAndNameRow({ + super.key, + required this.settingsProfileUIState, + required this.onImageLoaded, + required this.currentProfile, + required this.client, + required this.canEditAvatar, + required this.menuController, + required this.menuChildren, + required this.basicInfoWidget, + }); + + final ValueNotifier> settingsProfileUIState; + final Function(MatrixFile) onImageLoaded; + final ValueNotifier currentProfile; + final Client client; + final bool canEditAvatar; + final MenuController? menuController; + final List? menuChildren; + final Widget basicInfoWidget; + + @override + Widget build(BuildContext context) { + final l10n = L10n.of(context)!; + final theme = Theme.of(context); + final appBarTheme = theme.appBarTheme; + final colorScheme = theme.colorScheme; + final textTheme = theme.textTheme; + + return Container( + width: SettingsProfileViewWebStyle.bodyWidth, + padding: SettingsProfileViewWebStyle.paddingWidgetBasicInfo, + clipBehavior: .antiAlias, + decoration: ShapeDecoration( + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: .circular(SettingsProfileViewWebStyle.radiusCircular), + ), + ), + child: Column( + mainAxisSize: .min, + crossAxisAlignment: .start, + children: [ + Padding( + padding: SettingsProfileViewWebStyle.paddingBasicInfoTitle, + child: Text( + l10n.basicInfo, + style: textTheme.labelLarge?.copyWith( + color: colorScheme.onSurface, + ), + ), + ), + Row( + crossAxisAlignment: .start, + children: [ + Padding( padding: SettingsProfileViewWebStyle.paddingWidgetBasicInfo, - clipBehavior: Clip.antiAlias, - decoration: ShapeDecoration( - color: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - SettingsProfileViewWebStyle.radiusCircular, - ), - ), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + child: Stack( + alignment: AlignmentDirectional.center, children: [ - Padding( - padding: - SettingsProfileViewWebStyle.paddingBasicInfoTitle, - child: Text( - L10n.of(context)!.workIdentitiesInfo, - style: Theme.of(context).textTheme.labelLarge?.copyWith( - color: Theme.of(context).colorScheme.onSurface, - ), - ), + const SizedBox( + width: SettingsProfileViewWebStyle.widthSize, ), - Padding( - padding: SettingsProfileViewWebStyle - .paddingWorkIdentitiesInfoWidget, - child: workIdentitiesInfoWidget, + ValueListenableBuilder( + valueListenable: settingsProfileUIState, + builder: (context, uiState, child) => uiState.fold( + (failure) => child!, + (success) { + if (success is GetAvatarOnWebUIStateSuccess) { + return ClipOval( + child: SizedBox.fromSize( + size: const .fromRadius( + SettingsProfileViewWebStyle.radiusImageMemory, + ), + child: StreamImageViewer( + matrixFile: success.matrixFile!, + onImageLoaded: onImageLoaded, + ), + ), + ); + } + return child!; + }, + ), + child: ValueListenableBuilder( + valueListenable: currentProfile, + builder: (context, profile, _) { + final displayName = + profile?.displayName ?? + client.mxid(context).localpart ?? + client.mxid(context); + return Material( + elevation: appBarTheme.scrolledUnderElevation ?? 4, + shadowColor: appBarTheme.shadowColor, + shape: RoundedRectangleBorder( + side: BorderSide(color: theme.dividerColor), + borderRadius: .circular(AvatarStyle.defaultSize), + ), + child: Avatar( + mxContent: profile?.avatarUrl, + name: displayName, + size: SettingsProfileViewWebStyle.avatarSize, + fontSize: + SettingsProfileViewWebStyle.avatarFontSize, + ), + ); + }, + ), ), + if (canEditAvatar) + EditMenuBtn( + menuController: menuController, + menuChildren: menuChildren, + ), ], ), ), + Expanded(child: basicInfoWidget), ], ), + ], + ), + ); + } +} + +class EditMenuBtn extends StatelessWidget { + const EditMenuBtn({ + super.key, + required this.menuController, + required this.menuChildren, + }); + + final MenuController? menuController; + final List? menuChildren; + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + + return Positioned( + bottom: SettingsProfileViewWebStyle.positionedBottomSize, + right: SettingsProfileViewWebStyle.positionedRightSize, + child: MenuAnchor( + controller: menuController, + style: MenuStyle( + padding: const WidgetStatePropertyAll(EdgeInsets.zero), + shape: WidgetStatePropertyAll( + RoundedRectangleBorder( + borderRadius: .circular(PopupMenuWidgetStyle.menuBorderRadius), + ), + ), + backgroundColor: WidgetStatePropertyAll( + PopupMenuWidgetStyle.defaultMenuColor(context), + ), ), + builder: (_, MenuController menuController, _) { + return GestureDetector( + onTap: () => menuController.isOpen + ? menuController.close() + : menuController.open(), + child: Container( + decoration: BoxDecoration( + color: colorScheme.primary, + borderRadius: .circular(SettingsProfileViewWebStyle.avatarSize), + border: .all( + color: colorScheme.onPrimary, + width: SettingsProfileViewWebStyle.iconEditBorderWidth, + ), + ), + padding: SettingsProfileViewWebStyle.paddingEditIcon, + child: Icon( + Icons.edit, + size: SettingsProfileViewWebStyle.iconEditSize, + color: colorScheme.onPrimary, + ), + ), + ); + }, + menuChildren: menuChildren ?? [], ), ); } diff --git a/lib/pages/settings_dashboard/settings_profile/settings_profile_view_web_style.dart b/lib/pages/settings_dashboard/settings_profile/settings_profile_view_web_style.dart index 6728cdf2c8..93dd3bcd9c 100644 --- a/lib/pages/settings_dashboard/settings_profile/settings_profile_view_web_style.dart +++ b/lib/pages/settings_dashboard/settings_profile/settings_profile_view_web_style.dart @@ -13,22 +13,19 @@ class SettingsProfileViewWebStyle { static const double radiusCircular = 16; static const double radiusImageMemory = 48; - static const EdgeInsetsDirectional paddingBody = EdgeInsetsDirectional.all( - 32, - ); + static const EdgeInsetsDirectional paddingBody = .all(32); - static const EdgeInsetsDirectional paddingWidgetBasicInfo = - EdgeInsetsDirectional.all(16); + static const EdgeInsetsDirectional paddingWidgetBasicInfo = .all(16); - static const EdgeInsetsDirectional paddingBasicInfoTitle = - EdgeInsetsDirectional.only(bottom: 32); + static const EdgeInsetsDirectional paddingBasicInfoTitle = .only(bottom: 32); - static const EdgeInsetsDirectional paddingEditIcon = - EdgeInsetsDirectional.all(8); + static const EdgeInsetsDirectional paddingEditIcon = .all(8); - static const EdgeInsetsDirectional paddingWidgetEditProfileInfo = - EdgeInsetsDirectional.symmetric(vertical: 16); + static const EdgeInsetsDirectional paddingWidgetEditProfileInfo = .symmetric( + vertical: 16, + ); - static const EdgeInsetsDirectional paddingWorkIdentitiesInfoWidget = - EdgeInsetsDirectional.only(bottom: 16); + static const EdgeInsetsDirectional paddingWorkIdentitiesInfoWidget = .only( + bottom: 16, + ); } diff --git a/lib/pages/settings_dashboard/settings_security/settings_security.dart b/lib/pages/settings_dashboard/settings_security/settings_security.dart index f5f7c4b2d9..25617a270a 100644 --- a/lib/pages/settings_dashboard/settings_security/settings_security.dart +++ b/lib/pages/settings_dashboard/settings_security/settings_security.dart @@ -1,8 +1,11 @@ import 'dart:async'; import 'dart:convert'; +import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/domain/usecase/recovery/get_recovery_words_interactor.dart'; +import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:fluffychat/pages/bootstrap/bootstrap_dialog.dart'; import 'package:fluffychat/presentation/extensions/client_extension.dart'; import 'package:fluffychat/utils/beautify_string_extension.dart'; @@ -10,19 +13,14 @@ import 'package:fluffychat/utils/clipboard.dart'; import 'package:fluffychat/utils/dialog/twake_dialog.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_file_extension.dart'; import 'package:fluffychat/utils/twake_snackbar.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; - -import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:flutter/services.dart'; import 'package:flutter_app_lock/flutter_app_lock.dart'; -import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; - import 'package:intl/intl.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/config/setting_keys.dart'; -import 'package:fluffychat/widgets/matrix.dart'; import 'settings_security_view.dart'; class SettingsSecurity extends StatefulWidget { @@ -35,8 +33,7 @@ class SettingsSecurity extends StatefulWidget { class SettingsSecurityController extends State { StreamSubscription? ignoredUsersStreamSub; - ValueNotifier> ignoredUsersNotifier = - ValueNotifier>([]); + final ignoredUsersNotifier = ValueNotifier>([]); Client get client => Matrix.read(context).client; @@ -121,9 +118,7 @@ class SettingsSecurityController extends State { ); if (!mounted || input == null) return; final success = await TwakeDialog.showFutureLoadingDialogFullScreen( - future: () => Matrix.of( - context, - ).client.changePassword(input.last, oldPassword: input.first), + future: () => client.changePassword(input.last, oldPassword: input.first), ); if (!mounted) return; if (success.error == null) { @@ -136,9 +131,11 @@ class SettingsSecurityController extends State { final currentLock = await const FlutterSecureStorage().read( key: SettingKeys.appLockKey, ); + final appLock = AppLock.of(context)!; + if (!mounted) return; if (currentLock?.isNotEmpty ?? false) { - await AppLock.of(context)!.showLockScreen(); + await appLock.showLockScreen(); } if (!mounted) return; final newLock = await showTextInputDialog( @@ -156,7 +153,7 @@ class SettingsSecurityController extends State { } return l10n.pleaseEnter4Digits; }, - keyboardType: TextInputType.number, + keyboardType: .number, obscureText: true, maxLines: 1, minLines: 1, @@ -170,14 +167,14 @@ class SettingsSecurityController extends State { ); if (!mounted) return; if (newLock.single.isEmpty) { - AppLock.of(context)!.disable(); + appLock.disable(); } else { - AppLock.of(context)!.enable(); + appLock.enable(); } } - void showBootstrapDialog(BuildContext context) async { - await BootstrapDialog(client: Matrix.of(context).client).show(); + void showBootstrapDialog() async { + await BootstrapDialog(client: client).show(); } Future dehydrateAction() => dehydrateDevice(context); @@ -212,9 +209,7 @@ class SettingsSecurityController extends State { } Future copyPublicKey() async { - TwakeClipboard.instance.copyText( - Matrix.of(context).client.fingerprintKey.beautified, - ); + TwakeClipboard.instance.copyText(client.fingerprintKey.beautified); TwakeSnackBar.show(context, L10n.of(context)!.copiedPublicKeyToClipboard); } diff --git a/lib/pages/settings_dashboard/settings_security/settings_security_view.dart b/lib/pages/settings_dashboard/settings_security/settings_security_view.dart index 08517b1d83..2991c1fc02 100644 --- a/lib/pages/settings_dashboard/settings_security/settings_security_view.dart +++ b/lib/pages/settings_dashboard/settings_security/settings_security_view.dart @@ -31,6 +31,7 @@ class SettingsSecurityView extends StatelessWidget { final refColorTertiary30 = LinagoraRefColors.material().tertiary[30]; final sysColor = LinagoraSysColors.material(); final linagoraTextStyleBodyMedium = LinagoraTextStyle.material().bodyMedium; + final client = Matrix.of(context).client; return Scaffold( backgroundColor: sysColor.onPrimary, @@ -98,14 +99,16 @@ class SettingsSecurityView extends StatelessWidget { : ignoredUsers.length.toString(), leadingWidget: SvgPicture.asset( ImagePaths.icFrontHand, - colorFilter: ColorFilter.mode( + colorFilter: .mode( refColorTertiary30 ?? sysColor.onSurface, BlendMode.srcIn, ), ), onTap: () { if (ignoredUsers.isNotEmpty) { - context.push('/rooms/security/blockedUsers'); + context.push( + AppRoutePaths.securityBlockedUsersFull, + ); } }, ); @@ -147,7 +150,7 @@ class SettingsSecurityView extends StatelessWidget { ImagePaths.icRecoveryKey, colorFilter: ColorFilter.mode( refColorTertiary30 ?? sysColor.onSurface, - BlendMode.srcIn, + .srcIn, ), ), isHideTrailingIcon: true, @@ -173,7 +176,7 @@ class SettingsSecurityView extends StatelessWidget { ); }, ), - if (Matrix.of(context).client.encryption != null) ...{ + if (client.encryption != null) ...{ if (PlatformInfos.isMobile) Column( children: [ @@ -206,9 +209,7 @@ class SettingsSecurityView extends StatelessWidget { height: 116, title: l10n.yourPublicKey, titleColor: colorScheme.onBackground, - subtitle: Matrix.of( - context, - ).client.fingerprintKey.beautified, + subtitle: client.fingerprintKey.beautified, subtitleStyle: linagoraTextStyleBodyMedium.copyWith( color: refColorTertiary30, fontFamily: 'monospace', diff --git a/lib/pages/settings_dashboard/settings_stories/settings_stories.dart b/lib/pages/settings_dashboard/settings_stories/settings_stories.dart index 10e6e67c5d..c42a03a6a3 100644 --- a/lib/pages/settings_dashboard/settings_stories/settings_stories.dart +++ b/lib/pages/settings_dashboard/settings_stories/settings_stories.dart @@ -1,10 +1,9 @@ +import 'package:fluffychat/pages/settings_dashboard/settings_stories/settings_stories_view.dart'; import 'package:fluffychat/utils/dialog/twake_dialog.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; - import 'package:matrix/matrix.dart'; -import 'package:fluffychat/pages/settings_dashboard/settings_stories/settings_stories_view.dart'; -import 'package:fluffychat/widgets/matrix.dart'; class SettingsStories extends StatefulWidget { const SettingsStories({super.key}); @@ -34,9 +33,7 @@ class SettingsStoriesController extends State { future: () async { await user.kick(); await room.client.setStoriesBlockList(blockList.toSet().toList()); - setState(() { - users[user] = false; - }); + setState(() => users[user] = false); }, ); return; @@ -49,27 +46,25 @@ class SettingsStoriesController extends State { future: () async { await room.client.setStoriesBlockList(blockList); await room.invite(user.id); - setState(() { - users[user] = true; - }); + setState(() => users[user] = true); }, ); return; } Future _loadUsers() async { - final room = _storiesRoom = await Matrix.of( - context, - ).client.getStoriesRoom(context); + final client = Matrix.of(context).client; + + final room = _storiesRoom = await client.getStoriesRoom(context); if (room == null) { noStoriesRoom = true; return; } final users = await room.requestParticipants(); users.removeWhere((u) => u.id == room.client.userID); - final contacts = Matrix.of( - context, - ).client.contacts.where((contact) => !users.any((u) => u.id == contact.id)); + final contacts = client.contacts.where( + (contact) => !users.any((u) => u.id == contact.id), + ); for (final user in contacts) { this.users[user] = false; } @@ -83,12 +78,10 @@ class SettingsStoriesController extends State { void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { - setState(() { - loadUsers = _loadUsers(); - }); + setState(() => loadUsers = _loadUsers()); }); } @override - Widget build(BuildContext context) => SettingsStoriesView(this); + Widget build(_) => SettingsStoriesView(this); } diff --git a/lib/pages/settings_dashboard/settings_stories/settings_stories_view.dart b/lib/pages/settings_dashboard/settings_stories/settings_stories_view.dart index 763bf92aef..48dc1844c3 100644 --- a/lib/pages/settings_dashboard/settings_stories/settings_stories_view.dart +++ b/lib/pages/settings_dashboard/settings_stories/settings_stories_view.dart @@ -1,10 +1,8 @@ -import 'package:flutter/material.dart'; - import 'package:fluffychat/generated/l10n/app_localizations.dart'; - import 'package:fluffychat/pages/settings_dashboard/settings_stories/settings_stories.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/widgets/avatar/avatar.dart'; +import 'package:flutter/material.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; class SettingsStoriesView extends StatelessWidget { @@ -13,20 +11,24 @@ class SettingsStoriesView extends StatelessWidget { @override Widget build(BuildContext context) { + final onPrimary = LinagoraSysColors.material().onPrimary; + final l10n = L10n.of(context)!; + final theme = Theme.of(context); + return Scaffold( - backgroundColor: LinagoraSysColors.material().onPrimary, + backgroundColor: onPrimary, appBar: AppBar( - backgroundColor: LinagoraSysColors.material().onPrimary, - title: Text(L10n.of(context)!.whoCanSeeMyStories), + backgroundColor: onPrimary, + title: Text(l10n.whoCanSeeMyStories), elevation: 0, ), body: Column( children: [ ListTile( - title: Text(L10n.of(context)!.whoCanSeeMyStoriesDesc), + title: Text(l10n.whoCanSeeMyStoriesDesc), leading: CircleAvatar( - backgroundColor: Theme.of(context).secondaryHeaderColor, - foregroundColor: Theme.of(context).colorScheme.secondary, + backgroundColor: theme.secondaryHeaderColor, + foregroundColor: theme.colorScheme.secondary, child: const Icon(Icons.lock), ), ), @@ -39,14 +41,14 @@ class SettingsStoriesView extends StatelessWidget { if (error != null) { return Center(child: Text(error.toLocalizedString(context))); } - if (snapshot.connectionState != ConnectionState.done) { + if (snapshot.connectionState != .done) { return const Center( child: CircularProgressIndicator.adaptive(strokeWidth: 2), ); } return ListView.builder( itemCount: controller.users.length, - itemBuilder: (context, i) { + itemBuilder: (_, i) { final user = controller.users.keys.toList()[i]; return SwitchListTile.adaptive( value: controller.users[user] ?? false, diff --git a/lib/pages/settings_dashboard/settings_style/settings_style.dart b/lib/pages/settings_dashboard/settings_style/settings_style.dart index 445c76aedd..0bf90d8845 100644 --- a/lib/pages/settings_dashboard/settings_style/settings_style.dart +++ b/lib/pages/settings_dashboard/settings_style/settings_style.dart @@ -1,10 +1,11 @@ import 'package:collection/collection.dart'; import 'package:file_picker/file_picker.dart'; -import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/material.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/setting_keys.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/theme_builder.dart'; +import 'package:flutter/material.dart'; + import 'settings_style_view.dart'; class SettingsStyle extends StatefulWidget { @@ -16,31 +17,30 @@ class SettingsStyle extends StatefulWidget { class SettingsStyleController extends State { void setWallpaperAction() async { - final picked = await FilePicker.platform.pickFiles(type: FileType.image); + final picked = await FilePicker.platform.pickFiles(type: .image); final pickedFile = picked?.files.firstOrNull; if (pickedFile == null) return; - await Matrix.of( - context, - ).store.setItem(SettingKeys.wallpaper, pickedFile.path); + await matrix.store.setItem(SettingKeys.wallpaper, pickedFile.path); setState(() {}); } void deleteWallpaperAction() async { - Matrix.of(context).wallpaper = null; - await Matrix.of(context).store.deleteItem(SettingKeys.wallpaper); + matrix.wallpaper = null; + await matrix.store.deleteItem(SettingKeys.wallpaper); setState(() {}); } void setChatColor(Color? color) async { - if (color != null) { - AppConfig.colorSchemeSeed = color; - ThemeController.of(context).setPrimaryColor(color); - } + if (color == null) return; + AppConfig.colorSchemeSeed = color; + controller.setPrimaryColor(color); } - ThemeMode get currentTheme => ThemeController.of(context).themeMode; - Color? get currentColor => ThemeController.of(context).primaryColor; + MatrixState get matrix => Matrix.of(context); + ThemeController get controller => ThemeController.of(context); + ThemeMode get currentTheme => controller.themeMode; + Color? get currentColor => controller.primaryColor; static final List customColors = [ AppConfig.chatColor, @@ -55,14 +55,14 @@ class SettingsStyleController extends State { void switchTheme(ThemeMode? newTheme) { if (newTheme == null) return; switch (newTheme) { - case ThemeMode.light: - ThemeController.of(context).setThemeMode(ThemeMode.light); + case .light: + controller.setThemeMode(.light); break; - case ThemeMode.dark: - ThemeController.of(context).setThemeMode(ThemeMode.dark); + case .dark: + controller.setThemeMode(.dark); break; - case ThemeMode.system: - ThemeController.of(context).setThemeMode(ThemeMode.system); + case .system: + controller.setThemeMode(.system); break; } setState(() {}); @@ -70,7 +70,7 @@ class SettingsStyleController extends State { void changeFontSizeFactor(double d) { setState(() => AppConfig.fontSizeFactor = d); - Matrix.of(context).store.setItem( + matrix.store.setItem( SettingKeys.fontSizeFactor, AppConfig.fontSizeFactor.toString(), ); @@ -78,12 +78,12 @@ class SettingsStyleController extends State { void changeBubbleSizeFactor(double d) { setState(() => AppConfig.bubbleSizeFactor = d); - Matrix.of(context).store.setItem( + matrix.store.setItem( SettingKeys.bubbleSizeFactor, AppConfig.bubbleSizeFactor.toString(), ); } @override - Widget build(BuildContext context) => SettingsStyleView(this); + Widget build(_) => SettingsStyleView(this); } diff --git a/lib/pages/settings_dashboard/settings_style/settings_style_view.dart b/lib/pages/settings_dashboard/settings_style/settings_style_view.dart index a1c8f01adf..f93ef3f0d7 100644 --- a/lib/pages/settings_dashboard/settings_style/settings_style_view.dart +++ b/lib/pages/settings_dashboard/settings_style/settings_style_view.dart @@ -1,11 +1,10 @@ import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/material.dart'; - import 'package:fluffychat/generated/l10n/app_localizations.dart'; - import 'package:fluffychat/widgets/layouts/max_width_body.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; + import 'settings_style.dart'; class SettingsStyleView extends StatelessWidget { @@ -17,12 +16,17 @@ class SettingsStyleView extends StatelessWidget { Widget build(BuildContext context) { const colorPickerSize = 32.0; final wallpaper = Matrix.of(context).wallpaper; + final onPrimary = LinagoraSysColors.material().onPrimary; + final l10n = L10n.of(context)!; + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; + return Scaffold( - backgroundColor: LinagoraSysColors.material().onPrimary, + backgroundColor: onPrimary, appBar: AppBar( - backgroundColor: LinagoraSysColors.material().onPrimary, + backgroundColor: onPrimary, leading: const BackButton(), - title: Text(L10n.of(context)!.changeTheme), + title: Text(l10n.changeTheme), ), body: MaxWidthBody( withScrolling: true, @@ -32,20 +36,18 @@ class SettingsStyleView extends StatelessWidget { height: colorPickerSize + 24, child: ListView( shrinkWrap: true, - scrollDirection: Axis.horizontal, + scrollDirection: .horizontal, children: SettingsStyleController.customColors .map( (color) => Padding( - padding: const EdgeInsets.all(12.0), + padding: const .all(12.0), child: InkWell( - borderRadius: BorderRadius.circular(colorPickerSize), + borderRadius: .circular(colorPickerSize), onTap: () => controller.setChatColor(color), child: color == null ? Material( elevation: 0, - borderRadius: BorderRadius.circular( - colorPickerSize, - ), + borderRadius: .circular(colorPickerSize), child: Image.asset( 'assets/colors.png', width: colorPickerSize, @@ -55,9 +57,7 @@ class SettingsStyleView extends StatelessWidget { : Material( color: color, elevation: 6, - borderRadius: BorderRadius.circular( - colorPickerSize, - ), + borderRadius: .circular(colorPickerSize), child: SizedBox( width: colorPickerSize, height: colorPickerSize, @@ -81,45 +81,45 @@ class SettingsStyleView extends StatelessWidget { const Divider(height: 1), RadioListTile( groupValue: controller.currentTheme, - value: ThemeMode.system, - title: Text(L10n.of(context)!.systemTheme), + value: .system, + title: Text(l10n.systemTheme), onChanged: controller.switchTheme, ), RadioListTile( groupValue: controller.currentTheme, - value: ThemeMode.light, - title: Text(L10n.of(context)!.lightTheme), + value: .light, + title: Text(l10n.lightTheme), onChanged: controller.switchTheme, ), RadioListTile( groupValue: controller.currentTheme, - value: ThemeMode.dark, - title: Text(L10n.of(context)!.darkTheme), + value: .dark, + title: Text(l10n.darkTheme), onChanged: controller.switchTheme, ), const Divider(height: 1), ListTile( title: Text( - L10n.of(context)!.wallpaper, + l10n.wallpaper, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, + color: colorScheme.secondary, + fontWeight: .bold, ), ), ), if (wallpaper != null) ListTile( - title: Image.file(wallpaper, height: 38, fit: BoxFit.cover), + title: Image.file(wallpaper, height: 38, fit: .cover), trailing: const Icon(Icons.delete_outlined, color: Colors.red), onTap: controller.deleteWallpaperAction, ), Builder( builder: (context) { return ListTile( - title: Text(L10n.of(context)!.changeWallpaper), + title: Text(l10n.changeWallpaper), trailing: Icon( Icons.photo_outlined, - color: Theme.of(context).textTheme.bodyLarge?.color, + color: theme.textTheme.bodyLarge?.color, ), onTap: controller.setWallpaperAction, ); @@ -128,29 +128,27 @@ class SettingsStyleView extends StatelessWidget { const Divider(height: 1), ListTile( title: Text( - L10n.of(context)!.messages, + l10n.messages, style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, + color: colorScheme.secondary, + fontWeight: .bold, ), ), ), Container( - alignment: Alignment.centerLeft, - padding: const EdgeInsets.symmetric(horizontal: 12), + alignment: .centerLeft, + padding: const .symmetric(horizontal: 12), child: Material( - color: Theme.of(context).colorScheme.primary, + color: colorScheme.primary, elevation: 6, - shadowColor: Theme.of( - context, - ).secondaryHeaderColor.withAlpha(100), - borderRadius: BorderRadius.circular(AppConfig.borderRadius), + shadowColor: theme.secondaryHeaderColor.withAlpha(100), + borderRadius: .circular(AppConfig.borderRadius), child: Padding( - padding: EdgeInsets.all(16 * AppConfig.bubbleSizeFactor), + padding: .all(16 * AppConfig.bubbleSizeFactor), child: Text( 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor', style: TextStyle( - color: Theme.of(context).colorScheme.onPrimary, + color: colorScheme.onPrimary, fontSize: AppConfig.messageFontSize * AppConfig.fontSizeFactor, ), @@ -159,7 +157,7 @@ class SettingsStyleView extends StatelessWidget { ), ), ListTile( - title: Text(L10n.of(context)!.fontSize), + title: Text(l10n.fontSize), trailing: Text('× ${AppConfig.fontSizeFactor}'), ), Slider.adaptive( @@ -171,7 +169,7 @@ class SettingsStyleView extends StatelessWidget { onChanged: controller.changeFontSizeFactor, ), ListTile( - title: Text(L10n.of(context)!.bubbleSize), + title: Text(l10n.bubbleSize), trailing: Text('× ${AppConfig.bubbleSizeFactor}'), ), Slider.adaptive( diff --git a/lib/pages/twake_welcome/twake_welcome.dart b/lib/pages/twake_welcome/twake_welcome.dart index 8ed9719466..1ff0dd78f1 100644 --- a/lib/pages/twake_welcome/twake_welcome.dart +++ b/lib/pages/twake_welcome/twake_welcome.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/go_routes/app_route_paths.dart'; import 'package:equatable/equatable.dart'; import 'package:fluffychat/presentation/mixins/connect_page_mixin.dart'; import 'package:fluffychat/pages/twake_welcome/twake_welcome_view.dart'; @@ -40,7 +41,7 @@ class TwakeWelcome extends StatefulWidget { class TwakeWelcomeController extends State with ConnectPageMixin { void goToHomeserverPicker() { if (widget.arg != null && widget.arg?.isAddAnotherAccount == true) { - context.push('/rooms/addaccount/homeserverpicker'); + context.push(AppRoutePaths.addAccountHomeserverPickerFull); } else { context.push('/home/homeserverpicker'); } diff --git a/lib/widgets/layouts/adaptive_layout/adaptive_scaffold_primary_navigation_view.dart b/lib/widgets/layouts/adaptive_layout/adaptive_scaffold_primary_navigation_view.dart index 47c97b99c4..8d08375c3f 100644 --- a/lib/widgets/layouts/adaptive_layout/adaptive_scaffold_primary_navigation_view.dart +++ b/lib/widgets/layouts/adaptive_layout/adaptive_scaffold_primary_navigation_view.dart @@ -1,3 +1,4 @@ +import 'package:fluffychat/config/go_routes/app_route_paths.dart'; import 'package:fluffychat/pages/chat_list/client_chooser_button_style.dart'; import 'package:fluffychat/widgets/avatar/avatar.dart'; import 'package:fluffychat/widgets/layouts/adaptive_layout/adaptive_scaffold_primary_navigation_style.dart'; @@ -73,7 +74,7 @@ class AdaptiveScaffoldPrimaryNavigationView extends StatelessWidget { size: AdaptiveScaffoldPrimaryNavigationStyle.avatarSize, fontSize: ClientChooserButtonStyle.avatarFontSizeInAppBar, - onTap: () => context.go('/rooms/profile'), + onTap: () => context.go(AppRoutePaths.profileFull), ); }, ), From b582498d70d52a8f5511b3d15a23a091acc3d135 Mon Sep 17 00:00:00 2001 From: Clement Guyon Date: Thu, 12 Mar 2026 15:15:49 +0100 Subject: [PATCH 3/3] tests: Add E2E test for recovery key copy-to-clipboard flow --- integration_test/robots/login_robot.dart | 27 +++++-- .../setting/settings_recovery_key_robot.dart | 71 +++++++++++++++++++ .../setting/settings_recovery_key_test.dart | 54 ++++++++++++++ .../settings_security_view.dart | 2 + 4 files changed, 149 insertions(+), 5 deletions(-) create mode 100644 integration_test/robots/setting/settings_recovery_key_robot.dart create mode 100644 integration_test/tests/setting/settings_recovery_key_test.dart diff --git a/integration_test/robots/login_robot.dart b/integration_test/robots/login_robot.dart index fe59c57d5a..6b5761f957 100644 --- a/integration_test/robots/login_robot.dart +++ b/integration_test/robots/login_robot.dart @@ -1,8 +1,11 @@ import 'dart:io'; +import 'package:fluffychat/generated/l10n/app_localizations.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/homeserver_picker/homeserver_picker_view.dart'; import 'package:fluffychat/pages/twake_welcome/twake_welcome.dart'; import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:patrol/patrol.dart'; import '../base/core_robot.dart'; @@ -53,7 +56,9 @@ class LoginRobot extends CoreRobot { } Future tapOnUseYourCompanyServer() async { - await $('Use your company server').tap(); + final context = $.tester.element(find.byType(Scaffold).first); + final l10n = L10n.of(context)!; + await $(l10n.useYourCompanyServer).tap(); } Future enterServerUrl(String serverUrl) async { @@ -61,7 +66,8 @@ class LoginRobot extends CoreRobot { } Future clickOnContinueBtn() async { - const label = 'Continue'; + final context = $.tester.element(find.byType(Scaffold).first); + final label = L10n.of(context)!.continueProcess; await $.waitUntilVisible($(label)); await $.tap($(label)); await waitUntilAbsent( @@ -182,8 +188,15 @@ class LoginRobot extends CoreRobot { // set a delay for verifying Captcha await Future.delayed(const Duration(seconds: 2)); - // tap on Sign in - await $.native.tap(getSignInBtn(), appId: getBrowserAppId()); + // tap on Sign in – the browser modal may close immediately after a + // successful login, causing Patrol to report an error even though the + // tap succeeded. We catch that error and let the flow continue. + try { + await $.native.tap(getSignInBtn(), appId: getBrowserAppId()); + } catch (_) { + // Browser closed after successful SSO login – expected. + return; + } // if "verify ...please wait for Captcha" dialog is shown, click OK to continue waiting // and click Sign in again @@ -197,7 +210,11 @@ class LoginRobot extends CoreRobot { getOKBtnInVerifyCaptchaDialog(), appId: getBrowserAppId(), ); - await $.native.tap(getSignInBtn(), appId: getBrowserAppId()); + try { + await $.native.tap(getSignInBtn(), appId: getBrowserAppId()); + } catch (_) { + // Browser closed after successful SSO login – expected. + } } } } diff --git a/integration_test/robots/setting/settings_recovery_key_robot.dart b/integration_test/robots/setting/settings_recovery_key_robot.dart new file mode 100644 index 0000000000..e37f059f08 --- /dev/null +++ b/integration_test/robots/setting/settings_recovery_key_robot.dart @@ -0,0 +1,71 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:patrol/patrol.dart'; + +import 'package:fluffychat/generated/l10n/app_localizations.dart'; + +import '../home_robot.dart'; + +class SettingsRecoveryKeyRobot extends HomeRobot { + SettingsRecoveryKeyRobot(super.$); + + PatrolFinder recoveryKeyItem() { + return $(const Key('recovery_key_settings_item')); + } + + PatrolFinder recoveryKeyCopyButton() { + return $(const Key('recovery_key_copy_button')); + } + + Future waitForRecoveryKeyVisible() async { + await $.waitUntilVisible(recoveryKeyItem()); + } + + /// Taps the copy button, then confirms the warning dialog by tapping "Copy". + Future tapCopyAndConfirm() async { + await recoveryKeyCopyButton().tap(); + await _tapConfirmCopyInDialog(); + } + + /// Taps the recovery key row, then confirms the warning dialog. + Future tapRowAndConfirm() async { + await recoveryKeyItem().tap(); + await _tapConfirmCopyInDialog(); + } + + Future _tapConfirmCopyInDialog() async { + final context = $.tester.element(find.byType(Scaffold).first); + final l10n = L10n.of(context)!; + + if (Platform.isAndroid) { + final copyButton = $( + AlertDialog, + ).$(TextButton).containing(find.text(l10n.copy.toUpperCase())); + await $.waitUntilVisible(copyButton); + await copyButton.tap(); + } else { + final copyButton = $( + CupertinoAlertDialog, + ).$(CupertinoDialogAction).containing(find.text(l10n.copy)); + await $.waitUntilVisible(copyButton); + await copyButton.tap(); + } + } + + /// Reads the current text content from the system clipboard. + Future getClipboardText() async { + final data = await Clipboard.getData(Clipboard.kTextPlain); + return data?.text; + } + + /// Verifies the snackbar "Recovery key copied to clipboard" is shown. + Future verifySnackBarIsShown() async { + final context = $.tester.element(find.byType(Scaffold).first); + final l10n = L10n.of(context)!; + await $.waitUntilVisible($(l10n.recoveryKeyCopiedToClipboard)); + } +} diff --git a/integration_test/tests/setting/settings_recovery_key_test.dart b/integration_test/tests/setting/settings_recovery_key_test.dart new file mode 100644 index 0000000000..b1fd3a165c --- /dev/null +++ b/integration_test/tests/setting/settings_recovery_key_test.dart @@ -0,0 +1,54 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../base/test_base.dart'; +import '../../robots/home_robot.dart'; +import '../../robots/setting/setting_robot.dart'; +import '../../robots/setting/settings_recovery_key_robot.dart'; + +void main() { + TestBase().twakePatrolTest( + description: + 'Copy recovery key and verify clipboard contains the actual key', + test: ($) async { + // Clear clipboard before test + await Clipboard.setData(const ClipboardData(text: '')); + + // Navigate to Settings > Privacy and Security + await HomeRobot($).gotoSettingScreen(); + await SettingRobot($).openPrivacyAndSecuritySetting(); + + final recoveryKeyRobot = SettingsRecoveryKeyRobot($); + + // Verify recovery key item is visible + await recoveryKeyRobot.waitForRecoveryKeyVisible(); + + // Tap the copy button and confirm the warning dialog + await recoveryKeyRobot.tapCopyAndConfirm(); + + // Verify the snackbar confirmation is shown + await recoveryKeyRobot.verifySnackBarIsShown(); + + // Read clipboard content and verify it contains the actual key + final clipboardText = await recoveryKeyRobot.getClipboardText(); + + expect( + clipboardText, + isNotNull, + reason: 'Clipboard should contain the recovery key after copy', + ); + expect( + clipboardText, + isNotEmpty, + reason: 'Recovery key in clipboard should not be empty', + ); + // Ensure the clipboard contains the real key, not the masked bullets + expect( + clipboardText, + isNot(equals('\u2022' * 32)), + reason: + 'Clipboard should contain the actual recovery key, not the masked value', + ); + }, + ); +} diff --git a/lib/pages/settings_dashboard/settings_security/settings_security_view.dart b/lib/pages/settings_dashboard/settings_security/settings_security_view.dart index 2991c1fc02..086dcb1b05 100644 --- a/lib/pages/settings_dashboard/settings_security/settings_security_view.dart +++ b/lib/pages/settings_dashboard/settings_security/settings_security_view.dart @@ -139,6 +139,7 @@ class SettingsSecurityView extends StatelessWidget { Padding( padding: SettingsViewStyle.bodySettingsScreenPadding, child: SettingsItemBuilder( + key: const Key('recovery_key_settings_item'), title: l10n.recoveryKey, titleColor: colorScheme.onBackground, subtitle: '\u2022' * 32, @@ -155,6 +156,7 @@ class SettingsSecurityView extends StatelessWidget { ), isHideTrailingIcon: true, trailingWidget: InkWell( + key: const Key('recovery_key_copy_button'), onTap: controller.copyRecoveryKey, child: const Icon(Icons.content_copy), ),