diff --git a/assets/l10n/intl_ar.arb b/assets/l10n/intl_ar.arb index 59d320458..57d7a4252 100644 --- a/assets/l10n/intl_ar.arb +++ b/assets/l10n/intl_ar.arb @@ -2616,5 +2616,7 @@ "appname": {}, "unread": {} } - } + }, + "noDatabaseEncryption": "تشفير قاعدة البيانات غير مدعوم على هذا النظام الأساسي", + "@noDatabaseEncryption": {} } diff --git a/assets/l10n/intl_de.arb b/assets/l10n/intl_de.arb index 13bf1c48a..221886f84 100644 --- a/assets/l10n/intl_de.arb +++ b/assets/l10n/intl_de.arb @@ -2586,5 +2586,95 @@ } }, "verifyOtherDeviceDescription": "Wenn Sie ein anderes Gerät verifizieren, können diese Geräteschlüssel austauschen, was Ihre Sicherheit insgesamt erhöht. 💪 Wenn Sie eine Verifizierung starten, erscheint ein Pop-up in der App auf beiden Geräten. Dort sehen Sie dann eine Reihe von Emojis oder Zahlen, die Sie miteinander vergleichen müssen. Am besten hältst du beide Geräte bereit, bevor du die Verifizierung startest. 🤳", - "@verifyOtherDeviceDescription": {} + "@verifyOtherDeviceDescription": {}, + "presenceStyle": "Statusmeldungen:", + "@presenceStyle": { + "type": "text", + "placeholders": {} + }, + "presencesToggle": "Status-Nachrichten anderer Benutzer anzeigen", + "@presencesToggle": { + "type": "text", + "placeholders": {} + }, + "incomingMessages": "Eingehende Nachrichten", + "@incomingMessages": {}, + "commandHint_unignore": "Angegebene Matrix-ID nicht mehr ignorieren", + "@commandHint_unignore": {}, + "commandHint_ignore": "Angegebene Matrix-ID ignorieren", + "@commandHint_ignore": {}, + "noDatabaseEncryption": "Datenbankverschlüsselung wird auf dieser Plattform nicht unterstützt", + "@noDatabaseEncryption": {}, + "hidePresences": "Status-Liste verbergen?", + "@hidePresences": {}, + "stickers": "Sticker", + "@stickers": {}, + "discover": "Entdecken", + "@discover": {}, + "unreadChatsInApp": "{appname}: {unread} ungelesene Chats", + "@unreadChatsInApp": { + "type": "text", + "placeholders": { + "appname": {}, + "unread": {} + } + }, + "customEmojisAndStickersBody": "Eigene Emojis oder Sticker zur Nutzung im Chat hinzufügen oder teilen.", + "@customEmojisAndStickersBody": {}, + "globalChatId": "Globale Chat-ID", + "@globalChatId": {}, + "accessAndVisibility": "Zugang und Sichtbarkeit", + "@accessAndVisibility": {}, + "hideMemberChangesInPublicChats": "Mitglieder-Änderungen in öffentlichen Chats ausblenden", + "@hideMemberChangesInPublicChats": {}, + "accessAndVisibilityDescription": "Wer darf dem Chat beitreten und wie kann der Chat gefunden werden.", + "@accessAndVisibilityDescription": {}, + "hideMemberChangesInPublicChatsBody": "Zeige keine Beitritt- oder Verlassen-Ereignisse von Mitgliedern in der Timeline an, um die Lesbarkeit in öffentlichen Chats zu verbessern.", + "@hideMemberChangesInPublicChatsBody": {}, + "userWouldLikeToChangeTheChat": "{user} würde dem Chat gerne beitreten.", + "@userWouldLikeToChangeTheChat": { + "placeholders": { + "user": {} + } + }, + "noPublicLinkHasBeenCreatedYet": "Es wurde noch kein öffentlicher Link erstellt", + "@noPublicLinkHasBeenCreatedYet": {}, + "chatCanBeDiscoveredViaSearchOnServer": "Chat kann über die Suche auf {server} gefunden werden", + "@chatCanBeDiscoveredViaSearchOnServer": { + "type": "text", + "placeholders": { + "server": {} + } + }, + "appLockDescription": "App mit einer PIN sperren, wenn sie nicht verwendet wird", + "@appLockDescription": {}, + "calls": "Anrufe", + "@calls": {}, + "customEmojisAndStickers": "Eigene Emojis und Sticker", + "@customEmojisAndStickers": {}, + "hideRedactedMessages": "Geschwärzte Nachrichten verstecken", + "@hideRedactedMessages": {}, + "hideRedactedMessagesBody": "Wenn jemand eine Nachricht schwärzt/löscht, dann wird diese Nachricht im Chat nicht mehr sichtbar sein.", + "@hideRedactedMessagesBody": {}, + "hideInvalidOrUnknownMessageFormats": "Ungültige und unbekannte Nachrichten-Formate ausblenden", + "@hideInvalidOrUnknownMessageFormats": {}, + "overview": "Übersicht", + "@overview": {}, + "notifyMeFor": "Benachrichtige mich für", + "@notifyMeFor": {}, + "passwordRecoverySettings": "Passwort-Wiederherstellungs-Einstellungen", + "@passwordRecoverySettings": {}, + "knock": "Anklopfen", + "@knock": {}, + "knocking": "Klopft", + "@knocking": {}, + "thereAreCountUsersBlocked": "Im Augenblick werden {count} Benutzer blockiert.", + "@thereAreCountUsersBlocked": { + "type": "text", + "count": {} + }, + "usersMustKnock": "Benutzer müssen anklopfen", + "@usersMustKnock": {}, + "noOneCanJoin": "Niemand kann beitreten", + "@noOneCanJoin": {} } diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 84703a551..3ec89e6b1 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -136,6 +136,7 @@ "type": "text", "placeholders": {} }, + "appLockDescription": "Lock the app when not using with a pin code", "archive": "Archive", "@archive": { "type": "text", @@ -773,6 +774,12 @@ "type": "text", "placeholders": {} }, + "globalChatId": "Global chat ID", + "accessAndVisibility": "Access and visibility", + "accessAndVisibilityDescription": "Who is allowed to join this chat and how the chat can be discovered.", + "calls": "Calls", + "customEmojisAndStickers": "Custom emojis and stickers", + "customEmojisAndStickersBody": "Add or share custom emojis or stickers which can be used in any chat.", "emoteShortcode": "Emote shortcode", "@emoteShortcode": { "type": "text", @@ -958,7 +965,9 @@ "type": "text", "placeholders": {} }, - "hideUnknownEvents": "Hide unknown events", + "hideRedactedMessages": "Hide redacted messages", + "hideRedactedMessagesBody": "If someone redacts a message, this message won't be visible in the chat anymore.", + "hideInvalidOrUnknownMessageFormats": "Hide invalid or unknown message formats", "@hideUnknownEvents": { "type": "text", "placeholders": {} @@ -1426,6 +1435,11 @@ "type": "text", "placeholders": {} }, + "hideMemberChangesInPublicChats": "Hide member changes in public chats", + "hideMemberChangesInPublicChatsBody": "Do not show in the chat timeline if someone joins or leaves a public chat to improve readability.", + "overview": "Overview", + "notifyMeFor": "Notify me for", + "passwordRecoverySettings": "Password recovery settings", "passwordRecovery": "Password recovery", "@passwordRecovery": { "type": "text", @@ -2306,12 +2320,22 @@ "user": {} } }, - "hasKnocked": "{user} has knocked", + "hasKnocked": "🚪 {user} has knocked", "@hasKnocked": { "placeholders": { "user": {} } }, + "usersMustKnock": "Users must knock", + "noOneCanJoin": "No one can join", + "userWouldLikeToChangeTheChat": "{user} would like to join the chat.", + "@userWouldLikeToChangeTheChat": { + "placeholders": { + "user": {} + } + }, + "noPublicLinkHasBeenCreatedYet": "No public link has been created yet", + "knock": "Knock", "users": "Users", "@users": {}, "unlockOldMessages": "Unlock old messages", @@ -3725,6 +3749,14 @@ "query": {} } }, + "knocking": "Knocking", + "chatCanBeDiscoveredViaSearchOnServer": "Chat can be discovered via the search on {server}", + "@chatCanBeDiscoveredViaSearchOnServer": { + "type": "text", + "placeholders": { + "server": {} + } + }, "searchChatsRooms": "Search for #chats, @users...", "createClass": "Create class", "createExchange": "Create exchange", @@ -3970,5 +4002,11 @@ "capacitySetTooLow": "Room capacity cannot be set below the current number of non-admins.", "roomCapacityExplanation": "Room capacity limits the number of non-admins allowed in a room.", "enterNumber": "Please enter a whole number value.", - "buildTranslation": "Build your translation from the choices above" + "buildTranslation": "Build your translation from the choices above", + "noDatabaseEncryption": "Database encryption is not supported on this platform", + "thereAreCountUsersBlocked": "Right now there are {count} users blocked.", + "@thereAreCountUsersBlocked": { + "type": "text", + "count": {} + } } \ No newline at end of file diff --git a/assets/l10n/intl_eu.arb b/assets/l10n/intl_eu.arb index fc29b0f12..f584d2ac2 100644 --- a/assets/l10n/intl_eu.arb +++ b/assets/l10n/intl_eu.arb @@ -2616,5 +2616,7 @@ "appname": {}, "unread": {} } - } + }, + "noDatabaseEncryption": "Plataforma honetan ezin da datu-basea zifratu", + "@noDatabaseEncryption": {} } diff --git a/assets/l10n/intl_gl.arb b/assets/l10n/intl_gl.arb index 0231b9dce..5073a3a9a 100644 --- a/assets/l10n/intl_gl.arb +++ b/assets/l10n/intl_gl.arb @@ -2616,5 +2616,7 @@ "appname": {}, "unread": {} } - } + }, + "noDatabaseEncryption": "Nesta plataforma non temos soporte para cifrar a base de datos", + "@noDatabaseEncryption": {} } diff --git a/assets/l10n/intl_id.arb b/assets/l10n/intl_id.arb index e4eaf6d8c..055814c47 100644 --- a/assets/l10n/intl_id.arb +++ b/assets/l10n/intl_id.arb @@ -2615,5 +2615,7 @@ "appname": {}, "unread": {} } - } + }, + "noDatabaseEncryption": "Enkripsi basis data tidak didukung di platform ini", + "@noDatabaseEncryption": {} } diff --git a/assets/l10n/intl_pt_BR.arb b/assets/l10n/intl_pt_BR.arb index bfbae8f48..3085a4afa 100644 --- a/assets/l10n/intl_pt_BR.arb +++ b/assets/l10n/intl_pt_BR.arb @@ -2484,5 +2484,136 @@ "searchChatsRooms": "Buscar por #conversas, @usuários...", "@searchChatsRooms": {}, "databaseMigrationBody": "Por favor, espere. Isto pode demorar um pouco.", - "@databaseMigrationBody": {} + "@databaseMigrationBody": {}, + "youInvitedToBy": "Você foi convidado através do link para:\n{alias}", + "@youInvitedToBy": { + "placeholders": { + "alias": {} + } + }, + "forwardMessageTo": "Encaminhar mensagem para {roomName}?", + "@forwardMessageTo": { + "type": "text", + "placeholders": { + "roomName": {} + } + }, + "formattedMessagesDescription": "Mostrar mensagens ricas com conteúdos tipo negrito usando markdown.", + "@formattedMessagesDescription": {}, + "verifyOtherUser": "🔐 Verificar outro usuário", + "@verifyOtherUser": {}, + "verifyOtherDevice": "🔐 Verificar outro aparelho", + "@verifyOtherDevice": {}, + "acceptedKeyVerification": "{sender} aceitou sua chave de verificação", + "@acceptedKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "startedKeyVerification": "{sender} iniciou a chave de verificação", + "@startedKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "transparent": "Transparente", + "@transparent": {}, + "databaseBuildErrorBody": "Não foi possível construir o banco de dados SQLite. O aplicativo tentará utilizar o banco de dados legado por enquanto. Por favor, reporte este erro aos desenvolvedores em {url}. A mensagem de erro é: {error}", + "@databaseBuildErrorBody": { + "type": "text", + "placeholders": { + "url": {}, + "error": {} + } + }, + "initAppError": "Ocorreu um erro enquanto o aplicativo era iniciado", + "@initAppError": {}, + "restoreSessionBody": "O aplicativo tentará agora restaurar sua sessão a partir do backup. Por favor, reporte este ao desenvolvedor em {url}. A mensagem de erro é: {error}", + "@restoreSessionBody": { + "type": "text", + "placeholders": { + "url": {}, + "error": {} + } + }, + "sendReadReceipts": "Enviar recibos de leitura", + "@sendReadReceipts": {}, + "sendTypingNotificationsDescription": "Outros participantes neste chat podem ver quando você está digitando uma nova mensagem.", + "@sendTypingNotificationsDescription": {}, + "formattedMessages": "Mensagens formatadas", + "@formattedMessages": {}, + "presenceStyle": "Presença:", + "@presenceStyle": { + "type": "text", + "placeholders": {} + }, + "presencesToggle": "Mostrar o status das mensagens de outros usuários", + "@presencesToggle": { + "type": "text", + "placeholders": {} + }, + "commandHint_ignore": "Ignorar o seguinte ID Matrix", + "@commandHint_ignore": {}, + "commandHint_unignore": "Designorar o seguinte ID Matrix", + "@commandHint_unignore": {}, + "hidePresences": "Esconder lista de status?", + "@hidePresences": {}, + "sessionLostBody": "Sua sessão foi desconectada. Por favor, reporte este ao desenvolvedor em {url}. A mensagem de erro é: {error}", + "@sessionLostBody": { + "type": "text", + "placeholders": { + "url": {}, + "error": {} + } + }, + "sendReadReceiptsDescription": "Outros participantes neste chat podem ver quando você tiver lido uma mensagem.", + "@sendReadReceiptsDescription": {}, + "verifyOtherUserDescription": "Se você verificar outro usuário, você terá certeza que você conhece com quem está conversando. 💪\n\nQuando iniciar uma verificação, você e o outro usuário receberão um popup no aplicativo. Então vocês receberão uma série de emojis ou números para comparar um com o outro.\n\nA melhor maneira de fazer este procedimento é encontrar pessoalmente ou através de um vídeochamada. 👭", + "@verifyOtherUserDescription": {}, + "requestedKeyVerification": "{sender} enviou uma chave de verificação", + "@requestedKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "verifyOtherDeviceDescription": "Quando você verifica outro aparelho, estes aparelhos poderão trocar chaves, aumentando sua segurança. 💪\n\nQuando iniciar a verificação, um popup aparecerá no aplicativo em ambos os aparelhos. Então você verá uma série de emojis ou números que você terá que comparar um com o outro.\n\nÉ melhor fazer esse procedimento com ambos os aparelhos em mãos antes de começar a verificação. 🤳", + "@verifyOtherDeviceDescription": {}, + "canceledKeyVerification": "{sender} cancelou sua chave de verificação", + "@canceledKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "isReadyForKeyVerification": "{sender} está pronto para a chave de verificação", + "@isReadyForKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "completedKeyVerification": "{sender} completou a chave de verificação", + "@completedKeyVerification": { + "type": "text", + "placeholders": { + "sender": {} + } + }, + "stickers": "Stickers", + "@stickers": {}, + "discover": "Descubra", + "@discover": {}, + "incomingMessages": "Mensagens recebidas", + "@incomingMessages": {}, + "unreadChatsInApp": "{appname}: {unread} mensagens não lidas", + "@unreadChatsInApp": { + "type": "text", + "placeholders": { + "appname": {}, + "unread": {} + } + } } diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 8bf162372..2d0bfd700 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/archive/archive.dart'; import 'package:fluffychat/pages/chat/chat.dart'; +import 'package:fluffychat/pages/chat_access_settings/chat_access_settings_controller.dart'; import 'package:fluffychat/pages/chat_details/chat_details.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/chat_members/chat_members.dart'; @@ -546,6 +547,17 @@ abstract class AppRoutes { ), ), routes: [ + GoRoute( + path: 'access', + pageBuilder: (context, state) => defaultPageBuilder( + context, + state, + ChatAccessSettings( + roomId: state.pathParameters['roomid']!, + ), + ), + redirect: loggedOutRedirect, + ), GoRoute( path: 'members', pageBuilder: (context, state) => defaultPageBuilder( diff --git a/lib/config/themes.dart b/lib/config/themes.dart index 64ea62c49..dc602bb9f 100644 --- a/lib/config/themes.dart +++ b/lib/config/themes.dart @@ -141,6 +141,9 @@ abstract class FluffyThemes { ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( + backgroundColor: colorScheme.secondaryContainer, + foregroundColor: colorScheme.onSecondaryContainer, + elevation: 0, padding: const EdgeInsets.all(16), textStyle: const TextStyle(fontSize: 16), shape: RoundedRectangleBorder( diff --git a/lib/pages/bootstrap/bootstrap_dialog.dart b/lib/pages/bootstrap/bootstrap_dialog.dart index 8bf4f4b02..e1c4cde71 100644 --- a/lib/pages/bootstrap/bootstrap_dialog.dart +++ b/lib/pages/bootstrap/bootstrap_dialog.dart @@ -386,7 +386,10 @@ class BootstrapDialogState extends State { const SizedBox(height: 16), ElevatedButton.icon( style: ElevatedButton.styleFrom( - foregroundColor: Colors.red, + backgroundColor: + Theme.of(context).colorScheme.errorContainer, + foregroundColor: + Theme.of(context).colorScheme.onErrorContainer, ), icon: const Icon(Icons.delete_outlined), label: Text(L10n.of(context)!.recoveryKeyLost), diff --git a/lib/pages/chat_access_settings/chat_access_settings_controller.dart b/lib/pages/chat_access_settings/chat_access_settings_controller.dart new file mode 100644 index 000000000..068a47f37 --- /dev/null +++ b/lib/pages/chat_access_settings/chat_access_settings_controller.dart @@ -0,0 +1,216 @@ +import 'package:flutter/material.dart' hide Visibility; + +import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:future_loading_dialog/future_loading_dialog.dart'; +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/pages/chat_access_settings/chat_access_settings_page.dart'; +import 'package:fluffychat/utils/localized_exception_extension.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +class ChatAccessSettings extends StatefulWidget { + final String roomId; + const ChatAccessSettings({required this.roomId, super.key}); + + @override + State createState() => ChatAccessSettingsController(); +} + +class ChatAccessSettingsController extends State { + bool joinRulesLoading = false; + bool visibilityLoading = false; + bool historyVisibilityLoading = false; + bool guestAccessLoading = false; + Room get room => Matrix.of(context).client.getRoomById(widget.roomId)!; + + void setJoinRule(JoinRules? newJoinRules) async { + if (newJoinRules == null) return; + setState(() { + joinRulesLoading = true; + }); + + try { + await room.setJoinRules(newJoinRules); + } catch (e, s) { + Logs().w('Unable to change join rules', e, s); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + e.toLocalizedString(context), + ), + ), + ); + } + } finally { + if (mounted) { + setState(() { + joinRulesLoading = false; + }); + } + } + } + + void setHistoryVisibility(HistoryVisibility? historyVisibility) async { + if (historyVisibility == null) return; + setState(() { + historyVisibilityLoading = true; + }); + + try { + await room.setHistoryVisibility(historyVisibility); + } catch (e, s) { + Logs().w('Unable to change history visibility', e, s); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + e.toLocalizedString(context), + ), + ), + ); + } + } finally { + if (mounted) { + setState(() { + historyVisibilityLoading = false; + }); + } + } + } + + void setGuestAccess(GuestAccess? guestAccess) async { + if (guestAccess == null) return; + setState(() { + guestAccessLoading = true; + }); + + try { + await room.setGuestAccess(guestAccess); + } catch (e, s) { + Logs().w('Unable to change guest access', e, s); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + e.toLocalizedString(context), + ), + ), + ); + } + } finally { + if (mounted) { + setState(() { + guestAccessLoading = false; + }); + } + } + } + + void updateRoomAction() async { + final roomVersion = room + .getState(EventTypes.RoomCreate)! + .content + .tryGet('room_version'); + final capabilitiesResult = await showFutureLoadingDialog( + context: context, + future: () => room.client.getCapabilities(), + ); + final capabilities = capabilitiesResult.result; + if (capabilities == null) return; + final newVersion = await showConfirmationDialog( + context: context, + title: L10n.of(context)!.replaceRoomWithNewerVersion, + actions: capabilities.mRoomVersions!.available.entries + .where((r) => r.key != roomVersion) + .map( + (version) => AlertDialogAction( + key: version.key, + label: + '${version.key} (${version.value.toString().split('.').last})', + ), + ) + .toList(), + ); + if (newVersion == null || + OkCancelResult.cancel == + await showOkCancelAlertDialog( + useRootNavigator: false, + context: context, + okLabel: L10n.of(context)!.yes, + cancelLabel: L10n.of(context)!.cancel, + title: L10n.of(context)!.areYouSure, + message: L10n.of(context)!.roomUpgradeDescription, + isDestructiveAction: true, + )) { + return; + } + await showFutureLoadingDialog( + context: context, + future: () => room.client.upgradeRoom(room.id, newVersion), + ); + } + + void setCanonicalAlias() async { + final input = await showTextInputDialog( + context: context, + title: L10n.of(context)!.editRoomAliases, + cancelLabel: L10n.of(context)!.cancel, + okLabel: L10n.of(context)!.ok, + textFields: [ + DialogTextField( + prefixText: '#', + suffixText: room.client.userID!.domain!, + initialText: room.canonicalAlias.localpart, + ), + ], + ); + final newAliasLocalpart = input?.singleOrNull?.trim(); + if (newAliasLocalpart == null || newAliasLocalpart.isEmpty) return; + + await showFutureLoadingDialog( + context: context, + future: () => room.setCanonicalAlias( + '#$newAliasLocalpart:${room.client.userID!.domain!}', + ), + ); + } + + void setChatVisibilityOnDirectory(bool? visibility) async { + if (visibility == null) return; + setState(() { + visibilityLoading = true; + }); + + try { + await room.client.setRoomVisibilityOnDirectory( + room.id, + visibility: visibility == true ? Visibility.public : Visibility.private, + ); + setState(() {}); + } catch (e, s) { + Logs().w('Unable to change visibility', e, s); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + e.toLocalizedString(context), + ), + ), + ); + } + } finally { + if (mounted) { + setState(() { + visibilityLoading = false; + }); + } + } + } + + @override + Widget build(BuildContext context) { + return ChatAccessSettingsPageView(this); + } +} diff --git a/lib/pages/chat_access_settings/chat_access_settings_page.dart b/lib/pages/chat_access_settings/chat_access_settings_page.dart new file mode 100644 index 000000000..776fcd48b --- /dev/null +++ b/lib/pages/chat_access_settings/chat_access_settings_page.dart @@ -0,0 +1,189 @@ +import 'package:flutter/material.dart' hide Visibility; + +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/pages/chat_access_settings/chat_access_settings_controller.dart'; +import 'package:fluffychat/utils/fluffy_share.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; +import 'package:fluffychat/widgets/layouts/max_width_body.dart'; + +class ChatAccessSettingsPageView extends StatelessWidget { + final ChatAccessSettingsController controller; + const ChatAccessSettingsPageView(this.controller, {super.key}); + + @override + Widget build(BuildContext context) { + final room = controller.room; + return Scaffold( + appBar: AppBar( + leading: const Center(child: BackButton()), + title: Text(L10n.of(context)!.accessAndVisibility), + ), + body: MaxWidthBody( + child: StreamBuilder( + stream: room.onUpdate.stream, + builder: (context, snapshot) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: Text( + L10n.of(context)!.visibilityOfTheChatHistory, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + ), + ), + ), + for (final historyVisibility in HistoryVisibility.values) + RadioListTile.adaptive( + title: Text( + historyVisibility + .getLocalizedString(MatrixLocals(L10n.of(context)!)), + ), + value: historyVisibility, + groupValue: room.historyVisibility, + onChanged: controller.historyVisibilityLoading || + !room.canChangeHistoryVisibility + ? null + : controller.setHistoryVisibility, + ), + Divider(color: Theme.of(context).dividerColor), + ListTile( + title: Text( + L10n.of(context)!.whoIsAllowedToJoinThisGroup, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + ), + ), + ), + for (final joinRule in JoinRules.values) + RadioListTile.adaptive( + title: Text( + joinRule.localizedString(L10n.of(context)!), + ), + value: joinRule, + groupValue: room.joinRules, + onChanged: + controller.joinRulesLoading || !room.canChangeJoinRules + ? null + : controller.setJoinRule, + ), + Divider(color: Theme.of(context).dividerColor), + if ({JoinRules.public, JoinRules.knock} + .contains(room.joinRules)) ...[ + ListTile( + title: Text( + L10n.of(context)!.areGuestsAllowedToJoin, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + ), + ), + ), + for (final guestAccess in GuestAccess.values) + RadioListTile.adaptive( + title: Text( + guestAccess + .getLocalizedString(MatrixLocals(L10n.of(context)!)), + ), + value: guestAccess, + groupValue: room.guestAccess, + onChanged: controller.guestAccessLoading || + !room.canChangeGuestAccess + ? null + : controller.setGuestAccess, + ), + Divider(color: Theme.of(context).dividerColor), + FutureBuilder( + future: room.client.getRoomVisibilityOnDirectory(room.id), + builder: (context, snapshot) => SwitchListTile.adaptive( + value: snapshot.data == Visibility.public, + title: Text( + L10n.of(context)!.chatCanBeDiscoveredViaSearchOnServer( + room.client.userID!.domain!, + ), + ), + onChanged: controller.setChatVisibilityOnDirectory, + ), + ), + ListTile( + title: Text(L10n.of(context)!.publicLink), + subtitle: room.canonicalAlias.isEmpty + ? Text( + L10n.of(context)!.noPublicLinkHasBeenCreatedYet, + style: const TextStyle( + fontStyle: FontStyle.italic, + ), + ) + : Text( + 'https://matrix.to/#/${room.canonicalAlias}', + style: TextStyle( + decoration: TextDecoration.underline, + color: Theme.of(context).colorScheme.primary, + ), + ), + onTap: room.canChangeStateEvent(EventTypes.RoomCanonicalAlias) + ? controller.setCanonicalAlias + : null, + trailing: room.canonicalAlias.isEmpty + ? const Padding( + padding: EdgeInsets.symmetric(horizontal: 8.0), + child: Icon(Icons.add), + ) + : IconButton( + icon: Icon(Icons.adaptive.share_outlined), + onPressed: () => FluffyShare.share( + 'https://matrix.to/#/${room.canonicalAlias}', + context, + ), + ), + ), + ], + ListTile( + title: Text(L10n.of(context)!.globalChatId), + subtitle: SelectableText(room.id), + trailing: IconButton( + icon: const Icon(Icons.copy_outlined), + onPressed: () => FluffyShare.share(room.id, context), + ), + ), + ListTile( + title: Text(L10n.of(context)!.roomVersion), + subtitle: SelectableText( + room + .getState(EventTypes.RoomCreate)! + .content + .tryGet('room_version') ?? + 'Unknown', + ), + trailing: room.canSendEvent(EventTypes.RoomTombstone) + ? IconButton( + icon: const Icon(Icons.upgrade_outlined), + onPressed: controller.updateRoomAction, + ) + : null, + ), + ], + ), + ), + ), + ); + } +} + +extension JoinRulesDisplayString on JoinRules { + String localizedString(L10n l10n) { + switch (this) { + case JoinRules.public: + return l10n.anyoneCanJoin; + case JoinRules.invite: + return l10n.invitedUsersOnly; + case JoinRules.knock: + return l10n.usersMustKnock; + case JoinRules.private: + return l10n.noOneCanJoin; + } + } +} diff --git a/lib/pages/chat_details/chat_details.dart b/lib/pages/chat_details/chat_details.dart index e64b01bf0..7b7bbfd1c 100644 --- a/lib/pages/chat_details/chat_details.dart +++ b/lib/pages/chat_details/chat_details.dart @@ -7,17 +7,14 @@ import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_des import 'package:fluffychat/pangea/utils/set_class_name.dart'; import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart'; import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_settings.dart'; -import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/app_lock.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:matrix/matrix.dart' as matrix; import 'package:matrix/matrix.dart'; enum AliasActions { copy, delete, setCanonical } @@ -83,122 +80,6 @@ class ChatDetailsController extends State { // } // Pangea# - void editAliases() async { - final room = Matrix.of(context).client.getRoomById(roomId!); - - final aliasesResult = await showFutureLoadingDialog( - context: context, - future: () => room!.client.getLocalAliases(room.id), - ); - - final aliases = aliasesResult.result; - - if (aliases == null) return; - final adminMode = room!.canSendEvent(EventTypes.RoomCanonicalAlias); - if (aliases.isEmpty && (room.canonicalAlias.isNotEmpty)) { - aliases.add(room.canonicalAlias); - } - if (aliases.isEmpty && adminMode) { - return setAliasAction(); - } - final select = await showConfirmationDialog( - // #Pangea - useRootNavigator: false, - // Pangea# - context: context, - title: L10n.of(context)!.editRoomAliases, - actions: [ - if (adminMode) - AlertDialogAction(label: L10n.of(context)!.create, key: 'new'), - ...aliases.map((alias) => AlertDialogAction(key: alias, label: alias)), - ], - ); - if (select == null) return; - if (select == 'new') { - return setAliasAction(); - } - final option = await showConfirmationDialog( - context: context, - title: select, - actions: [ - AlertDialogAction( - label: L10n.of(context)!.copyToClipboard, - key: AliasActions.copy, - isDefaultAction: true, - ), - if (adminMode) ...{ - AlertDialogAction( - label: L10n.of(context)!.setAsCanonicalAlias, - key: AliasActions.setCanonical, - isDestructiveAction: true, - ), - AlertDialogAction( - label: L10n.of(context)!.delete, - key: AliasActions.delete, - isDestructiveAction: true, - ), - }, - ], - ); - if (option == null) return; - switch (option) { - case AliasActions.copy: - await Clipboard.setData(ClipboardData(text: select)); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(L10n.of(context)!.copiedToClipboard)), - ); - break; - case AliasActions.delete: - await showFutureLoadingDialog( - context: context, - future: () => room.client.deleteRoomAlias(select), - ); - break; - case AliasActions.setCanonical: - await showFutureLoadingDialog( - context: context, - future: () => room.client.setRoomStateWithKey( - room.id, - EventTypes.RoomCanonicalAlias, - '', - { - 'alias': select, - }, - ), - ); - break; - } - } - - void setAliasAction() async { - final room = Matrix.of(context).client.getRoomById(roomId!)!; - final domain = room.client.userID!.domain; - - final input = await showTextInputDialog( - // #Pangea - useRootNavigator: false, - // Pangea# - context: context, - title: L10n.of(context)!.setInvitationLink, - okLabel: L10n.of(context)!.ok, - cancelLabel: L10n.of(context)!.cancel, - textFields: [ - DialogTextField( - prefixText: '#', - suffixText: domain, - hintText: L10n.of(context)!.alias, - initialText: room.canonicalAlias.localpart, - ), - ], - ); - if (input == null) return; - await showFutureLoadingDialog( - context: context, - future: () => - room.client.setRoomAlias('#${input.single}:${domain!}', room.id), - ); - } - void setTopicAction() async { final room = Matrix.of(context).client.getRoomById(roomId!)!; // #Pangea @@ -232,91 +113,6 @@ class ChatDetailsController extends State { // Pangea# } - void setGuestAccess() async { - final room = Matrix.of(context).client.getRoomById(roomId!)!; - final currentGuestAccess = room.guestAccess; - final newGuestAccess = await showConfirmationDialog( - context: context, - title: L10n.of(context)!.areGuestsAllowedToJoin, - actions: GuestAccess.values - .map( - (guestAccess) => AlertDialogAction( - key: guestAccess, - label: guestAccess - .getLocalizedString(MatrixLocals(L10n.of(context)!)), - isDefaultAction: guestAccess == currentGuestAccess, - ), - ) - .toList(), - ); - if (newGuestAccess == null || newGuestAccess == currentGuestAccess) return; - await showFutureLoadingDialog( - context: context, - future: () => room.setGuestAccess(newGuestAccess), - ); - } - - void setHistoryVisibility() async { - final room = Matrix.of(context).client.getRoomById(roomId!)!; - final currentHistoryVisibility = room.historyVisibility; - final newHistoryVisibility = - await showConfirmationDialog( - context: context, - title: L10n.of(context)!.visibilityOfTheChatHistory, - actions: HistoryVisibility.values - .map( - (visibility) => AlertDialogAction( - key: visibility, - label: visibility - .getLocalizedString(MatrixLocals(L10n.of(context)!)), - isDefaultAction: visibility == currentHistoryVisibility, - ), - ) - .toList(), - ); - if (newHistoryVisibility == null || - newHistoryVisibility == currentHistoryVisibility) return; - await showFutureLoadingDialog( - context: context, - future: () => room.setHistoryVisibility(newHistoryVisibility), - ); - } - - void setJoinRules() async { - final room = Matrix.of(context).client.getRoomById(roomId!)!; - final currentJoinRule = room.joinRules; - final newJoinRule = await showConfirmationDialog( - context: context, - title: L10n.of(context)!.whoIsAllowedToJoinThisGroup, - actions: JoinRules.values - .map( - (joinRule) => AlertDialogAction( - key: joinRule, - label: - joinRule.getLocalizedString(MatrixLocals(L10n.of(context)!)), - isDefaultAction: joinRule == currentJoinRule, - ), - ) - .toList(), - ); - if (newJoinRule == null || newJoinRule == currentJoinRule) return; - await showFutureLoadingDialog( - context: context, - future: () async { - await room.setJoinRules(newJoinRule); - room.client.setRoomVisibilityOnDirectory( - roomId!, - visibility: { - JoinRules.public, - JoinRules.knock, - }.contains(newJoinRule) - ? matrix.Visibility.public - : matrix.Visibility.private, - ); - }, - ); - } - void goToEmoteSettings() async { final room = Matrix.of(context).client.getRoomById(roomId!)!; // okay, we need to test if there are any emote state events other than the default one diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index 3159af329..f100208e2 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -50,7 +50,8 @@ class ChatDetailsView extends StatelessWidget { } return StreamBuilder( - stream: room.onUpdate.stream, + stream: room.client.onRoomState.stream + .where((update) => update.roomId == room.id), builder: (context, snapshot) { var members = room.getParticipants().toList() ..sort((b, a) => a.powerLevel.compareTo(b.powerLevel)); @@ -80,17 +81,16 @@ class ChatDetailsView extends StatelessWidget { elevation: Theme.of(context).appBarTheme.elevation, actions: [ // #Pangea - //if (room.canonicalAlias.isNotEmpty) - // IconButton( - // tooltip: L10n.of(context)!.share, - // icon: Icon(Icons.adaptive.share_outlined), - // onPressed: () => FluffyShare.share( - // L10n.of(context)!.youInvitedToBy( - // AppConfig.inviteLinkPrefix + room.canonicalAlias, - // ), - // context, - // ), - // ), + // if (room.canonicalAlias.isNotEmpty) + // IconButton( + // tooltip: L10n.of(context)!.share, + // icon: Icon(Icons.adaptive.share_outlined), + // onPressed: () => FluffyShare.share( + // AppConfig.inviteLinkPrefix + room.canonicalAlias, + // context, + // ), + // ), + // Pangea# if (controller.widget.embeddedCloseButton == null) ChatSettingsPopupMenu(room, false), ], @@ -144,7 +144,6 @@ class ChatDetailsView extends StatelessWidget { mxContent: room.avatar, name: displayname, size: Avatar.defaultSize * 2.5, - fontSize: 18 * 2.5, ), ), ), @@ -196,7 +195,7 @@ class ChatDetailsView extends StatelessWidget { style: TextButton.styleFrom( foregroundColor: Theme.of(context) .colorScheme - .onBackground, + .onSurface, ), label: Text( room.isDirectChat @@ -303,10 +302,18 @@ class ChatDetailsView extends StatelessWidget { // else // Padding( // padding: const EdgeInsets.all(16.0), - // child: OutlinedButton.icon( + // child: TextButton.icon( // onPressed: controller.setTopicAction, // label: Text(L10n.of(context)!.setChatDescription), // icon: const Icon(Icons.edit_outlined), + // style: TextButton.styleFrom( + // backgroundColor: Theme.of(context) + // .colorScheme + // .secondaryContainer, + // foregroundColor: Theme.of(context) + // .colorScheme + // .onSecondaryContainer, + // ), // ), // ), // Padding( @@ -341,23 +348,6 @@ class ChatDetailsView extends StatelessWidget { // height: 1, // color: Theme.of(context).dividerColor, // ), - // if (room.joinRules == JoinRules.public) - // ListTile( - // leading: CircleAvatar( - // backgroundColor: - // Theme.of(context).scaffoldBackgroundColor, - // foregroundColor: iconColor, - // child: const Icon(Icons.link_outlined), - // ), - // trailing: const Icon(Icons.chevron_right_outlined), - // onTap: controller.editAliases, - // title: Text(L10n.of(context)!.editRoomAliases), - // subtitle: Text( - // (room.canonicalAlias.isNotEmpty) - // ? room.canonicalAlias - // : L10n.of(context)!.none, - // ), - // ), // ListTile( // leading: CircleAvatar( // backgroundColor: @@ -367,7 +357,8 @@ class ChatDetailsView extends StatelessWidget { // Icons.insert_emoticon_outlined, // ), // ), - // title: Text(L10n.of(context)!.emoteSettings), + // title: + // Text(L10n.of(context)!.customEmojisAndStickers), // subtitle: Text(L10n.of(context)!.setCustomEmotes), // onTap: controller.goToEmoteSettings, // trailing: const Icon(Icons.chevron_right_outlined), @@ -381,71 +372,16 @@ class ChatDetailsView extends StatelessWidget { // child: const Icon(Icons.shield_outlined), // ), // title: Text( - // L10n.of(context)!.whoIsAllowedToJoinThisGroup, + // L10n.of(context)!.accessAndVisibility, // ), - // trailing: room.canChangeJoinRules - // ? const Icon(Icons.chevron_right_outlined) - // : null, // subtitle: Text( - // room.joinRules?.getLocalizedString( - // MatrixLocals(L10n.of(context)!), - // ) ?? - // L10n.of(context)!.none, + // L10n.of(context)!.accessAndVisibilityDescription, // ), - // onTap: room.canChangeJoinRules - // ? controller.setJoinRules - // : null, + // onTap: () => context + // .push('/rooms/${room.id}/details/access'), + // trailing: const Icon(Icons.chevron_right_outlined), // ), // if (!room.isDirectChat) - // ListTile( - // leading: CircleAvatar( - // backgroundColor: - // Theme.of(context).scaffoldBackgroundColor, - // foregroundColor: iconColor, - // child: const Icon(Icons.visibility_outlined), - // ), - // trailing: room.canChangeHistoryVisibility - // ? const Icon(Icons.chevron_right_outlined) - // : null, - // title: Text( - // L10n.of(context)!.visibilityOfTheChatHistory, - // ), - // subtitle: Text( - // room.historyVisibility?.getLocalizedString( - // MatrixLocals(L10n.of(context)!), - // ) ?? - // L10n.of(context)!.none, - // ), - // onTap: room.canChangeHistoryVisibility - // ? controller.setHistoryVisibility - // : null, - // ), - // if (room.jsoinRules == JoinRules.public) - // ListTile( - // leading: CircleAvatar( - // backgroundColor: - // Theme.of(context).scaffoldBackgroundColor, - // foregroundColor: iconColor, - // child: const Icon( - // Icons.person_add_alt_1_outlined, - // ), - // ), - // trailing: room.canChangeGuestAccess - // ? const Icon(Icons.chevron_right_outlined) - // : null, - // title: Text( - // L10n.of(context)!.areGuestsAllowedToJoin, - // ), - // subtitle: Text( - // room.guestAccess.getLocalizedString( - // MatrixLocals(L10n.of(context)!), - // ), - // ), - // onTap: room.canChangeGuestAccess - // ? controller.setGuestAccess - // : null, - // ), - // if (!room.isDirectChat) if (!room.isDirectChat && !room.isSpace && room.isRoomAdmin) @@ -710,9 +646,12 @@ class ChatDetailsView extends StatelessWidget { // ListTile( // title: Text(L10n.of(context)!.inviteContact), // leading: CircleAvatar( - // backgroundColor: - // Theme.of(context).colorScheme.primary, - // foregroundColor: Colors.white, + // backgroundColor: Theme.of(context) + // .colorScheme + // .primaryContainer, + // foregroundColor: Theme.of(context) + // .colorScheme + // .onPrimaryContainer, // radius: Avatar.defaultSize / 2, // child: const Icon(Icons.add_outlined), // ), diff --git a/lib/pages/chat_details/participant_list_item.dart b/lib/pages/chat_details/participant_list_item.dart index b120ff777..9b5580692 100644 --- a/lib/pages/chat_details/participant_list_item.dart +++ b/lib/pages/chat_details/participant_list_item.dart @@ -13,12 +13,14 @@ class ParticipantListItem extends StatelessWidget { @override Widget build(BuildContext context) { - final membershipBatch = { - Membership.join: '', - Membership.ban: L10n.of(context)!.banned, - Membership.invite: L10n.of(context)!.invited, - Membership.leave: L10n.of(context)!.leftTheChat, + final membershipBatch = switch (user.membership) { + Membership.ban => L10n.of(context)!.banned, + Membership.invite => L10n.of(context)!.invited, + Membership.join => null, + Membership.knock => L10n.of(context)!.knocking, + Membership.leave => L10n.of(context)!.leftTheChat, }; + final permissionBatch = user.powerLevel == 100 ? L10n.of(context)!.admin : user.powerLevel >= 50 @@ -70,7 +72,7 @@ class ParticipantListItem extends StatelessWidget { // Pangea# ), ), - membershipBatch[user.membership]!.isEmpty + membershipBatch == null ? const SizedBox.shrink() : Container( padding: const EdgeInsets.all(4), @@ -79,8 +81,7 @@ class ParticipantListItem extends StatelessWidget { color: Theme.of(context).secondaryHeaderColor, borderRadius: BorderRadius.circular(8), ), - child: - Center(child: Text(membershipBatch[user.membership]!)), + child: Center(child: Text(membershipBatch)), ), ], ), diff --git a/lib/pages/chat_permissions_settings/chat_permissions_settings.dart b/lib/pages/chat_permissions_settings/chat_permissions_settings.dart index a6cdcbef0..4d3bdaa09 100644 --- a/lib/pages/chat_permissions_settings/chat_permissions_settings.dart +++ b/lib/pages/chat_permissions_settings/chat_permissions_settings.dart @@ -2,7 +2,6 @@ import 'dart:developer'; import 'package:flutter/material.dart'; -import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:go_router/go_router.dart'; @@ -71,44 +70,6 @@ class ChatPermissionsSettingsController extends State { false), ); - void updateRoomAction(Capabilities capabilities) async { - final room = Matrix.of(context).client.getRoomById(roomId!)!; - final roomVersion = room - .getState(EventTypes.RoomCreate)! - .content['room_version'] as String? ?? - '1'; - final newVersion = await showConfirmationDialog( - context: context, - title: L10n.of(context)!.replaceRoomWithNewerVersion, - actions: capabilities.mRoomVersions!.available.entries - .where((r) => r.key != roomVersion) - .map( - (version) => AlertDialogAction( - key: version.key, - label: - '${version.key} (${version.value.toString().split('.').last})', - ), - ) - .toList(), - ); - if (newVersion == null || - OkCancelResult.cancel == - await showOkCancelAlertDialog( - useRootNavigator: false, - context: context, - okLabel: L10n.of(context)!.yes, - cancelLabel: L10n.of(context)!.cancel, - title: L10n.of(context)!.areYouSure, - message: L10n.of(context)!.roomUpgradeDescription, - )) { - return; - } - await showFutureLoadingDialog( - context: context, - future: () => room.client.upgradeRoom(roomId!, newVersion), - ).then((_) => context.pop()); - } - @override Widget build(BuildContext context) => ChatPermissionsSettingsView(this); } diff --git a/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart index b88095b20..11aad87ea 100644 --- a/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart +++ b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart @@ -54,7 +54,7 @@ class ChatPermissionsSettingsView extends StatelessWidget { entry.value, ), ), - const Divider(thickness: 1), + Divider(color: Theme.of(context).dividerColor), ListTile( title: Text( L10n.of(context)!.notifications, @@ -87,7 +87,7 @@ class ChatPermissionsSettingsView extends StatelessWidget { ); }, ), - const Divider(thickness: 1), + Divider(color: Theme.of(context).dividerColor), ListTile( title: Text( L10n.of(context)!.configureChat, @@ -109,33 +109,6 @@ class ChatPermissionsSettingsView extends StatelessWidget { category: 'events', ), ), - if (room.canSendEvent(EventTypes.RoomTombstone)) ...{ - const Divider(thickness: 1), - FutureBuilder( - future: room.client.getCapabilities(), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return const Center( - child: CircularProgressIndicator.adaptive( - strokeWidth: 2, - ), - ); - } - final roomVersion = room - .getState(EventTypes.RoomCreate)! - .content['room_version'] as String? ?? - '1'; - - return ListTile( - title: Text( - '${L10n.of(context)!.roomVersion}: $roomVersion', - ), - onTap: () => - controller.updateRoomAction(snapshot.data!), - ); - }, - ), - }, ], ), ], diff --git a/lib/pages/device_settings/device_settings_view.dart b/lib/pages/device_settings/device_settings_view.dart index 5493836ff..e107d30c9 100644 --- a/lib/pages/device_settings/device_settings_view.dart +++ b/lib/pages/device_settings/device_settings_view.dart @@ -71,11 +71,6 @@ class DevicesSettingsView extends StatelessWidget { block: controller.blockDeviceAction, unblock: controller.unblockDeviceAction, ), - const Divider( - height: 16.0, - indent: 16, - endIndent: 16, - ), ], if (controller.notThisDevice.isNotEmpty) Padding( @@ -85,17 +80,18 @@ class DevicesSettingsView extends StatelessWidget { ), child: SizedBox( width: double.infinity, - child: OutlinedButton.icon( + child: TextButton.icon( label: Text( controller.errorDeletingDevices ?? L10n.of(context)!.removeAllOtherDevices, ), - style: OutlinedButton.styleFrom( - foregroundColor: - Theme.of(context).colorScheme.error, - side: BorderSide( - color: Theme.of(context).colorScheme.error, - ), + style: TextButton.styleFrom( + foregroundColor: Theme.of(context) + .colorScheme + .onErrorContainer, + backgroundColor: Theme.of(context) + .colorScheme + .errorContainer, ), icon: controller.loadingDeletingDevices ? const CircularProgressIndicator.adaptive( diff --git a/lib/pages/homeserver_picker/homeserver_picker_view.dart b/lib/pages/homeserver_picker/homeserver_picker_view.dart index cd7e7e8a9..5bc85dff4 100644 --- a/lib/pages/homeserver_picker/homeserver_picker_view.dart +++ b/lib/pages/homeserver_picker/homeserver_picker_view.dart @@ -259,12 +259,7 @@ class _LoginButton extends StatelessWidget { width: double.infinity, child: OutlinedButton.icon( style: OutlinedButton.styleFrom( - side: BorderSide( - width: withBorder ? 1 : 0, - color: withBorder - ? Theme.of(context).colorScheme.onBackground - : Colors.transparent, - ), + side: BorderSide.none, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(99), ), diff --git a/lib/pages/settings/settings_view.dart b/lib/pages/settings/settings_view.dart index cc52f0ef0..094f3797d 100644 --- a/lib/pages/settings/settings_view.dart +++ b/lib/pages/settings/settings_view.dart @@ -39,7 +39,7 @@ class SettingsView extends StatelessWidget { ], ), body: ListTileTheme( - iconColor: Theme.of(context).colorScheme.onBackground, + iconColor: Theme.of(context).colorScheme.onSurface, child: ListView( key: const Key('SettingsListViewContent'), children: [ @@ -105,7 +105,7 @@ class SettingsView extends StatelessWidget { ), style: TextButton.styleFrom( foregroundColor: - Theme.of(context).colorScheme.onBackground, + Theme.of(context).colorScheme.onSurface, ), label: Text( displayname, @@ -138,8 +138,11 @@ class SettingsView extends StatelessWidget { ); }, ), - const Divider(thickness: 1), // #Pangea + // Divider( + // height: 1, + // color: Theme.of(context).dividerColor, + // ), // if (showChatBackupBanner == null) // ListTile( // leading: const Icon(Icons.backup_outlined), @@ -154,7 +157,10 @@ class SettingsView extends StatelessWidget { // title: Text(L10n.of(context)!.chatBackup), // onChanged: controller.firstRunBootstrapAction, // ), - // const Divider(thickness: 1), + // Divider( + // height: 1, + // color: Theme.of(context).dividerColor, + // ), // Pangea# ListTile( leading: const Icon(Icons.format_paint_outlined), @@ -196,7 +202,10 @@ class SettingsView extends StatelessWidget { onTap: () => context.go('/rooms/settings/security'), trailing: const Icon(Icons.chevron_right_outlined), ), - const Divider(thickness: 1), + Divider( + height: 1, + color: Theme.of(context).dividerColor, + ), ListTile( leading: const Icon(Icons.help_outline_outlined), title: Text(L10n.of(context)!.help), diff --git a/lib/pages/settings_chat/settings_chat_view.dart b/lib/pages/settings_chat/settings_chat_view.dart index 26a8821a1..54fa2cc7d 100644 --- a/lib/pages/settings_chat/settings_chat_view.dart +++ b/lib/pages/settings_chat/settings_chat_view.dart @@ -1,7 +1,5 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/setting_keys.dart'; -import 'package:fluffychat/utils/platform_infos.dart'; -import 'package:fluffychat/utils/voip/callkeep_manager.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; import 'package:fluffychat/widgets/settings_switch_list_tile.dart'; import 'package:flutter/material.dart'; @@ -23,61 +21,85 @@ class SettingsChatView extends StatelessWidget { child: Column( children: [ // #Pangea - // ListTile( - // title: Text(L10n.of(context)!.emoteSettings), - // onTap: () => context.go('/rooms/settings/chat/emotes'), - // trailing: const Icon(Icons.chevron_right_outlined), - // leading: const Icon(Icons.emoji_emotions_outlined), - // ), - // const Divider(), // SettingsSwitchListTile.adaptive( - // title: L10n.of(context)!.renderRichContent, + // title: L10n.of(context)!.formattedMessages, + // subtitle: L10n.of(context)!.formattedMessagesDescription, // onChanged: (b) => AppConfig.renderHtml = b, // storeKey: SettingKeys.renderHtml, // defaultValue: AppConfig.renderHtml, // ), // Pangea# SettingsSwitchListTile.adaptive( - title: L10n.of(context)!.hideRedactedEvents, + title: L10n.of(context)!.hideMemberChangesInPublicChats, + subtitle: L10n.of(context)!.hideMemberChangesInPublicChatsBody, + onChanged: (b) => AppConfig.hideUnimportantStateEvents = b, + storeKey: SettingKeys.hideUnimportantStateEvents, + defaultValue: AppConfig.hideUnimportantStateEvents, + ), + SettingsSwitchListTile.adaptive( + title: L10n.of(context)!.hideRedactedMessages, + subtitle: L10n.of(context)!.hideRedactedMessagesBody, onChanged: (b) => AppConfig.hideRedactedEvents = b, storeKey: SettingKeys.hideRedactedEvents, defaultValue: AppConfig.hideRedactedEvents, ), SettingsSwitchListTile.adaptive( - title: L10n.of(context)!.hideUnknownEvents, + title: L10n.of(context)!.hideInvalidOrUnknownMessageFormats, onChanged: (b) => AppConfig.hideUnknownEvents = b, storeKey: SettingKeys.hideUnknownEvents, defaultValue: AppConfig.hideUnknownEvents, ), - // #Pangea - // SettingsSwitchListTile.adaptive( - // title: L10n.of(context)!.hideUnimportantStateEvents, - // onChanged: (b) => AppConfig.hideUnimportantStateEvents = b, - // storeKey: SettingKeys.hideUnimportantStateEvents, - // defaultValue: AppConfig.hideUnimportantStateEvents, - // ), // Pangea# - if (PlatformInfos.isMobile) - SettingsSwitchListTile.adaptive( - title: L10n.of(context)!.autoplayImages, - onChanged: (b) => AppConfig.autoplayImages = b, - storeKey: SettingKeys.autoplayImages, - defaultValue: AppConfig.autoplayImages, - ), - const Divider(), + // if (PlatformInfos.isMobile) + // SettingsSwitchListTile.adaptive( + // title: L10n.of(context)!.autoplayImages, + // onChanged: (b) => AppConfig.autoplayImages = b, + // storeKey: SettingKeys.autoplayImages, + // defaultValue: AppConfig.autoplayImages, + // ), + // Pangea# // #Pangea // SettingsSwitchListTile.adaptive( - // title: L10n.of(context)!.sendTypingNotifications, - // onChanged: (b) => AppConfig.sendTypingNotifications = b, - // storeKey: SettingKeys.sendTypingNotifications, - // defaultValue: AppConfig.sendTypingNotifications, - // ), - // SettingsSwitchListTile.adaptive( // title: L10n.of(context)!.sendOnEnter, // onChanged: (b) => AppConfig.sendOnEnter = b, // storeKey: SettingKeys.sendOnEnter, // defaultValue: AppConfig.sendOnEnter ?? !PlatformInfos.isMobile, // ), + // Divider( + // height: 1, + // color: Theme.of(context).dividerColor, + // ), + // ListTile( + // title: Text( + // L10n.of(context)!.customEmojisAndStickers, + // style: TextStyle( + // color: Theme.of(context).colorScheme.secondary, + // fontWeight: FontWeight.bold, + // ), + // ), + // ), + // ListTile( + // title: Text(L10n.of(context)!.customEmojisAndStickers), + // subtitle: Text(L10n.of(context)!.customEmojisAndStickersBody), + // onTap: () => context.go('/rooms/settings/chat/emotes'), + // trailing: const Padding( + // padding: EdgeInsets.all(16.0), + // child: Icon(Icons.chevron_right_outlined), + // ), + // ), + // Divider( + // height: 1, + // color: Theme.of(context).dividerColor, + // ), + // ListTile( + // title: Text( + // L10n.of(context)!.calls, + // style: TextStyle( + // color: Theme.of(context).colorScheme.secondary, + // fontWeight: FontWeight.bold, + // ), + // ), + // ), // SettingsSwitchListTile.adaptive( // title: L10n.of(context)!.experimentalVideoCalls, // onChanged: (b) { @@ -88,23 +110,17 @@ class SettingsChatView extends StatelessWidget { // storeKey: SettingKeys.experimentalVoip, // defaultValue: AppConfig.experimentalVoip, // ), + // if (PlatformInfos.isMobile) + // ListTile( + // title: Text(L10n.of(context)!.callingPermissions), + // onTap: () => + // CallKeepManager().checkoutPhoneAccountSetting(context), + // trailing: const Padding( + // padding: EdgeInsets.all(16.0), + // child: Icon(Icons.call), + // ), + // ), // Pangea# - if (PlatformInfos.isMobile) - ListTile( - title: Text(L10n.of(context)!.callingPermissions), - onTap: () => - CallKeepManager().checkoutPhoneAccountSetting(context), - trailing: const Padding( - padding: EdgeInsets.all(16.0), - child: Icon(Icons.call), - ), - ), - SettingsSwitchListTile.adaptive( - title: L10n.of(context)!.separateChatTypes, - onChanged: (b) => AppConfig.separateChatTypes = b, - storeKey: SettingKeys.separateChatTypes, - defaultValue: AppConfig.separateChatTypes, - ), ], ), ), diff --git a/lib/pages/settings_emotes/settings_emotes_view.dart b/lib/pages/settings_emotes/settings_emotes_view.dart index acb93788e..7e72cd464 100644 --- a/lib/pages/settings_emotes/settings_emotes_view.dart +++ b/lib/pages/settings_emotes/settings_emotes_view.dart @@ -24,7 +24,7 @@ class EmotesSettingsView extends StatelessWidget { return Scaffold( appBar: AppBar( leading: const Center(child: BackButton()), - title: Text(L10n.of(context)!.emoteSettings), + title: Text(L10n.of(context)!.customEmojisAndStickers), actions: [ PopupMenuButton( onSelected: (value) { diff --git a/lib/pages/settings_ignore_list/settings_ignore_list.dart b/lib/pages/settings_ignore_list/settings_ignore_list.dart index 0b27cee3b..d64be058b 100644 --- a/lib/pages/settings_ignore_list/settings_ignore_list.dart +++ b/lib/pages/settings_ignore_list/settings_ignore_list.dart @@ -1,6 +1,8 @@ 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'; import '../../widgets/matrix.dart'; import 'settings_ignore_list_view.dart'; @@ -25,9 +27,20 @@ class SettingsIgnoreListController extends State { } } + String? errorText; + void ignoreUser(BuildContext context) { - if (controller.text.isEmpty) return; - final userId = '@${controller.text}'; + final userId = controller.text.trim(); + if (userId.isEmpty) return; + if (!userId.isValidMatrixId || userId.sigil != '@') { + setState(() { + errorText = L10n.of(context)!.invalidInput; + }); + return; + } + setState(() { + errorText = null; + }); showFutureLoadingDialog( context: context, diff --git a/lib/pages/settings_ignore_list/settings_ignore_list_view.dart b/lib/pages/settings_ignore_list/settings_ignore_list_view.dart index 7c755c5ea..403440f82 100644 --- a/lib/pages/settings_ignore_list/settings_ignore_list_view.dart +++ b/lib/pages/settings_ignore_list/settings_ignore_list_view.dart @@ -37,13 +37,13 @@ class SettingsIgnoreListView extends StatelessWidget { textInputAction: TextInputAction.done, onSubmitted: (_) => controller.ignoreUser(context), decoration: InputDecoration( - border: const OutlineInputBorder(), - hintText: 'bad_guy:domain.abc', - prefixText: '@', + errorText: controller.errorText, + hintText: '@bad_guy:domain.abc', + floatingLabelBehavior: FloatingLabelBehavior.always, labelText: L10n.of(context)!.blockUsername, suffixIcon: IconButton( tooltip: L10n.of(context)!.block, - icon: const Icon(Icons.done_outlined), + icon: const Icon(Icons.send_outlined), onPressed: () => controller.ignoreUser(context), ), ), @@ -56,7 +56,9 @@ class SettingsIgnoreListView extends StatelessWidget { ], ), ), - const Divider(height: 1), + Divider( + color: Theme.of(context).dividerColor, + ), Expanded( child: StreamBuilder( stream: client.onAccountData.stream @@ -75,9 +77,11 @@ class SettingsIgnoreListView extends StatelessWidget { title: Text( s.data?.displayName ?? client.ignoredUsers[i], ), + subtitle: + Text(s.data?.userId ?? client.ignoredUsers[i]), trailing: IconButton( tooltip: L10n.of(context)!.delete, - icon: const Icon(Icons.delete_forever_outlined), + icon: const Icon(Icons.delete_outlined), onPressed: () => showFutureLoadingDialog( context: context, future: () => diff --git a/lib/pages/settings_notifications/settings_notifications.dart b/lib/pages/settings_notifications/settings_notifications.dart index 6ab4740af..2baf14dd0 100644 --- a/lib/pages/settings_notifications/settings_notifications.dart +++ b/lib/pages/settings_notifications/settings_notifications.dart @@ -6,6 +6,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:matrix/matrix.dart'; +import 'package:fluffychat/utils/localized_exception_extension.dart'; import '../../widgets/matrix.dart'; import 'settings_notifications_view.dart'; @@ -89,16 +90,52 @@ class SettingsNotificationsController extends State { } } - void setNotificationSetting(NotificationSettingsItem item, bool enabled) { - showFutureLoadingDialog( - context: context, - future: () => Matrix.of(context).client.setPushRuleEnabled( + bool isLoading = false; + + void setNotificationSetting( + NotificationSettingsItem item, + bool enabled, + ) async { + final scaffoldMessenger = ScaffoldMessenger.of(context); + setState(() { + isLoading = true; + }); + try { + await Matrix.of(context).client.setPushRuleEnabled( 'global', item.type, item.key, enabled, - ), - ); + ); + } catch (e, s) { + Logs().w('Unable to change notification settings', e, s); + scaffoldMessenger + .showSnackBar(SnackBar(content: Text(e.toLocalizedString(context)))); + } finally { + setState(() { + isLoading = false; + }); + } + } + + void onToggleMuteAllNotifications() async { + final scaffoldMessenger = ScaffoldMessenger.of(context); + setState(() { + isLoading = true; + }); + try { + await Matrix.of(context).client.setMuteAllPushNotifications( + !Matrix.of(context).client.allPushNotificationsMuted, + ); + } catch (e, s) { + Logs().w('Unable to change notification settings', e, s); + scaffoldMessenger + .showSnackBar(SnackBar(content: Text(e.toLocalizedString(context)))); + } finally { + setState(() { + isLoading = false; + }); + } } void onPusherTap(Pusher pusher) async { diff --git a/lib/pages/settings_notifications/settings_notifications_view.dart b/lib/pages/settings_notifications/settings_notifications_view.dart index 64d356a80..978858a02 100644 --- a/lib/pages/settings_notifications/settings_notifications_view.dart +++ b/lib/pages/settings_notifications/settings_notifications_view.dart @@ -1,7 +1,6 @@ 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'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; @@ -36,13 +35,18 @@ class SettingsNotificationsView extends StatelessWidget { title: Text( L10n.of(context)!.notificationsEnabledForThisAccount, ), - onChanged: (_) => showFutureLoadingDialog( - context: context, - future: () => Matrix.of(context) - .client - .setMuteAllPushNotifications( - !Matrix.of(context).client.allPushNotificationsMuted, - ), + onChanged: controller.isLoading + ? null + : (_) => controller.onToggleMuteAllNotifications(), + ), + Divider(color: Theme.of(context).dividerColor), + ListTile( + title: Text( + L10n.of(context)!.notifyMeFor, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + ), ), ), for (final item in NotificationSettingsItem.items) @@ -51,14 +55,14 @@ class SettingsNotificationsView extends StatelessWidget { ? false : controller.getNotificationSetting(item) ?? true, title: Text(item.title(context)), - onChanged: Matrix.of(context) - .client - .allPushNotificationsMuted + onChanged: controller.isLoading ? null - : (bool enabled) => - controller.setNotificationSetting(item, enabled), + : Matrix.of(context).client.allPushNotificationsMuted + ? null + : (bool enabled) => controller + .setNotificationSetting(item, enabled), ), - const Divider(), + Divider(color: Theme.of(context).dividerColor), ListTile( title: Text( L10n.of(context)!.devices, @@ -87,6 +91,14 @@ class SettingsNotificationsView extends StatelessWidget { ); } final pushers = snapshot.data ?? []; + if (pushers.isEmpty) { + return Center( + child: Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: Text(L10n.of(context)!.noOtherDevicesFound), + ), + ); + } return ListView.builder( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, diff --git a/lib/pages/settings_password/settings_password_view.dart b/lib/pages/settings_password/settings_password_view.dart index 577077a0a..dd81fa122 100644 --- a/lib/pages/settings_password/settings_password_view.dart +++ b/lib/pages/settings_password/settings_password_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:go_router/go_router.dart'; import 'package:fluffychat/pages/settings_password/settings_password.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; @@ -12,7 +13,15 @@ class SettingsPasswordView extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text(L10n.of(context)!.changePassword)), + appBar: AppBar( + title: Text(L10n.of(context)!.changePassword), + actions: [ + TextButton( + child: Text(L10n.of(context)!.passwordRecoverySettings), + onPressed: () => context.go('/rooms/settings/security/3pid'), + ), + ], + ), body: ListTileTheme( iconColor: Theme.of(context).colorScheme.onBackground, child: MaxWidthBody( diff --git a/lib/pages/settings_security/settings_security.dart b/lib/pages/settings_security/settings_security.dart index 3f488070b..69069d693 100644 --- a/lib/pages/settings_security/settings_security.dart +++ b/lib/pages/settings_security/settings_security.dart @@ -58,6 +58,7 @@ class SettingsSecurityController extends State { message: L10n.of(context)!.deactivateAccountWarning, okLabel: L10n.of(context)!.ok, cancelLabel: L10n.of(context)!.cancel, + isDestructiveAction: true, ) == OkCancelResult.cancel) { return; @@ -74,6 +75,7 @@ class SettingsSecurityController extends State { : L10n.of(context)!.supposedMxid(supposedMxid), ), ], + isDestructiveAction: true, okLabel: L10n.of(context)!.delete, cancelLabel: L10n.of(context)!.cancel, ); @@ -86,6 +88,7 @@ class SettingsSecurityController extends State { title: L10n.of(context)!.pleaseEnterYourPassword, okLabel: L10n.of(context)!.ok, cancelLabel: L10n.of(context)!.cancel, + isDestructiveAction: true, textFields: [ const DialogTextField( obscureText: true, diff --git a/lib/pages/settings_security/settings_security_view.dart b/lib/pages/settings_security/settings_security_view.dart index 20328ade7..8af35a7ad 100644 --- a/lib/pages/settings_security/settings_security_view.dart +++ b/lib/pages/settings_security/settings_security_view.dart @@ -1,7 +1,6 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/utils/beautify_string_extension.dart'; -import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -41,95 +40,15 @@ class SettingsSecurityView extends StatelessWidget { } return Column( children: [ - if (error != null) - ListTile( - leading: const Icon( - Icons.warning_outlined, - color: Colors.orange, - ), - title: Text( - error.toLocalizedString(context), - style: const TextStyle(color: Colors.orange), - ), - ), - if (capabilities?.mChangePassword?.enabled != false || - error != null) ...[ - ListTile( - leading: const Icon(Icons.key_outlined), - trailing: error != null - ? null - : const Icon(Icons.chevron_right_outlined), - title: Text( - L10n.of(context)!.changePassword, - style: TextStyle( - decoration: - error == null ? null : TextDecoration.lineThrough, - ), - ), - onTap: error != null - ? null - : () => - context.go('/rooms/settings/security/password'), - ), - ListTile( - leading: const Icon(Icons.mail_outlined), - trailing: error != null - ? null - : const Icon(Icons.chevron_right_outlined), - title: Text( - L10n.of(context)!.passwordRecovery, - style: TextStyle( - decoration: - error == null ? null : TextDecoration.lineThrough, - ), - ), - onTap: error != null - ? null - : () => context.go('/rooms/settings/security/3pid'), - ), - ], ListTile( - leading: const Icon(Icons.block_outlined), - trailing: const Icon(Icons.chevron_right_outlined), - title: Text(L10n.of(context)!.blockedUsers), - onTap: () => - context.go('/rooms/settings/security/ignorelist'), - ), - if (Matrix.of(context).client.encryption != null) ...{ - if (PlatformInfos.isMobile) - ListTile( - leading: const Icon(Icons.lock_outlined), - trailing: const Icon(Icons.chevron_right_outlined), - title: Text(L10n.of(context)!.appLock), - onTap: controller.setAppLockAction, - ), - }, - const Divider(height: 1), - ListTile( - leading: const Icon(Icons.tap_and_play), title: Text( - L10n.of(context)!.dehydrate, - style: const TextStyle(color: Colors.red), + L10n.of(context)!.privacy, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + ), ), - onTap: controller.dehydrateAction, ), - ListTile( - leading: const Icon(Icons.delete_outlined), - title: Text( - L10n.of(context)!.deleteAccount, - style: const TextStyle(color: Colors.red), - ), - onTap: controller.deleteAccountAction, - ), - ListTile( - title: Text(L10n.of(context)!.yourPublicKey), - subtitle: SelectableText( - Matrix.of(context).client.fingerprintKey.beautified, - style: const TextStyle(fontFamily: 'monospace'), - ), - leading: const Icon(Icons.vpn_key_outlined), - ), - const Divider(height: 1), SettingsSwitchListTile.adaptive( title: L10n.of(context)!.sendTypingNotifications, subtitle: @@ -145,6 +64,74 @@ class SettingsSecurityView extends StatelessWidget { storeKey: SettingKeys.sendPublicReadReceipts, defaultValue: AppConfig.sendPublicReadReceipts, ), + ListTile( + trailing: const Icon(Icons.chevron_right_outlined), + title: Text(L10n.of(context)!.blockedUsers), + subtitle: Text( + L10n.of(context)!.thereAreCountUsersBlocked( + Matrix.of(context).client.ignoredUsers.length, + ), + ), + onTap: () => + context.go('/rooms/settings/security/ignorelist'), + ), + if (Matrix.of(context).client.encryption != null) ...{ + if (PlatformInfos.isMobile) + ListTile( + trailing: const Icon(Icons.chevron_right_outlined), + title: Text(L10n.of(context)!.appLock), + subtitle: Text(L10n.of(context)!.appLockDescription), + onTap: controller.setAppLockAction, + ), + }, + Divider( + height: 1, + color: Theme.of(context).dividerColor, + ), + ListTile( + title: Text( + L10n.of(context)!.account, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + ), + ), + ), + ListTile( + title: Text(L10n.of(context)!.yourPublicKey), + leading: const Icon(Icons.vpn_key_outlined), + subtitle: SelectableText( + Matrix.of(context).client.fingerprintKey.beautified, + style: const TextStyle(fontFamily: 'monospace'), + ), + ), + if (capabilities?.mChangePassword?.enabled != false || + error != null) + ListTile( + leading: const Icon(Icons.password_outlined), + trailing: const Icon(Icons.chevron_right_outlined), + title: Text(L10n.of(context)!.changePassword), + onTap: () => + context.go('/rooms/settings/security/password'), + ), + ListTile( + iconColor: Colors.orange, + leading: const Icon(Icons.tap_and_play), + title: Text( + L10n.of(context)!.dehydrate, + style: const TextStyle(color: Colors.orange), + ), + onTap: controller.dehydrateAction, + ), + ListTile( + iconColor: Colors.red, + leading: const Icon(Icons.delete_outlined), + title: Text( + L10n.of(context)!.deleteAccount, + style: const TextStyle(color: Colors.red), + ), + onTap: controller.deleteAccountAction, + ), ], ); }, diff --git a/lib/pages/settings_style/settings_style_view.dart b/lib/pages/settings_style/settings_style_view.dart index ed80e8268..cf7348021 100644 --- a/lib/pages/settings_style/settings_style_view.dart +++ b/lib/pages/settings_style/settings_style_view.dart @@ -135,7 +135,10 @@ class SettingsStyleView extends StatelessWidget { ), ), const SizedBox(height: 8), - const Divider(height: 1), + Divider( + height: 1, + color: Theme.of(context).dividerColor, + ), ListTile( title: Text( L10n.of(context)!.setTheme, @@ -163,10 +166,13 @@ class SettingsStyleView extends StatelessWidget { title: Text(L10n.of(context)!.darkTheme), onChanged: controller.switchTheme, ), - const Divider(height: 1), + Divider( + height: 1, + color: Theme.of(context).dividerColor, + ), ListTile( title: Text( - L10n.of(context)!.presenceStyle, + L10n.of(context)!.overview, style: TextStyle( color: Theme.of(context).colorScheme.secondary, fontWeight: FontWeight.bold, @@ -179,7 +185,16 @@ class SettingsStyleView extends StatelessWidget { storeKey: SettingKeys.showPresences, defaultValue: AppConfig.showPresences, ), - const Divider(height: 1), + SettingsSwitchListTile.adaptive( + title: L10n.of(context)!.separateChatTypes, + onChanged: (b) => AppConfig.separateChatTypes = b, + storeKey: SettingKeys.separateChatTypes, + defaultValue: AppConfig.separateChatTypes, + ), + Divider( + height: 1, + color: Theme.of(context).dividerColor, + ), ListTile( title: Text( L10n.of(context)!.messagesStyle, diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet.dart index 06a652495..7b484e55d 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet.dart @@ -260,6 +260,26 @@ class UserBottomSheetController extends State { } } + void knockAccept() async { + final user = widget.user!; + final result = await showFutureLoadingDialog( + context: context, + future: () => user.room.invite(user.id), + ); + if (result.error != null) return; + Navigator.of(context).pop(); + } + + void knockDecline() async { + final user = widget.user!; + final result = await showFutureLoadingDialog( + context: context, + future: () => user.room.kick(user.id), + ); + if (result.error != null) return; + Navigator.of(context).pop(); + } + @override Widget build(BuildContext context) => UserBottomSheetView(this); } diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart index 93774a8ca..13319d0e2 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart @@ -4,6 +4,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:matrix/matrix.dart'; +import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/fluffy_share.dart'; import 'package:fluffychat/utils/url_launcher.dart'; @@ -105,6 +106,51 @@ class UserBottomSheetView extends StatelessWidget { ), body: ListView( children: [ + if (user?.membership == Membership.knock) + Padding( + padding: const EdgeInsets.all(12.0), + child: Material( + color: Theme.of(context).colorScheme.surfaceVariant, + borderRadius: BorderRadius.circular(AppConfig.borderRadius), + child: ListTile( + minVerticalPadding: 16, + title: Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: Text( + L10n.of(context)! + .userWouldLikeToChangeTheChat(displayname), + ), + ), + subtitle: Row( + children: [ + TextButton.icon( + style: TextButton.styleFrom( + backgroundColor: + Theme.of(context).colorScheme.background, + foregroundColor: + Theme.of(context).colorScheme.primary, + ), + onPressed: controller.knockAccept, + icon: const Icon(Icons.check_outlined), + label: Text(L10n.of(context)!.accept), + ), + const SizedBox(width: 12), + TextButton.icon( + style: TextButton.styleFrom( + backgroundColor: + Theme.of(context).colorScheme.errorContainer, + foregroundColor: + Theme.of(context).colorScheme.onErrorContainer, + ), + onPressed: controller.knockDecline, + icon: const Icon(Icons.cancel_outlined), + label: Text(L10n.of(context)!.decline), + ), + ], + ), + ), + ), + ), Row( children: [ Padding( diff --git a/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart b/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart index 0c2fcc4f6..0ee52ceae 100644 --- a/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart +++ b/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart @@ -21,11 +21,11 @@ extension IsStateExtension on Event { (isState || !AppConfig.hideAllStateEvents) && // #Pangea content.tryGet(ModelKey.transcription) == null && - // Pangea# // hide unimportant state events (!AppConfig.hideUnimportantStateEvents || !isState || importantStateEvents.contains(type)) && + // Pangea# // hide simple join/leave member events in public rooms (!AppConfig.hideUnimportantStateEvents || type != EventTypes.RoomMember || @@ -33,6 +33,13 @@ extension IsStateExtension on Event { content.tryGet('membership') == 'ban' || stateKey != senderId); + bool get isState => !{ + EventTypes.Message, + EventTypes.Sticker, + EventTypes.Encrypted, + }.contains(type); + + // #Pangea static const Set importantStateEvents = { EventTypes.Encryption, EventTypes.RoomCreate, @@ -40,10 +47,5 @@ extension IsStateExtension on Event { EventTypes.RoomTombstone, EventTypes.CallInvite, }; - - bool get isState => !{ - EventTypes.Message, - EventTypes.Sticker, - EventTypes.Encrypted, - }.contains(type); + // Pangea# } diff --git a/lib/widgets/public_room_bottom_sheet.dart b/lib/widgets/public_room_bottom_sheet.dart index 086162b3f..aec6c5167 100644 --- a/lib/widgets/public_room_bottom_sheet.dart +++ b/lib/widgets/public_room_bottom_sheet.dart @@ -31,17 +31,18 @@ class PublicRoomBottomSheet extends StatelessWidget { void _joinRoom(BuildContext context) async { final client = Matrix.of(outerContext).client; final chunk = this.chunk; + final knock = chunk?.joinRule == 'knock'; final result = await showFutureLoadingDialog( context: context, future: () async { if (chunk != null && client.getRoomById(chunk.roomId) != null) { return chunk.roomId; } - final roomId = chunk != null && chunk.joinRule == 'knock' + final roomId = chunk != null && knock ? await client.knockRoom(chunk.roomId) : await client.joinRoom(roomAlias ?? chunk!.roomId); - if (client.getRoomById(roomId) == null) { + if (!knock && client.getRoomById(roomId) == null) { await client.waitForRoomInSync(roomId); } // #Pangea @@ -53,6 +54,9 @@ class PublicRoomBottomSheet extends StatelessWidget { return roomId; }, ); + if (knock) { + return; + } if (result.error == null) { Navigator.of(context).pop(); // don't open the room if the joined room is a space @@ -144,9 +148,15 @@ class PublicRoomBottomSheet extends StatelessWidget { child: ElevatedButton.icon( onPressed: () => _joinRoom(context), label: Text( - chunk?.roomType == 'm.space' - ? L10n.of(context)!.joinSpace - : L10n.of(context)!.joinRoom, + chunk?.joinRule == 'knock' && + Matrix.of(context) + .client + .getRoomById(chunk!.roomId) == + null + ? L10n.of(context)!.knock + : chunk?.roomType == 'm.space' + ? L10n.of(context)!.joinSpace + : L10n.of(context)!.joinRoom, ), icon: const Icon(Icons.login_outlined), ),