diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 54ca265e7..ef31c69a3 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -1,8 +1,11 @@ import 'package:fluffychat/pages/archive.dart'; import 'package:fluffychat/pages/homeserver_picker.dart'; import 'package:fluffychat/pages/invitation_selection.dart'; +import 'package:fluffychat/pages/settings_account.dart'; +import 'package:fluffychat/pages/settings_chat.dart'; import 'package:fluffychat/pages/settings_emotes.dart'; import 'package:fluffychat/pages/settings_multiple_emotes.dart'; +import 'package:fluffychat/pages/settings_security.dart'; import 'package:fluffychat/widgets/layouts/side_view_layout.dart'; import 'package:fluffychat/widgets/layouts/two_column_layout.dart'; import 'package:fluffychat/pages/chat.dart'; @@ -219,41 +222,62 @@ class AppRoutes { ]; List get _settingsRoutes => [ - VWidget( - path: 'emotes', - widget: EmotesSettings(), - buildTransition: _dynamicTransition, - ), VWidget( path: 'notifications', widget: SettingsNotifications(), buildTransition: _dynamicTransition, ), VWidget( - path: 'ignorelist', - widget: SettingsIgnoreList(), + path: 'chat', + widget: SettingsChat(), buildTransition: _dynamicTransition, + stackedRoutes: [ + VWidget( + path: 'emotes', + widget: EmotesSettings(), + buildTransition: _dynamicTransition, + ), + VWidget( + path: 'style', + widget: SettingsStyle(), + buildTransition: _dynamicTransition, + ), + ], ), VWidget( - path: 'style', - widget: SettingsStyle(), + path: 'account', + widget: SettingsAccount(), buildTransition: _dynamicTransition, + stackedRoutes: [ + VWidget( + path: 'devices', + widget: DevicesSettings(), + buildTransition: _dynamicTransition, + ), + ], ), VWidget( - path: 'devices', - widget: DevicesSettings(), + path: 'security', + widget: SettingsSecurity(), buildTransition: _dynamicTransition, + stackedRoutes: [ + VWidget( + path: 'ignorelist', + widget: SettingsIgnoreList(), + buildTransition: _dynamicTransition, + ), + VWidget( + path: '3pid', + widget: Settings3Pid(), + buildTransition: _dynamicTransition, + ), + ], ), VWidget( path: '/logs', widget: LogViewer(), buildTransition: _dynamicTransition, ), - VWidget( - path: '3pid', - widget: Settings3Pid(), - buildTransition: _dynamicTransition, - ), ]; final _fadeTransition = (animation1, _, child) => diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index 13d1543ed..2ef756556 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -5,21 +5,15 @@ import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:matrix/matrix.dart'; import 'package:file_picker_cross/file_picker_cross.dart'; -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/sentry_controller.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_app_lock/flutter_app_lock.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:image_picker/image_picker.dart'; import 'views/settings_view.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; -import 'bootstrap_dialog.dart'; import '../widgets/matrix.dart'; -import '../config/app_config.dart'; -import '../config/setting_keys.dart'; class Settings extends StatefulWidget { @override @@ -27,12 +21,12 @@ class Settings extends StatefulWidget { } class SettingsController extends State { - Future profileFuture; - Profile profile; Future crossSigningCachedFuture; bool crossSigningCached; Future megolmBackupCachedFuture; bool megolmBackupCached; + Future profileFuture; + Profile profile; bool profileUpdated = false; void updateProfile() => setState(() { @@ -40,161 +34,6 @@ class SettingsController extends State { profile = profileFuture = null; }); - void logoutAction() async { - if (await showOkCancelAlertDialog( - useRootNavigator: false, - context: context, - title: L10n.of(context).areYouSureYouWantToLogout, - okLabel: L10n.of(context).yes, - cancelLabel: L10n.of(context).cancel, - ) == - OkCancelResult.cancel) { - return; - } - final matrix = Matrix.of(context); - await showFutureLoadingDialog( - context: context, - future: () => matrix.client.logout(), - ); - } - - void changePasswordAccountAction() async { - final input = await showTextInputDialog( - useRootNavigator: false, - context: context, - title: L10n.of(context).changePassword, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - textFields: [ - DialogTextField( - hintText: L10n.of(context).pleaseEnterYourPassword, - obscureText: true, - minLines: 1, - maxLines: 1, - ), - DialogTextField( - hintText: L10n.of(context).chooseAStrongPassword, - obscureText: true, - minLines: 1, - maxLines: 1, - ), - ], - ); - if (input == null) return; - final success = await showFutureLoadingDialog( - context: context, - future: () => Matrix.of(context) - .client - .changePassword(input.last, oldPassword: input.first), - ); - if (success.error == null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(L10n.of(context).passwordHasBeenChanged))); - } - } - - void deleteAccountAction() async { - if (await showOkCancelAlertDialog( - useRootNavigator: false, - context: context, - title: L10n.of(context).warning, - message: L10n.of(context).deactivateAccountWarning, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - ) == - OkCancelResult.cancel) { - return; - } - if (await showOkCancelAlertDialog( - useRootNavigator: false, - context: context, - title: L10n.of(context).areYouSure, - okLabel: L10n.of(context).yes, - cancelLabel: L10n.of(context).cancel, - ) == - OkCancelResult.cancel) { - return; - } - final input = await showTextInputDialog( - useRootNavigator: false, - context: context, - title: L10n.of(context).pleaseEnterYourPassword, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - textFields: [ - DialogTextField( - obscureText: true, - hintText: '******', - minLines: 1, - maxLines: 1, - ) - ], - ); - if (input == null) return; - await showFutureLoadingDialog( - context: context, - future: () => Matrix.of(context).client.deactivateAccount( - auth: AuthenticationPassword( - password: input.single, - user: Matrix.of(context).client.userID, - identifier: AuthenticationUserIdentifier( - user: Matrix.of(context).client.userID), - ), - ), - ); - } - - void setJitsiInstanceAction() async { - const prefix = 'https://'; - final input = await showTextInputDialog( - useRootNavigator: false, - context: context, - title: L10n.of(context).editJitsiInstance, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - textFields: [ - DialogTextField( - initialText: AppConfig.jitsiInstance.replaceFirst(prefix, ''), - prefixText: prefix, - ), - ], - ); - if (input == null) return; - var jitsi = prefix + input.single; - if (!jitsi.endsWith('/')) { - jitsi += '/'; - } - final matrix = Matrix.of(context); - await matrix.store.setItem(SettingKeys.jitsiInstance, jitsi); - AppConfig.jitsiInstance = jitsi; - } - - void setDisplaynameAction() async { - final input = await showTextInputDialog( - useRootNavigator: false, - context: context, - title: L10n.of(context).editDisplayname, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - textFields: [ - DialogTextField( - initialText: profile?.displayname ?? - Matrix.of(context).client.userID.localpart, - ) - ], - ); - if (input == null) return; - final matrix = Matrix.of(context); - final success = await showFutureLoadingDialog( - context: context, - future: () => - matrix.client.setDisplayName(matrix.client.userID, input.single), - ); - if (success.error == null) { - updateProfile(); - } - } - void setAvatarAction() async { final action = profile?.avatarUrl == null ? AvatarAction.change @@ -314,68 +153,6 @@ class SettingsController extends State { } } - void setAppLockAction() async { - final currentLock = - await FlutterSecureStorage().read(key: SettingKeys.appLockKey); - if (currentLock?.isNotEmpty ?? false) { - await AppLock.of(context).showLockScreen(); - } - final newLock = await showTextInputDialog( - useRootNavigator: false, - context: context, - title: L10n.of(context).pleaseChooseAPasscode, - message: L10n.of(context).pleaseEnter4Digits, - cancelLabel: L10n.of(context).cancel, - textFields: [ - DialogTextField( - validator: (text) { - if (text.isEmpty || (text.length == 4 && int.tryParse(text) >= 0)) { - return null; - } - return L10n.of(context).pleaseEnter4Digits; - }, - keyboardType: TextInputType.number, - obscureText: true, - maxLines: 1, - minLines: 1, - ) - ], - ); - if (newLock != null) { - await FlutterSecureStorage() - .write(key: SettingKeys.appLockKey, value: newLock.single); - if (newLock.single.isEmpty) { - AppLock.of(context).disable(); - } else { - AppLock.of(context).enable(); - } - } - } - - void bootstrapSettingsAction() async { - if (await Matrix.of(context).client.encryption.keyManager.isCached()) { - if (OkCancelResult.ok == - await showOkCancelAlertDialog( - useRootNavigator: false, - context: context, - title: L10n.of(context).keysCached, - message: L10n.of(context).wipeChatBackup, - isDestructiveAction: true, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - )) { - await BootstrapDialog( - client: Matrix.of(context).client, - wipe: true, - ).show(context); - } - return; - } - await BootstrapDialog( - client: Matrix.of(context).client, - ).show(context); - } - @override Widget build(BuildContext context) { final client = Matrix.of(context).client; diff --git a/lib/pages/settings_account.dart b/lib/pages/settings_account.dart new file mode 100644 index 000000000..9d9b07c97 --- /dev/null +++ b/lib/pages/settings_account.dart @@ -0,0 +1,163 @@ +import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/setting_keys.dart'; +import 'package:fluffychat/pages/views/settings_account_view.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:future_loading_dialog/future_loading_dialog.dart'; +import 'package:matrix/matrix.dart'; + +class SettingsAccount extends StatefulWidget { + const SettingsAccount({Key key}) : super(key: key); + + @override + SettingsAccountController createState() => SettingsAccountController(); +} + +class SettingsAccountController extends State { + Future profileFuture; + Profile profile; + bool profileUpdated = false; + + void updateProfile() => setState(() { + profileUpdated = true; + profile = profileFuture = null; + }); + + void setDisplaynameAction() async { + final input = await showTextInputDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context).editDisplayname, + okLabel: L10n.of(context).ok, + cancelLabel: L10n.of(context).cancel, + textFields: [ + DialogTextField( + initialText: profile?.displayname ?? + Matrix.of(context).client.userID.localpart, + ) + ], + ); + if (input == null) return; + final matrix = Matrix.of(context); + final success = await showFutureLoadingDialog( + context: context, + future: () => + matrix.client.setDisplayName(matrix.client.userID, input.single), + ); + if (success.error == null) { + updateProfile(); + } + } + + void setJitsiInstanceAction() async { + const prefix = 'https://'; + final input = await showTextInputDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context).editJitsiInstance, + okLabel: L10n.of(context).ok, + cancelLabel: L10n.of(context).cancel, + textFields: [ + DialogTextField( + initialText: AppConfig.jitsiInstance.replaceFirst(prefix, ''), + prefixText: prefix, + ), + ], + ); + if (input == null) return; + var jitsi = prefix + input.single; + if (!jitsi.endsWith('/')) { + jitsi += '/'; + } + final matrix = Matrix.of(context); + await matrix.store.setItem(SettingKeys.jitsiInstance, jitsi); + AppConfig.jitsiInstance = jitsi; + } + + void logoutAction() async { + if (await showOkCancelAlertDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context).areYouSureYouWantToLogout, + okLabel: L10n.of(context).yes, + cancelLabel: L10n.of(context).cancel, + ) == + OkCancelResult.cancel) { + return; + } + final matrix = Matrix.of(context); + await showFutureLoadingDialog( + context: context, + future: () => matrix.client.logout(), + ); + } + + void deleteAccountAction() async { + if (await showOkCancelAlertDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context).warning, + message: L10n.of(context).deactivateAccountWarning, + okLabel: L10n.of(context).ok, + cancelLabel: L10n.of(context).cancel, + ) == + OkCancelResult.cancel) { + return; + } + if (await showOkCancelAlertDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context).areYouSure, + okLabel: L10n.of(context).yes, + cancelLabel: L10n.of(context).cancel, + ) == + OkCancelResult.cancel) { + return; + } + final input = await showTextInputDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context).pleaseEnterYourPassword, + okLabel: L10n.of(context).ok, + cancelLabel: L10n.of(context).cancel, + textFields: [ + DialogTextField( + obscureText: true, + hintText: '******', + minLines: 1, + maxLines: 1, + ) + ], + ); + if (input == null) return; + await showFutureLoadingDialog( + context: context, + future: () => Matrix.of(context).client.deactivateAccount( + auth: AuthenticationPassword( + password: input.single, + user: Matrix.of(context).client.userID, + identifier: AuthenticationUserIdentifier( + user: Matrix.of(context).client.userID), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + final client = Matrix.of(context).client; + profileFuture ??= client + .getProfileFromUserId( + client.userID, + cache: !profileUpdated, + getFromRooms: !profileUpdated, + ) + .then((p) { + if (mounted) setState(() => profile = p); + return p; + }); + return SettingsAccountView(this); + } +} diff --git a/lib/pages/settings_chat.dart b/lib/pages/settings_chat.dart new file mode 100644 index 000000000..4304c7eac --- /dev/null +++ b/lib/pages/settings_chat.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +import 'views/settings_chat_view.dart'; + +class SettingsChat extends StatefulWidget { + const SettingsChat({Key key}) : super(key: key); + + @override + SettingsChatController createState() => SettingsChatController(); +} + +class SettingsChatController extends State { + @override + Widget build(BuildContext context) => SettingsChatView(this); +} diff --git a/lib/pages/settings_security.dart b/lib/pages/settings_security.dart new file mode 100644 index 000000000..3e98e6a24 --- /dev/null +++ b/lib/pages/settings_security.dart @@ -0,0 +1,120 @@ +import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:fluffychat/config/setting_keys.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_app_lock/flutter_app_lock.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:future_loading_dialog/future_loading_dialog.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +import 'bootstrap_dialog.dart'; +import 'views/settings_security_view.dart'; + +class SettingsSecurity extends StatefulWidget { + const SettingsSecurity({Key key}) : super(key: key); + + @override + SettingsSecurityController createState() => SettingsSecurityController(); +} + +class SettingsSecurityController extends State { + void changePasswordAccountAction() async { + final input = await showTextInputDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context).changePassword, + okLabel: L10n.of(context).ok, + cancelLabel: L10n.of(context).cancel, + textFields: [ + DialogTextField( + hintText: L10n.of(context).pleaseEnterYourPassword, + obscureText: true, + minLines: 1, + maxLines: 1, + ), + DialogTextField( + hintText: L10n.of(context).chooseAStrongPassword, + obscureText: true, + minLines: 1, + maxLines: 1, + ), + ], + ); + if (input == null) return; + final success = await showFutureLoadingDialog( + context: context, + future: () => Matrix.of(context) + .client + .changePassword(input.last, oldPassword: input.first), + ); + if (success.error == null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(L10n.of(context).passwordHasBeenChanged))); + } + } + + void setAppLockAction() async { + final currentLock = + await FlutterSecureStorage().read(key: SettingKeys.appLockKey); + if (currentLock?.isNotEmpty ?? false) { + await AppLock.of(context).showLockScreen(); + } + final newLock = await showTextInputDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context).pleaseChooseAPasscode, + message: L10n.of(context).pleaseEnter4Digits, + cancelLabel: L10n.of(context).cancel, + textFields: [ + DialogTextField( + validator: (text) { + if (text.isEmpty || (text.length == 4 && int.tryParse(text) >= 0)) { + return null; + } + return L10n.of(context).pleaseEnter4Digits; + }, + keyboardType: TextInputType.number, + obscureText: true, + maxLines: 1, + minLines: 1, + ) + ], + ); + if (newLock != null) { + await FlutterSecureStorage() + .write(key: SettingKeys.appLockKey, value: newLock.single); + if (newLock.single.isEmpty) { + AppLock.of(context).disable(); + } else { + AppLock.of(context).enable(); + } + } + } + + void bootstrapSettingsAction() async { + if (await Matrix.of(context).client.encryption.keyManager.isCached()) { + if (OkCancelResult.ok == + await showOkCancelAlertDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context).keysCached, + message: L10n.of(context).wipeChatBackup, + isDestructiveAction: true, + okLabel: L10n.of(context).ok, + cancelLabel: L10n.of(context).cancel, + )) { + await BootstrapDialog( + client: Matrix.of(context).client, + wipe: true, + ).show(context); + } + return; + } + await BootstrapDialog( + client: Matrix.of(context).client, + ).show(context); + } + + @override + Widget build(BuildContext context) => SettingsSecurityView(this); +} diff --git a/lib/pages/views/settings_account_view.dart b/lib/pages/views/settings_account_view.dart new file mode 100644 index 000000000..7e6c204c5 --- /dev/null +++ b/lib/pages/views/settings_account_view.dart @@ -0,0 +1,59 @@ +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/widgets/layouts/max_width_body.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:vrouter/vrouter.dart'; +import 'package:matrix/matrix.dart'; + +import '../settings_account.dart'; + +class SettingsAccountView extends StatelessWidget { + final SettingsAccountController controller; + const SettingsAccountView(this.controller, {Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(L10n.of(context).account)), + body: MaxWidthBody( + withScrolling: true, + child: Column( + children: [ + ListTile( + trailing: Icon(Icons.edit_outlined), + title: Text(L10n.of(context).editDisplayname), + subtitle: Text(controller.profile?.displayname ?? + Matrix.of(context).client.userID.localpart), + onTap: controller.setDisplaynameAction, + ), + ListTile( + trailing: Icon(Icons.phone_outlined), + title: Text(L10n.of(context).editJitsiInstance), + subtitle: Text(AppConfig.jitsiInstance), + onTap: controller.setJitsiInstanceAction, + ), + ListTile( + trailing: Icon(Icons.devices_other_outlined), + title: Text(L10n.of(context).devices), + onTap: () => VRouter.of(context).push('devices'), + ), + ListTile( + trailing: Icon(Icons.exit_to_app_outlined), + title: Text(L10n.of(context).logout), + onTap: controller.logoutAction, + ), + ListTile( + trailing: Icon(Icons.delete_forever_outlined), + title: Text( + L10n.of(context).deleteAccount, + style: TextStyle(color: Colors.red), + ), + onTap: controller.deleteAccountAction, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/views/settings_chat_view.dart b/lib/pages/views/settings_chat_view.dart new file mode 100644 index 000000000..b754c88ab --- /dev/null +++ b/lib/pages/views/settings_chat_view.dart @@ -0,0 +1,57 @@ +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/setting_keys.dart'; +import 'package:fluffychat/widgets/layouts/max_width_body.dart'; +import 'package:fluffychat/widgets/settings_switch_list_tile.dart'; +import 'package:flutter/material.dart'; +import 'package:vrouter/vrouter.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +import '../settings_chat.dart'; + +class SettingsChatView extends StatelessWidget { + final SettingsChatController controller; + const SettingsChatView(this.controller, {Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(L10n.of(context).chat)), + body: MaxWidthBody( + withScrolling: true, + child: Column( + children: [ + ListTile( + title: Text(L10n.of(context).changeTheme), + onTap: () => VRouter.of(context).push('style'), + trailing: Icon(Icons.style_outlined), + ), + ListTile( + title: Text(L10n.of(context).emoteSettings), + onTap: () => VRouter.of(context).push('emotes'), + trailing: Icon(Icons.insert_emoticon_outlined), + ), + Divider(height: 1), + SettingsSwitchListTile( + title: L10n.of(context).renderRichContent, + onChanged: (b) => AppConfig.renderHtml = b, + storeKey: SettingKeys.renderHtml, + defaultValue: AppConfig.renderHtml, + ), + SettingsSwitchListTile( + title: L10n.of(context).hideRedactedEvents, + onChanged: (b) => AppConfig.hideRedactedEvents = b, + storeKey: SettingKeys.hideRedactedEvents, + defaultValue: AppConfig.hideRedactedEvents, + ), + SettingsSwitchListTile( + title: L10n.of(context).hideUnknownEvents, + onChanged: (b) => AppConfig.hideUnknownEvents = b, + storeKey: SettingKeys.hideUnknownEvents, + defaultValue: AppConfig.hideUnknownEvents, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/views/settings_security_view.dart b/lib/pages/views/settings_security_view.dart new file mode 100644 index 000000000..23fcdfb04 --- /dev/null +++ b/lib/pages/views/settings_security_view.dart @@ -0,0 +1,73 @@ +import 'package:adaptive_dialog/adaptive_dialog.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_gen/gen_l10n/l10n.dart'; +import 'package:vrouter/vrouter.dart'; + +import 'package:fluffychat/utils/beautify_string_extension.dart'; +import '../settings_security.dart'; + +class SettingsSecurityView extends StatelessWidget { + final SettingsSecurityController controller; + const SettingsSecurityView(this.controller, {Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(L10n.of(context).security)), + body: MaxWidthBody( + withScrolling: true, + child: Column( + children: [ + ListTile( + trailing: Icon(Icons.block_outlined), + title: Text(L10n.of(context).ignoredUsers), + onTap: () => VRouter.of(context).push('ignorelist'), + ), + ListTile( + trailing: Icon(Icons.security_outlined), + title: Text( + L10n.of(context).changePassword, + ), + onTap: controller.changePasswordAccountAction, + ), + ListTile( + trailing: Icon(Icons.email_outlined), + title: Text(L10n.of(context).passwordRecovery), + onTap: () => VRouter.of(context).push('3pid'), + ), + if (Matrix.of(context).client.encryption != null) ...{ + Divider(thickness: 1), + if (PlatformInfos.isMobile) + ListTile( + trailing: Icon(Icons.lock_outlined), + title: Text(L10n.of(context).appLock), + onTap: controller.setAppLockAction, + ), + ListTile( + title: Text(L10n.of(context).yourPublicKey), + onTap: () => showOkAlertDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context).yourPublicKey, + message: Matrix.of(context).client.fingerprintKey.beautified, + okLabel: L10n.of(context).ok, + ), + trailing: Icon(Icons.vpn_key_outlined), + ), + ListTile( + title: Text(L10n.of(context).cachedKeys), + trailing: Icon(Icons.wb_cloudy_outlined), + subtitle: Text( + '${Matrix.of(context).client.encryption.keyManager.enabled ? L10n.of(context).onlineKeyBackupEnabled : L10n.of(context).onlineKeyBackupDisabled}\n${Matrix.of(context).client.encryption.crossSigning.enabled ? L10n.of(context).crossSigningEnabled : L10n.of(context).crossSigningDisabled}'), + onTap: controller.bootstrapSettingsAction, + ), + }, + ], + ), + ), + ); + } +} diff --git a/lib/pages/views/settings_view.dart b/lib/pages/views/settings_view.dart index 61c7f1b07..887b65a03 100644 --- a/lib/pages/views/settings_view.dart +++ b/lib/pages/views/settings_view.dart @@ -1,11 +1,3 @@ -import 'package:adaptive_dialog/adaptive_dialog.dart'; - -import 'package:fluffychat/widgets/sentry_switch_list_tile.dart'; -import 'package:fluffychat/widgets/settings_switch_list_tile.dart'; - -import 'package:matrix/matrix.dart'; -import 'package:fluffychat/utils/beautify_string_extension.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:flutter/material.dart'; @@ -14,9 +6,7 @@ import 'package:url_launcher/url_launcher.dart'; import 'package:vrouter/vrouter.dart'; import '../../widgets/content_banner.dart'; -import '../../widgets/matrix.dart'; import '../../config/app_config.dart'; -import '../../config/setting_keys.dart'; import '../settings.dart'; class SettingsView extends StatelessWidget { @@ -26,7 +16,6 @@ class SettingsView extends StatelessWidget { @override Widget build(BuildContext context) { - final client = Matrix.of(context).client; return Scaffold( body: NestedScrollView( headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) => @@ -49,176 +38,38 @@ class SettingsView extends StatelessWidget { body: ListView( children: [ ListTile( - title: Text( - L10n.of(context).notifications, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - ), - ), - ListTile( - trailing: Icon(Icons.notifications_outlined), + leading: Icon(Icons.notifications_outlined), title: Text(L10n.of(context).notifications), onTap: () => VRouter.of(context).push('/settings/notifications'), ), - Divider(thickness: 1), - ListTile( - title: Text( - L10n.of(context).chat, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - ), - ), - ListTile( - title: Text(L10n.of(context).changeTheme), - onTap: () => VRouter.of(context).push('/settings/style'), - trailing: Icon(Icons.style_outlined), - ), - SettingsSwitchListTile( - title: L10n.of(context).renderRichContent, - onChanged: (b) => AppConfig.renderHtml = b, - storeKey: SettingKeys.renderHtml, - defaultValue: AppConfig.renderHtml, - ), - SettingsSwitchListTile( - title: L10n.of(context).hideRedactedEvents, - onChanged: (b) => AppConfig.hideRedactedEvents = b, - storeKey: SettingKeys.hideRedactedEvents, - defaultValue: AppConfig.hideRedactedEvents, - ), - SettingsSwitchListTile( - title: L10n.of(context).hideUnknownEvents, - onChanged: (b) => AppConfig.hideUnknownEvents = b, - storeKey: SettingKeys.hideUnknownEvents, - defaultValue: AppConfig.hideUnknownEvents, - ), - ListTile( - title: Text(L10n.of(context).emoteSettings), - onTap: () => VRouter.of(context).push('/settings/emotes'), - trailing: Icon(Icons.insert_emoticon_outlined), - ), - Divider(thickness: 1), - ListTile( - title: Text( - L10n.of(context).account, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - ), - ), - ListTile( - trailing: Icon(Icons.edit_outlined), - title: Text(L10n.of(context).editDisplayname), - subtitle: Text( - controller.profile?.displayname ?? client.userID.localpart), - onTap: controller.setDisplaynameAction, - ), - ListTile( - trailing: Icon(Icons.phone_outlined), - title: Text(L10n.of(context).editJitsiInstance), - subtitle: Text(AppConfig.jitsiInstance), - onTap: controller.setJitsiInstanceAction, - ), - ListTile( - trailing: Icon(Icons.devices_other_outlined), - title: Text(L10n.of(context).devices), - onTap: () => VRouter.of(context).push('/settings/devices'), - ), - ListTile( - trailing: Icon(Icons.block_outlined), - title: Text(L10n.of(context).ignoredUsers), - onTap: () => VRouter.of(context).push('/settings/ignorelist'), - ), - SentrySwitchListTile(), - Divider(thickness: 1), - ListTile( - trailing: Icon(Icons.security_outlined), - title: Text( - L10n.of(context).changePassword, - ), - onTap: controller.changePasswordAccountAction, - ), ListTile( - trailing: Icon(Icons.email_outlined), - title: Text(L10n.of(context).passwordRecovery), - onTap: () => VRouter.of(context).push('/settings/3pid'), + leading: Icon(Icons.chat_bubble_outline), + title: Text(L10n.of(context).chat), + onTap: () => VRouter.of(context).push('/settings/chat'), ), ListTile( - trailing: Icon(Icons.exit_to_app_outlined), - title: Text(L10n.of(context).logout), - onTap: controller.logoutAction, + leading: Icon(Icons.account_box_outlined), + title: Text(L10n.of(context).account), + onTap: () => VRouter.of(context).push('/settings/account'), ), ListTile( - trailing: Icon(Icons.delete_forever_outlined), - title: Text( - L10n.of(context).deleteAccount, - style: TextStyle(color: Colors.red), - ), - onTap: controller.deleteAccountAction, + leading: Icon(Icons.security_outlined), + title: Text(L10n.of(context).security), + onTap: () => VRouter.of(context).push('/settings/security'), ), - if (client.encryption != null) ...{ - Divider(thickness: 1), - ListTile( - title: Text( - L10n.of(context).security, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - ), - ), - if (PlatformInfos.isMobile) - ListTile( - trailing: Icon(Icons.lock_outlined), - title: Text(L10n.of(context).appLock), - onTap: controller.setAppLockAction, - ), - ListTile( - title: Text(L10n.of(context).yourPublicKey), - onTap: () => showOkAlertDialog( - useRootNavigator: false, - context: context, - title: L10n.of(context).yourPublicKey, - message: client.fingerprintKey.beautified, - okLabel: L10n.of(context).ok, - ), - trailing: Icon(Icons.vpn_key_outlined), - ), - ListTile( - title: Text(L10n.of(context).cachedKeys), - trailing: Icon(Icons.wb_cloudy_outlined), - subtitle: Text( - '${client.encryption.keyManager.enabled ? L10n.of(context).onlineKeyBackupEnabled : L10n.of(context).onlineKeyBackupDisabled}\n${client.encryption.crossSigning.enabled ? L10n.of(context).crossSigningEnabled : L10n.of(context).crossSigningDisabled}'), - onTap: controller.bootstrapSettingsAction, - ), - }, Divider(thickness: 1), ListTile( - title: Text( - L10n.of(context).about, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - ), - onTap: () => VRouter.of(context).push('/logs'), - ), - ListTile( - trailing: Icon(Icons.help_outlined), + leading: Icon(Icons.help_outlined), title: Text(L10n.of(context).help), onTap: () => launch(AppConfig.supportUrl), ), ListTile( - trailing: Icon(Icons.privacy_tip_outlined), + leading: Icon(Icons.privacy_tip_outlined), title: Text(L10n.of(context).privacy), onTap: () => launch(AppConfig.privacyUrl), ), ListTile( - trailing: Icon(Icons.link_outlined), + leading: Icon(Icons.link_outlined), title: Text(L10n.of(context).about), onTap: () => PlatformInfos.showDialog(context), ),