From beb142b340ea5425f740f9fb5cb5717d294a908c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 03:01:55 +0000 Subject: [PATCH 01/75] build: (deps): bump record from 6.1.2 to 6.2.0 Bumps [record](https://github.com/llfbandit/record) from 6.1.2 to 6.2.0. - [Commits](https://github.com/llfbandit/record/compare/6.1.2...6.2.0) --- updated-dependencies: - dependency-name: record dependency-version: 6.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pubspec.lock | 24 ++++++++++++------------ pubspec.yaml | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 22d6e0b0..6dc79d3b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1424,26 +1424,26 @@ packages: dependency: "direct main" description: name: record - sha256: "6bad72fb3ea6708d724cf8b6c97c4e236cf9f43a52259b654efeb6fd9b737f1f" + sha256: d5b6b334f3ab02460db6544e08583c942dbf23e3504bf1e14fd4cbe3d9409277 url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.2.0" record_android: dependency: transitive description: name: record_android - sha256: fb54ee4e28f6829b8c580252a9ef49d9c549cfd263b0660ad7eeac0908658e9f + sha256: "3bb3c6abbcb5fc1e86719fc6f0acdee89dfe8078543b92caad11854c487e435a" url: "https://pub.dev" source: hosted - version: "1.4.4" + version: "1.5.0" record_ios: dependency: transitive description: name: record_ios - sha256: "765b42ac1be019b1674ddd809b811fc721fe5a93f7bb1da7803f0d16772fd6d7" + sha256: "8df7c136131bd05efc19256af29b2ba6ccc000ccc2c80d4b6b6d7a8d21a3b5a9" url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "1.2.0" record_linux: dependency: transitive description: @@ -1456,26 +1456,26 @@ packages: dependency: transitive description: name: record_macos - sha256: "842ea4b7e95f4dd237aacffc686d1b0ff4277e3e5357865f8d28cd28bc18ed95" + sha256: f04d1547ff61ae54b4154e9726f656a17ad993f1a90f8f44bc40de94bafa072f url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.0" record_platform_interface: dependency: transitive description: name: record_platform_interface - sha256: b0065fdf1ec28f5a634d676724d388a77e43ce7646fb049949f58c69f3fcb4ed + sha256: "8a81dbc4e14e1272a285bbfef6c9136d070a47d9b0d1f40aa6193516253ee2f6" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" record_web: dependency: transitive description: name: record_web - sha256: "4f0adf20c9ccafcc02d71111fd91fba1ca7b17a7453902593e5a9b25b74a5c56" + sha256: "7e9846981c1f2d111d86f0ae3309071f5bba8b624d1c977316706f08fc31d16d" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" record_windows: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index dbd54938..065e506e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -67,7 +67,7 @@ dependencies: qr_code_scanner_plus: ^2.0.14 qr_image: ^1.0.0 receive_sharing_intent: ^1.8.1 - record: ^6.1.2 + record: ^6.2.0 scroll_to_index: ^3.0.1 share_plus: ^12.0.1 shared_preferences: ^2.5.4 # Pinned because https://github.com/flutter/flutter/issues/118401 From 64140f5781e44548db326c362645a79d5c86f46b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 21:41:20 +0000 Subject: [PATCH 02/75] build: (deps): bump webrtc_interface from 1.4.0 to 1.5.1 Bumps webrtc_interface from 1.4.0 to 1.5.1. --- updated-dependencies: - dependency-name: webrtc_interface dependency-version: 1.5.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 08fd79c7..3b6c6ab6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2119,10 +2119,10 @@ packages: dependency: "direct main" description: name: webrtc_interface - sha256: ad0e5786b2acd3be72a3219ef1dde9e1cac071cf4604c685f11b61d63cdd6eb3 + sha256: c6f100eac5057d9a817a60473126f9828c796d42884d498af4f339c97b21014f url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.1" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index cee10f98..d162429e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -78,7 +78,7 @@ dependencies: video_compress: ^3.1.4 video_player: ^2.11.1 wakelock_plus: ^1.5.0 - webrtc_interface: ^1.3.0 + webrtc_interface: ^1.5.1 dev_dependencies: dart_code_linter: ^3.2.1 From 2076671a7a1ac5b97016744186e7d3ea986d3fcd Mon Sep 17 00:00:00 2001 From: Jelv Date: Wed, 25 Mar 2026 14:55:03 +0100 Subject: [PATCH 03/75] chore(translations): Translated using Weblate (Dutch) Currently translated at 100.0% (763 of 763 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/nl/ --- lib/l10n/intl_nl.arb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/l10n/intl_nl.arb b/lib/l10n/intl_nl.arb index 2b33e66b..18ed4ccd 100644 --- a/lib/l10n/intl_nl.arb +++ b/lib/l10n/intl_nl.arb @@ -1953,7 +1953,7 @@ "unsupportedAndroidVersion": "Niet-ondersteunde Android-versie", "unsupportedAndroidVersionLong": "Voor deze functie is een nieuwe Android-versie verplicht. Controleer je updates of Lineage OS-ondersteuning.", "videoCallsBetaWarning": "Houd er rekening mee dat videogesprekken momenteel in bèta zijn. Ze werken misschien niet zoals je verwacht of werken niet op alle platformen.", - "voiceCall": "Spraakoproep", + "voiceCall": "Spraakgesprek", "confirmEventUnpin": "Weet je zeker dat je de gebeurtenis definitief wilt losmaken?", "experimentalVideoCalls": "Videogesprekken (experimenteel)", "youAcceptedTheInvitation": "👍 Je hebt de uitnodiging geaccepteerd", @@ -2799,5 +2799,13 @@ "supportFluffyChat": "FluffyChat steunen", "support": "Steunen", "setLowPriority": "Lage prioriteit instellen", - "unsetLowPriority": "Lage prioriteit uitschakelen" -} \ No newline at end of file + "unsetLowPriority": "Lage prioriteit uitschakelen", + "removeCallFromChat": "Verwijder oproep van chat", + "removeCallFromChatDescription": "Wil je de oproep voor iedereen in de chat verwijderen?", + "removeCallForEveryone": "Verwijder oproep voor iedereen", + "live": "Live", + "startVoiceCall": "Start audio-gesprek", + "startVideoCall": "Start video-gesprek", + "joinVoiceCall": "Audio-gesprek opnemen", + "joinVideoCall": "Deelnemen aan video-gesprek" +} From 86a09824b14320c823d8fd93f29265da481fd37e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aindri=C3=BA=20Mac=20Giolla=20Eoin?= Date: Tue, 24 Mar 2026 14:31:56 +0100 Subject: [PATCH 04/75] chore(translations): Translated using Weblate (Irish) Currently translated at 100.0% (763 of 763 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ga/ --- lib/l10n/intl_ga.arb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/l10n/intl_ga.arb b/lib/l10n/intl_ga.arb index 246b11f4..018e2283 100644 --- a/lib/l10n/intl_ga.arb +++ b/lib/l10n/intl_ga.arb @@ -2806,5 +2806,13 @@ "iDoNotWantToSupport": "Nílim ag iarraidh tacú leis", "iAlreadySupportFluffyChat": "Tacaím le FluffyChat cheana féin", "setLowPriority": "Socraigh tosaíocht íseal", - "unsetLowPriority": "Díshuiteáil tosaíocht íseal" -} \ No newline at end of file + "unsetLowPriority": "Díshuiteáil tosaíocht íseal", + "removeCallFromChat": "Bain glao den chomhrá", + "removeCallFromChatDescription": "Ar mhaith leat an glao a bhaint den chomhrá do gach ball?", + "removeCallForEveryone": "Bain glao do gach duine", + "startVoiceCall": "Tosaigh glao gutha", + "startVideoCall": "Tosaigh glao físe", + "joinVoiceCall": "Glac páirt i nglao gutha", + "joinVideoCall": "Glac páirt i nglao físe", + "live": "Beo" +} From 16fb244bc4f66e80c8464f8a5bf23a7e75b11051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Wed, 25 Mar 2026 17:59:30 +0100 Subject: [PATCH 05/75] chore: Add missing base href --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 5c94ada7..4ed6a3ef 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -29,7 +29,7 @@ jobs: run: ./scripts/prepare-web.sh - run: rm ./assets/vodozemac/.gitignore - name: Build Release Web - run: flutter build web --dart-define=FLUTTER_WEB_CANVASKIT_URL=canvaskit/ --release --source-maps + run: flutter build web --dart-define=FLUTTER_WEB_CANVASKIT_URL=canvaskit/ --release --source-maps --base-href "/web/" - name: Create archive run: tar -czf fluffychat-web.tar.gz build/web/ - name: Upload Web Build From af90170d180920de9e6dd0c2f3cbd674910e5a8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Fri, 27 Mar 2026 07:53:18 +0100 Subject: [PATCH 06/75] chore: Correctly pass md to website --- .github/workflows/release.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 4ed6a3ef..8c7dfe92 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -49,8 +49,8 @@ jobs: - name: Clone fluffychat website run: | git clone https://github.com/krille-chan/fluffychat-website.git - cp CHANGELOG.md fluffychat-website/ - cp PRIVACY.md fluffychat-website/ + cp CHANGELOG.md fluffychat-website/src/ + cp PRIVACY.md fluffychat-website/src/ - name: Build website working-directory: fluffychat-website run: | From 2ad2b1e52c0a7067223395e1b1d2f41f19a0f783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Sun, 15 Mar 2026 07:43:47 +0100 Subject: [PATCH 07/75] build: Update fcm shared isolate --- .github/workflows/integrate.yaml | 5 +---- ios/Runner/AppDelegate.swift | 4 +++- lib/utils/background_push.dart | 5 ++++- scripts/add-firebase-messaging.sh | 2 +- scripts/release-ios-testflight.sh | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index 81669419..cf57ba7a 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -128,10 +128,7 @@ jobs: run: sudo xcode-select --switch /Applications/Xcode_16.4.app - run: brew install sqlcipher - uses: moonrepo/setup-rust@v1 - - name: Add Firebase Messaging - run: | - flutter pub add fcm_shared_isolate:0.1.0 - sed -i '' 's,//,,g' lib/utils/background_push.dart + - run: ./scripts/add-firebase-messaging.sh - run: flutter pub get - run: flutter build ios --no-codesign diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 6bea1100..6fc5538a 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -12,5 +12,7 @@ import Flutter func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) - } + + // From https://pub.dev/packages/flutter_local_notifications#-ios-setup + UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate } } diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart index 98f81eeb..a4265231 100644 --- a/lib/utils/background_push.dart +++ b/lib/utils/background_push.dart @@ -122,7 +122,7 @@ class BackgroundPush { //firebase.setListeners( // onMessage: (message) => pushHelper( // PushNotification.fromJson( - // Map.from(message['data'] ?? message), + // message.tryGetMap('data') ?? message, // ), // client: client, // l10n: l10n, @@ -351,6 +351,9 @@ class BackgroundPush { Future setupFirebase() async { Logs().v('Setup firebase'); if (_fcmToken?.isEmpty ?? true) { + if (PlatformInfos.isIOS) { + //await firebase.requestPermission(); + } try { //_fcmToken = await firebase.getToken(); if (_fcmToken == null) throw ('PushToken is null'); diff --git a/scripts/add-firebase-messaging.sh b/scripts/add-firebase-messaging.sh index fb845d71..af7ce025 100755 --- a/scripts/add-firebase-messaging.sh +++ b/scripts/add-firebase-messaging.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -flutter pub add fcm_shared_isolate:0.2.0 +flutter pub add fcm_shared_isolate flutter pub get if [[ "$OSTYPE" == "darwin"* ]]; then diff --git a/scripts/release-ios-testflight.sh b/scripts/release-ios-testflight.sh index 5eef1662..075324c6 100755 --- a/scripts/release-ios-testflight.sh +++ b/scripts/release-ios-testflight.sh @@ -1,5 +1,5 @@ #!/bin/sh -ve -flutter pub add fcm_shared_isolate:0.2.0 +flutter pub add fcm_shared_isolate sed -i '' 's,//,,g' lib/utils/background_push.dart flutter clean flutter pub get From 24906a0def64ae283fb6c04161e9b28d6aaacda9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Fri, 27 Mar 2026 08:17:51 +0100 Subject: [PATCH 08/75] build: Add changelog for 2.5.1 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- snap/snapcraft.yaml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c71b8ca..3a4177da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## v2.5.1 +Update to latest version of fcm_shared_isolate to fix push on iOS. + ## v2.5.0 FluffyChat 2.5.0 introduces a new homeserver picker for onboarding, better image compression performance and several smaller new features, design adjustments and bug fixes. diff --git a/pubspec.yaml b/pubspec.yaml index 400dd469..6eabbbc0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none # On version bump please also increase: # 1. The build number (for F-Droid) # 2. The version in /snap/snapcraft.yaml -version: 2.5.0+3550 +version: 2.5.1+3551 environment: sdk: ">=3.11.1 <4.0.0" diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index ebc618ce..71245163 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,7 +1,7 @@ name: fluffychat title: FluffyChat base: core24 -version: 2.5.0 +version: 2.5.1 license: AGPL-3.0 summary: The cutest messenger in the Matrix network description: | From cf4f5a24ab311ae5d3c55947f6f14dd63d67e67d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Fri, 27 Mar 2026 10:50:04 +0100 Subject: [PATCH 09/75] chore: Follow up fix build website --- .github/workflows/release.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 8c7dfe92..85eafd63 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -49,8 +49,8 @@ jobs: - name: Clone fluffychat website run: | git clone https://github.com/krille-chan/fluffychat-website.git - cp CHANGELOG.md fluffychat-website/src/ - cp PRIVACY.md fluffychat-website/src/ + cat CHANGELOG.md >> fluffychat-website/src/privacy.md + cat PRIVACY.md >> fluffychat-website/src/changelog.md - name: Build website working-directory: fluffychat-website run: | From a3fc1bff01796e90de8a84db2607e29e9b6b4991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Fri, 27 Mar 2026 11:47:43 +0100 Subject: [PATCH 10/75] chore: Follow up build website --- .github/workflows/release.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 85eafd63..133f099c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -49,8 +49,8 @@ jobs: - name: Clone fluffychat website run: | git clone https://github.com/krille-chan/fluffychat-website.git - cat CHANGELOG.md >> fluffychat-website/src/privacy.md - cat PRIVACY.md >> fluffychat-website/src/changelog.md + cat CHANGELOG.md >> fluffychat-website/src/changelog.md + cat PRIVACY.md >> fluffychat-website/src/privacy.md - name: Build website working-directory: fluffychat-website run: | From 3296c0d92d92d82e7e6d7649ee9d9e8187f253fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Wed, 25 Mar 2026 10:42:17 +0100 Subject: [PATCH 11/75] refactor: Enable lint use_build_context_synchronously --- analysis_options.yaml | 1 - lib/pages/archive/archive.dart | 1 + lib/pages/bootstrap/bootstrap_dialog.dart | 5 +- lib/pages/chat/chat.dart | 108 ++++++++++-------- lib/pages/chat/events/audio_player.dart | 2 + lib/pages/chat/events/cute_events.dart | 1 + lib/pages/chat/events/message_content.dart | 2 + lib/pages/chat/pinned_events.dart | 8 +- lib/pages/chat/recording_view_model.dart | 4 + lib/pages/chat/send_file_dialog.dart | 1 + lib/pages/chat/send_location_dialog.dart | 1 + lib/pages/chat/start_poll_bottom_sheet.dart | 1 + .../chat_access_settings_controller.dart | 42 ++++--- lib/pages/chat_details/chat_details.dart | 46 +++++--- .../chat_encryption_settings.dart | 33 +++--- lib/pages/chat_list/chat_list.dart | 54 +++++---- .../chat_list/client_chooser_button.dart | 1 + lib/pages/chat_list/space_view.dart | 3 + .../chat_permissions_settings.dart | 1 + .../device_settings/device_settings.dart | 44 ++++--- lib/pages/image_viewer/video_player.dart | 2 + .../intro/flows/restore_backup_flow.dart | 5 +- lib/pages/intro/intro_page.dart | 1 + lib/pages/intro/intro_page_presenter.dart | 3 +- .../invitation_selection.dart | 10 +- .../key_verification_dialog.dart | 8 +- lib/pages/login/login.dart | 50 +++++--- .../new_private_chat/new_private_chat.dart | 19 +-- .../new_private_chat/qr_scanner_modal.dart | 1 + lib/pages/settings/settings.dart | 44 ++++--- lib/pages/settings_3pid/settings_3pid.dart | 47 +++++--- .../import_archive_dialog.dart | 4 +- .../settings_emotes/settings_emotes.dart | 15 ++- .../settings_notifications.dart | 1 + .../settings_password/settings_password.dart | 6 +- .../settings_security/settings_security.dart | 47 ++++---- lib/pages/settings_style/settings_style.dart | 1 + lib/utils/fluffy_share.dart | 13 ++- .../event_extension.dart | 2 + lib/utils/platform_infos.dart | 10 +- lib/utils/show_update_snackbar.dart | 5 +- lib/utils/sign_in_flows/check_homeserver.dart | 4 + lib/utils/url_launcher.dart | 23 ++-- .../adaptive_dialogs/public_room_dialog.dart | 10 +- lib/widgets/adaptive_dialogs/user_dialog.dart | 1 + lib/widgets/chat_settings_popup_menu.dart | 10 +- lib/widgets/future_loading_dialog.dart | 10 ++ .../local_notifications_extension.dart | 5 +- lib/widgets/matrix.dart | 24 ++-- .../member_actions_popup_menu_button.dart | 43 ++++--- 50 files changed, 490 insertions(+), 293 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 05852ec2..a2bf618f 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -36,7 +36,6 @@ analyzer: - dart_code_linter errors: todo: ignore - use_build_context_synchronously: ignore exclude: - lib/l10n/*.dart diff --git a/lib/pages/archive/archive.dart b/lib/pages/archive/archive.dart index 5f8c1e23..7e22c861 100644 --- a/lib/pages/archive/archive.dart +++ b/lib/pages/archive/archive.dart @@ -48,6 +48,7 @@ class ArchiveController extends State { OkCancelResult.ok) { return; } + if (!mounted) return; await showFutureLoadingDialog( context: context, futureWithProgress: (onProgress) async { diff --git a/lib/pages/bootstrap/bootstrap_dialog.dart b/lib/pages/bootstrap/bootstrap_dialog.dart index 4ade9f98..2e3af6a8 100644 --- a/lib/pages/bootstrap/bootstrap_dialog.dart +++ b/lib/pages/bootstrap/bootstrap_dialog.dart @@ -382,6 +382,7 @@ class BootstrapDialogState extends State { ).wrongRecoveryKey, ); } catch (e, s) { + if (!context.mounted) return; ErrorReporter( context, 'Unable to open SSSS with recovery key', @@ -425,6 +426,7 @@ class BootstrapDialogState extends State { cancelLabel: L10n.of(context).cancel, ); if (consent != OkCancelResult.ok) return; + if (!context.mounted) return; final req = await showFutureLoadingDialog( context: context, delay: false, @@ -435,11 +437,12 @@ class BootstrapDialogState extends State { }, ); if (req.error != null) return; + if (!context.mounted) return; final success = await KeyVerificationDialog( request: req.result!, ).show(context); if (success != true) return; - if (!mounted) return; + if (!context.mounted) return; final result = await showFutureLoadingDialog( context: context, diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 2dfc70d5..7572566a 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -211,6 +211,7 @@ class ChatController extends State context: context, future: room.leave, ); + if (!mounted) return; if (success.error != null) return; context.go('/rooms'); } @@ -475,8 +476,9 @@ class ChatController extends State Future? loadTimelineFuture; Future _getTimeline({String? eventContextId}) async { - await Matrix.of(context).client.roomsLoading; - await Matrix.of(context).client.accountDataLoading; + final matrix = Matrix.of(context); + await matrix.client.roomsLoading; + await matrix.client.accountDataLoading; if (eventContextId != null && (!eventContextId.isValidMatrixId || eventContextId.sigil != '\$')) { eventContextId = null; @@ -632,6 +634,7 @@ class ChatController extends State Future sendFileAction({FileType type = FileType.any}) async { final files = await selectFiles(context, allowMultiple: true, type: type); if (files.isEmpty) return; + if (!mounted) return; await showAdaptiveDialog( context: context, builder: (c) => SendFileDialog( @@ -663,6 +666,7 @@ class ChatController extends State FocusScope.of(context).requestFocus(FocusNode()); final file = await ImagePicker().pickImage(source: ImageSource.camera); if (file == null) return; + if (!mounted) return; await showAdaptiveDialog( context: context, @@ -684,6 +688,7 @@ class ChatController extends State maxDuration: const Duration(minutes: 1), ); if (file == null) return; + if (!mounted) return; await showAdaptiveDialog( context: context, @@ -726,26 +731,27 @@ class ChatController extends State mimeType: mimeType, ); - room - .sendFileEvent( - file, - inReplyTo: replyEvent, - threadRootEventId: activeThreadId, - extraContent: { - 'info': {...file.info, 'duration': duration}, - 'org.matrix.msc3245.voice': {}, - 'org.matrix.msc1767.audio': { - 'duration': duration, - 'waveform': waveform, - }, + try { + await room.sendFileEvent( + file, + inReplyTo: replyEvent, + threadRootEventId: activeThreadId, + extraContent: { + 'info': {...file.info, 'duration': duration}, + 'org.matrix.msc3245.voice': {}, + 'org.matrix.msc1767.audio': { + 'duration': duration, + 'waveform': waveform, }, - ) - .catchError((e) { - scaffoldMessenger.showSnackBar( - SnackBar(content: Text((e as Object).toLocalizedString(context))), - ); - return null; - }); + }, + ); + } catch (e) { + if (!mounted) return; + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(e.toLocalizedString(context))), + ); + return; + } setState(() { replyEvent = null; }); @@ -807,29 +813,30 @@ class ChatController extends State Future reportEventAction() async { final event = selectedEvents.single; + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); final score = await showModalActionPopup( context: context, - title: L10n.of(context).reportMessage, - message: L10n.of(context).howOffensiveIsThisContent, - cancelLabel: L10n.of(context).cancel, + title: l10n.reportMessage, + message: l10n.howOffensiveIsThisContent, + cancelLabel: l10n.cancel, actions: [ - AdaptiveModalAction( - value: -100, - label: L10n.of(context).extremeOffensive, - ), - AdaptiveModalAction(value: -50, label: L10n.of(context).offensive), - AdaptiveModalAction(value: 0, label: L10n.of(context).inoffensive), + AdaptiveModalAction(value: -100, label: l10n.extremeOffensive), + AdaptiveModalAction(value: -50, label: l10n.offensive), + AdaptiveModalAction(value: 0, label: l10n.inoffensive), ], ); if (score == null) return; + if (!mounted) return; final reason = await showTextInputDialog( context: context, - title: L10n.of(context).whyDoYouWantToReportThis, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - hintText: L10n.of(context).reason, + title: l10n.whyDoYouWantToReportThis, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, + hintText: l10n.reason, ); if (reason == null || reason.isEmpty) return; + if (!mounted) return; final result = await showFutureLoadingDialog( context: context, future: () => Matrix.of(context).client.reportEvent( @@ -840,12 +847,13 @@ class ChatController extends State ), ); if (result.error != null) return; + if (!mounted) return; setState(() { showEmojiPicker = false; selectedEvents.clear(); }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(L10n.of(context).contentHasBeenReported)), + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.contentHasBeenReported)), ); } @@ -861,6 +869,7 @@ class ChatController extends State } setState(selectedEvents.clear); } catch (e, s) { + if (!mounted) return; ErrorReporter( context, 'Error while delete error events action', @@ -885,6 +894,7 @@ class ChatController extends State : null; if (reasonInput == null) return; final reason = reasonInput.isEmpty ? null : reasonInput; + if (!mounted) return; await showFutureLoadingDialog( context: context, futureWithProgress: (onProgress) async { @@ -1239,6 +1249,7 @@ class ChatController extends State okLabel: L10n.of(context).unpin, cancelLabel: L10n.of(context).cancel, ); + if (!mounted) return; if (response == OkCancelResult.ok) { final events = room.pinnedEventIds ..removeWhere((oldEvent) => oldEvent == eventId); @@ -1328,17 +1339,18 @@ class ChatController extends State Future onPhoneButtonTap() async { // VoIP required Android SDK 21 if (PlatformInfos.isAndroid) { - DeviceInfoPlugin().androidInfo.then((value) { - if (value.version.sdkInt < 21) { - Navigator.pop(context); - showOkAlertDialog( - context: context, - title: L10n.of(context).unsupportedAndroidVersion, - message: L10n.of(context).unsupportedAndroidVersionLong, - okLabel: L10n.of(context).close, - ); - } - }); + final androidInfo = await DeviceInfoPlugin().androidInfo; + if (!mounted) return; + if (androidInfo.version.sdkInt < 21) { + Navigator.pop(context); + await showOkAlertDialog( + context: context, + title: L10n.of(context).unsupportedAndroidVersion, + message: L10n.of(context).unsupportedAndroidVersionLong, + okLabel: L10n.of(context).close, + ); + return; + } } final callType = await showModalActionPopup( context: context, @@ -1359,11 +1371,13 @@ class ChatController extends State ], ); if (callType == null) return; + if (!mounted) return; final voipPlugin = Matrix.of(context).voipPlugin; try { await voipPlugin!.voip.inviteToCall(room, callType); } catch (e) { + if (!mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context)))); diff --git a/lib/pages/chat/events/audio_player.dart b/lib/pages/chat/events/audio_player.dart index 9fc2dbf1..413aef01 100644 --- a/lib/pages/chat/events/audio_player.dart +++ b/lib/pages/chat/events/audio_player.dart @@ -189,6 +189,7 @@ class AudioPlayerState extends State { }); } catch (e, s) { Logs().v('Could not download audio file', e, s); + if (!mounted) rethrow; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context)))); @@ -208,6 +209,7 @@ class AudioPlayerState extends State { ), ); } + if (!mounted) return; audioPlayer.play().onError( ErrorReporter(context, 'Unable to play audio message').onErrorCallback, diff --git a/lib/pages/chat/events/cute_events.dart b/lib/pages/chat/events/cute_events.dart index 01d53c6d..1c84f59f 100644 --- a/lib/pages/chat/events/cute_events.dart +++ b/lib/pages/chat/events/cute_events.dart @@ -50,6 +50,7 @@ class _CuteContentState extends State { Future addOverlay() async { _isOverlayShown = true; await Future.delayed(const Duration(milliseconds: 50)); + if (!mounted) return; OverlayEntry? overlay; overlay = OverlayEntry( diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index eb5aa649..1ec5f5f0 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -61,9 +61,11 @@ class MessageContent extends StatelessWidget { final client = Matrix.of(context).client; final state = await client.getCryptoIdentityState(); if (!state.connected) { + if (!context.mounted) return; final success = await context.push('/backup'); if (success != true) return; } + if (!context.mounted) return; event.requestKey(); final sender = event.senderFromMemoryOrFallback; await showAdaptiveBottomSheet( diff --git a/lib/pages/chat/pinned_events.dart b/lib/pages/chat/pinned_events.dart index 53b6ebcf..e183e918 100644 --- a/lib/pages/chat/pinned_events.dart +++ b/lib/pages/chat/pinned_events.dart @@ -15,6 +15,7 @@ class PinnedEvents extends StatelessWidget { const PinnedEvents(this.controller, {super.key}); Future _displayPinnedEventsDialog(BuildContext context) async { + final l10n = L10n.of(context); final eventsResult = await showFutureLoadingDialog( context: context, future: () => Future.wait( @@ -25,13 +26,14 @@ class PinnedEvents extends StatelessWidget { ); final events = eventsResult.result; if (events == null) return; + if (!context.mounted) return; final eventId = events.length == 1 ? events.single?.eventId : await showModalActionPopup( context: context, - title: L10n.of(context).pin, - cancelLabel: L10n.of(context).cancel, + title: l10n.pin, + cancelLabel: l10n.cancel, actions: events .map( (event) => AdaptiveModalAction( @@ -39,7 +41,7 @@ class PinnedEvents extends StatelessWidget { icon: const Icon(Icons.push_pin_outlined), label: event?.calcLocalizedBodyFallback( - MatrixLocals(L10n.of(context)), + MatrixLocals(l10n), withSenderNamePrefix: true, hideReply: true, ) ?? diff --git a/lib/pages/chat/recording_view_model.dart b/lib/pages/chat/recording_view_model.dart index d66ccbe4..e7d85e0d 100644 --- a/lib/pages/chat/recording_view_model.dart +++ b/lib/pages/chat/recording_view_model.dart @@ -44,6 +44,7 @@ class RecordingViewModelState extends State { room.client.getConfig(); // Preload server file configuration. if (PlatformInfos.isAndroid) { final info = await DeviceInfoPlugin().androidInfo; + if (!mounted) return; if (info.version.sdkInt < 19) { showOkAlertDialog( context: context, @@ -76,6 +77,7 @@ class RecordingViewModelState extends State { final result = await audioRecorder.hasPermission(); if (result != true) { + if (!mounted) return; showOkAlertDialog( context: context, title: L10n.of(context).oopsSomethingWentWrong, @@ -97,10 +99,12 @@ class RecordingViewModelState extends State { ), path: path ?? '', ); + if (!mounted) return; setState(() => duration = Duration.zero); _subscribe(); } catch (e, s) { Logs().w('Unable to start voice message recording', e, s); + if (!mounted) return; showOkAlertDialog( context: context, title: L10n.of(context).oopsSomethingWentWrong, diff --git a/lib/pages/chat/send_file_dialog.dart b/lib/pages/chat/send_file_dialog.dart index ef6b9cb5..0b6d14f3 100644 --- a/lib/pages/chat/send_file_dialog.dart +++ b/lib/pages/chat/send_file_dialog.dart @@ -146,6 +146,7 @@ class SendFileDialogState extends State { scaffoldMessenger.clearSnackBars(); } catch (e) { scaffoldMessenger.clearSnackBars(); + if (!mounted || !widget.outerContext.mounted) rethrow; final theme = Theme.of(context); scaffoldMessenger.showSnackBar( SnackBar( diff --git a/lib/pages/chat/send_location_dialog.dart b/lib/pages/chat/send_location_dialog.dart index 5c460eb6..b5cde79e 100644 --- a/lib/pages/chat/send_location_dialog.dart +++ b/lib/pages/chat/send_location_dialog.dart @@ -81,6 +81,7 @@ class SendLocationDialogState extends State { context: context, future: () => widget.room.sendLocation(body, uri), ); + if (!mounted) return; Navigator.of(context, rootNavigator: false).pop(); } diff --git a/lib/pages/chat/start_poll_bottom_sheet.dart b/lib/pages/chat/start_poll_bottom_sheet.dart index 5612f7fe..eb054dd7 100644 --- a/lib/pages/chat/start_poll_bottom_sheet.dart +++ b/lib/pages/chat/start_poll_bottom_sheet.dart @@ -44,6 +44,7 @@ class _StartPollBottomSheetState extends State { maxSelections: _allowMultipleAnswers ? _answers.length : 1, txid: _txid, ); + if (!mounted) return; Navigator.of(context).pop(); } catch (e, s) { Logs().w('Unable to create poll', e, s); diff --git a/lib/pages/chat_access_settings/chat_access_settings_controller.dart b/lib/pages/chat_access_settings/chat_access_settings_controller.dart index e090e863..1c55f889 100644 --- a/lib/pages/chat_access_settings/chat_access_settings_controller.dart +++ b/lib/pages/chat_access_settings/chat_access_settings_controller.dart @@ -160,6 +160,7 @@ class ChatAccessSettingsController extends State { } Future updateRoomAction() async { + final l10n = L10n.of(context); final roomVersion = room .getState(EventTypes.RoomCreate)! .content @@ -170,10 +171,11 @@ class ChatAccessSettingsController extends State { ); final capabilities = capabilitiesResult.result; if (capabilities == null) return; + if (!mounted) return; final newVersion = await showModalActionPopup( context: context, - title: L10n.of(context).replaceRoomWithNewerVersion, - cancelLabel: L10n.of(context).cancel, + title: l10n.replaceRoomWithNewerVersion, + cancelLabel: l10n.cancel, actions: capabilities.mRoomVersions!.available.entries .where((r) => r.key != roomVersion) .map( @@ -185,18 +187,20 @@ class ChatAccessSettingsController extends State { ) .toList(), ); - if (newVersion == null || - OkCancelResult.cancel == - await showOkCancelAlertDialog( - context: context, - okLabel: L10n.of(context).yes, - cancelLabel: L10n.of(context).cancel, - title: L10n.of(context).areYouSure, - message: L10n.of(context).roomUpgradeDescription, - isDestructive: true, - )) { + if (newVersion == null) return; + if (!mounted) return; + final confirmUpgrade = await showOkCancelAlertDialog( + context: context, + okLabel: l10n.yes, + cancelLabel: l10n.cancel, + title: l10n.areYouSure, + message: l10n.roomUpgradeDescription, + isDestructive: true, + ); + if (confirmUpgrade == OkCancelResult.cancel) { return; } + if (!mounted) return; final result = await showFutureLoadingDialog( context: context, futureWithProgress: (onProgress) async { @@ -243,6 +247,7 @@ class ChatAccessSettingsController extends State { } Future addAlias() async { + final l10n = L10n.of(context); final domain = room.client.userID?.domain; if (domain == null) { throw Exception('userID or domain is null! This should never happen.'); @@ -250,11 +255,12 @@ class ChatAccessSettingsController extends State { final input = await showTextInputDialog( context: context, - title: L10n.of(context).editRoomAliases, + title: l10n.editRoomAliases, prefixText: '#', suffixText: domain, - hintText: L10n.of(context).alias, + hintText: l10n.alias, ); + if (!mounted) return; final aliasLocalpart = input?.trim(); if (aliasLocalpart == null || aliasLocalpart.isEmpty) return; final alias = '#$aliasLocalpart:$domain'; @@ -264,17 +270,19 @@ class ChatAccessSettingsController extends State { future: () => room.client.setRoomAlias(alias, room.id), ); if (result.error != null) return; + if (!mounted) return; setState(() {}); if (!room.canChangeStateEvent(EventTypes.RoomCanonicalAlias)) return; final canonicalAliasConsent = await showOkCancelAlertDialog( context: context, - title: L10n.of(context).setAsCanonicalAlias, + title: l10n.setAsCanonicalAlias, message: alias, - okLabel: L10n.of(context).yes, - cancelLabel: L10n.of(context).no, + okLabel: l10n.yes, + cancelLabel: l10n.no, ); + if (!mounted) return; final altAliases = room diff --git a/lib/pages/chat_details/chat_details.dart b/lib/pages/chat_details/chat_details.dart index d227e24e..03bb8e61 100644 --- a/lib/pages/chat_details/chat_details.dart +++ b/lib/pages/chat_details/chat_details.dart @@ -37,69 +37,78 @@ class ChatDetailsController extends State { String? get roomId => widget.roomId; Future setDisplaynameAction() async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); final room = Matrix.of(context).client.getRoomById(roomId!)!; final input = await showTextInputDialog( context: context, - title: L10n.of(context).changeTheNameOfTheGroup, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - initialText: room.getLocalizedDisplayname(MatrixLocals(L10n.of(context))), + title: l10n.changeTheNameOfTheGroup, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, + initialText: room.getLocalizedDisplayname(MatrixLocals(l10n)), ); if (input == null) return; + if (!mounted) return; final success = await showFutureLoadingDialog( context: context, future: () => room.setName(input), ); + if (!mounted) return; if (success.error == null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(L10n.of(context).displaynameHasBeenChanged)), + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.displaynameHasBeenChanged)), ); } } Future setTopicAction() async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); final room = Matrix.of(context).client.getRoomById(roomId!)!; final input = await showTextInputDialog( context: context, - title: L10n.of(context).setChatDescription, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - hintText: L10n.of(context).noChatDescriptionYet, + title: l10n.setChatDescription, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, + hintText: l10n.noChatDescriptionYet, initialText: room.topic, minLines: 4, maxLines: 8, ); if (input == null) return; + if (!mounted) return; final success = await showFutureLoadingDialog( context: context, future: () => room.setDescription(input), ); + if (!mounted) return; if (success.error == null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(L10n.of(context).chatDescriptionHasBeenChanged)), + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.chatDescriptionHasBeenChanged)), ); } } Future setAvatarAction() async { + final l10n = L10n.of(context); final room = Matrix.of(context).client.getRoomById(roomId!); final actions = [ if (PlatformInfos.isMobile) AdaptiveModalAction( value: AvatarAction.camera, - label: L10n.of(context).openCamera, + label: l10n.openCamera, isDefaultAction: true, icon: const Icon(Icons.camera_alt_outlined), ), AdaptiveModalAction( value: AvatarAction.file, - label: L10n.of(context).openGallery, + label: l10n.openGallery, icon: const Icon(Icons.photo_outlined), ), if (room?.avatar != null) AdaptiveModalAction( value: AvatarAction.remove, - label: L10n.of(context).delete, + label: l10n.delete, isDestructive: true, icon: const Icon(Icons.delete_outlined), ), @@ -108,11 +117,12 @@ class ChatDetailsController extends State { ? actions.single.value : await showModalActionPopup( context: context, - title: L10n.of(context).editRoomAvatar, - cancelLabel: L10n.of(context).cancel, + title: l10n.editRoomAvatar, + cancelLabel: l10n.cancel, actions: actions, ); if (action == null) return; + if (!mounted) return; if (action == AvatarAction.remove) { await showFutureLoadingDialog( context: context, @@ -131,6 +141,7 @@ class ChatDetailsController extends State { if (result == null) return; file = MatrixFile(bytes: await result.readAsBytes(), name: result.path); } else { + if (!mounted) return; final picked = await selectFiles( context, allowMultiple: false, @@ -143,6 +154,7 @@ class ChatDetailsController extends State { name: pickedFile.name, ); } + if (!mounted) return; await showFutureLoadingDialog( context: context, future: () => room!.setAvatar(file), diff --git a/lib/pages/chat_encryption_settings/chat_encryption_settings.dart b/lib/pages/chat_encryption_settings/chat_encryption_settings.dart index efcaf751..6e17d92f 100644 --- a/lib/pages/chat_encryption_settings/chat_encryption_settings.dart +++ b/lib/pages/chat_encryption_settings/chat_encryption_settings.dart @@ -30,38 +30,40 @@ class ChatEncryptionSettingsController extends State { } Future enableEncryption(_) async { + final l10n = L10n.of(context); if (room.encrypted) { showOkAlertDialog( context: context, - title: L10n.of(context).sorryThatsNotPossible, - message: L10n.of(context).disableEncryptionWarning, + title: l10n.sorryThatsNotPossible, + message: l10n.disableEncryptionWarning, ); return; } if (room.joinRules == JoinRules.public) { showOkAlertDialog( context: context, - title: L10n.of(context).sorryThatsNotPossible, - message: L10n.of(context).noEncryptionForPublicRooms, + title: l10n.sorryThatsNotPossible, + message: l10n.noEncryptionForPublicRooms, ); return; } if (!room.canChangeStateEvent(EventTypes.Encryption)) { showOkAlertDialog( context: context, - title: L10n.of(context).sorryThatsNotPossible, - message: L10n.of(context).noPermission, + title: l10n.sorryThatsNotPossible, + message: l10n.noPermission, ); return; } final consent = await showOkCancelAlertDialog( context: context, - title: L10n.of(context).areYouSure, - message: L10n.of(context).enableEncryptionWarning, - okLabel: L10n.of(context).yes, - cancelLabel: L10n.of(context).cancel, + title: l10n.areYouSure, + message: l10n.enableEncryptionWarning, + okLabel: l10n.yes, + cancelLabel: l10n.cancel, ); if (consent != OkCancelResult.ok) return; + if (!mounted) return; await showFutureLoadingDialog( context: context, future: () => room.enableEncryption(), @@ -69,14 +71,16 @@ class ChatEncryptionSettingsController extends State { } Future startVerification() async { + final l10n = L10n.of(context); final consent = await showOkCancelAlertDialog( context: context, - title: L10n.of(context).verifyOtherUser, - message: L10n.of(context).verifyOtherUserDescription, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, + title: l10n.verifyOtherUser, + message: l10n.verifyOtherUserDescription, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, ); if (consent != OkCancelResult.ok) return; + if (!mounted) return; final req = await room.client.userDeviceKeys[room.directChatMatrixID]! .startVerification(); req.onUpdate = () { @@ -84,6 +88,7 @@ class ChatEncryptionSettingsController extends State { setState(() {}); } }; + if (!mounted) return; await KeyVerificationDialog(request: req).show(context); } diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index bb00e694..9ac99e30 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -90,6 +90,8 @@ class ChatListController extends State }); Future onChatTap(Room room) async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); if (room.membership == Membership.invite) { final joinResult = await showFutureLoadingDialog( context: context, @@ -105,10 +107,11 @@ class ChatListController extends State ); if (joinResult.error != null) return; } + if (!mounted) return; if (room.membership == Membership.ban) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(L10n.of(context).youHaveBeenBannedFromThisChat)), + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.youHaveBeenBannedFromThisChat)), ); return; } @@ -156,23 +159,25 @@ class ChatListController extends State static const String _serverStoreNamespace = 'im.fluffychat.search.server'; Future setServer() async { + final matrix = Matrix.of(context); + final l10n = L10n.of(context); final newServer = await showTextInputDialog( useRootNavigator: false, - title: L10n.of(context).changeTheHomeserver, + title: l10n.changeTheHomeserver, context: context, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, prefixText: 'https://', - hintText: Matrix.of(context).client.homeserver?.host, + hintText: matrix.client.homeserver?.host, initialText: searchServer, keyboardType: TextInputType.url, autocorrect: false, - validator: (server) => server.contains('.') == true - ? null - : L10n.of(context).invalidServerName, + validator: (server) => + server.contains('.') == true ? null : l10n.invalidServerName, ); if (newServer == null) return; - Matrix.of(context).store.setString(_serverStoreNamespace, newServer); + if (!mounted) return; + matrix.store.setString(_serverStoreNamespace, newServer); setState(() { searchServer = newServer; }); @@ -185,6 +190,7 @@ class ChatListController extends State Future _search() async { final client = Matrix.of(context).client; + final scaffoldMessenger = ScaffoldMessenger.of(context); if (!isSearching) { setState(() { isSearching = true; @@ -227,9 +233,10 @@ class ChatListController extends State ); } catch (e, s) { Logs().w('Searching has crashed', e, s); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context)))); + if (!mounted) return; + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(e.toLocalizedString(context))), + ); } if (!isSearchMode) return; setState(() { @@ -293,9 +300,8 @@ class ChatListController extends State Future editSpace(BuildContext context, String spaceId) async { await Matrix.of(context).client.getRoomById(spaceId)!.postLoad(); - if (mounted) { - context.push('/rooms/$spaceId/details'); - } + if (!context.mounted) return; + context.push('/rooms/$spaceId/details'); } // Needs to match GroupsSpacesEntry for 'separate group' checking. @@ -742,6 +748,7 @@ class ChatListController extends State .toList(), ); if (space == null) return; + if (!mounted) return; await showFutureLoadingDialog( context: context, future: () => space.setSpaceChild(room.id), @@ -767,16 +774,18 @@ class ChatListController extends State } Future setStatus() async { + final l10n = L10n.of(context); final client = Matrix.of(context).client; final currentPresence = await client.fetchCurrentPresence(client.userID!); + if (!mounted) return; final input = await showTextInputDialog( useRootNavigator: false, context: context, - title: L10n.of(context).setStatus, - message: L10n.of(context).leaveEmptyToClearStatus, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - hintText: L10n.of(context).statusExampleMessage, + title: l10n.setStatus, + message: l10n.leaveEmptyToClearStatus, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, + hintText: l10n.statusExampleMessage, maxLines: 6, minLines: 1, maxLength: 255, @@ -904,18 +913,21 @@ class ChatListController extends State if (action == null) return; switch (action) { case EditBundleAction.addToBundle: + if (!mounted) return; final bundle = await showTextInputDialog( context: context, title: l10n.bundleName, hintText: l10n.bundleName, ); if (bundle == null || bundle.isEmpty || bundle.isEmpty) return; + if (!mounted) return; await showFutureLoadingDialog( context: context, future: () => client.setAccountBundle(bundle), ); break; case EditBundleAction.removeFromBundle: + if (!mounted) return; await showFutureLoadingDialog( context: context, future: () => client.removeFromAccountBundle(activeBundle!), diff --git a/lib/pages/chat_list/client_chooser_button.dart b/lib/pages/chat_list/client_chooser_button.dart index 64d7d08f..3e6a4110 100644 --- a/lib/pages/chat_list/client_chooser_button.dart +++ b/lib/pages/chat_list/client_chooser_button.dart @@ -209,6 +209,7 @@ class ClientChooserButton extends StatelessWidget { cancelLabel: L10n.of(context).cancel, ); if (consent != OkCancelResult.ok) return; + if (!context.mounted) return; context.go('/rooms/settings/addaccount'); break; case SettingsAction.newGroup: diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index b24f1993..bc4804ac 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -170,14 +170,17 @@ class _SpaceViewState extends State { switch (action) { case SpaceActions.settings: await space?.postLoad(); + if (!mounted) return; context.push('/rooms/${widget.spaceId}/details'); break; case SpaceActions.invite: await space?.postLoad(); + if (!mounted) return; context.push('/rooms/${widget.spaceId}/invite'); break; case SpaceActions.members: await space?.postLoad(); + if (!mounted) return; context.push('/rooms/${widget.spaceId}/details/members'); break; case SpaceActions.leave: diff --git a/lib/pages/chat_permissions_settings/chat_permissions_settings.dart b/lib/pages/chat_permissions_settings/chat_permissions_settings.dart index f7d342fe..c83064b5 100644 --- a/lib/pages/chat_permissions_settings/chat_permissions_settings.dart +++ b/lib/pages/chat_permissions_settings/chat_permissions_settings.dart @@ -36,6 +36,7 @@ class ChatPermissionsSettingsController extends State { currentLevel: currentLevel, ); if (newLevel == null) return; + if (!context.mounted) return; final content = Map.from( room.getState(EventTypes.RoomPowerLevels)!.content, ); diff --git a/lib/pages/device_settings/device_settings.dart b/lib/pages/device_settings/device_settings.dart index 05eb1efb..2e6f565e 100644 --- a/lib/pages/device_settings/device_settings.dart +++ b/lib/pages/device_settings/device_settings.dart @@ -41,12 +41,15 @@ class DevicesSettingsController extends State { Future _checkChatBackup() async { final client = Matrix.of(context).client; final state = await client.getCryptoIdentityState(); + if (!mounted) return; setState(() { chatBackupEnabled = state.initialized && !state.connected; }); } Future removeDevicesAction(List devices) async { + final l10n = L10n.of(context); + final matrix = Matrix.of(context); final client = Matrix.of(context).client; final wellKnown = await Result.capture(client.getWellknown()); @@ -57,18 +60,19 @@ class DevicesSettingsController extends State { launchUrlString(accountManageUrl, mode: LaunchMode.inAppBrowserView); return; } + if (!mounted) return; if (await showOkCancelAlertDialog( context: context, - title: L10n.of(context).areYouSure, - okLabel: L10n.of(context).remove, - cancelLabel: L10n.of(context).cancel, - message: L10n.of(context).removeDevicesDescription, + title: l10n.areYouSure, + okLabel: l10n.remove, + cancelLabel: l10n.cancel, + message: l10n.removeDevicesDescription, isDestructive: true, ) == OkCancelResult.cancel) { return; } - final matrix = Matrix.of(context); + if (!mounted) return; final deviceIds = []; for (final userDevice in devices) { deviceIds.add(userDevice.deviceId); @@ -85,19 +89,21 @@ class DevicesSettingsController extends State { } Future renameDeviceAction(Device device) async { + final l10n = L10n.of(context); + final matrix = Matrix.of(context); final displayName = await showTextInputDialog( context: context, - title: L10n.of(context).changeDeviceName, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, + title: l10n.changeDeviceName, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, hintText: device.displayName, ); if (displayName == null) return; + if (!mounted) return; final success = await showFutureLoadingDialog( context: context, - future: () => Matrix.of( - context, - ).client.updateDevice(device.deviceId, displayName: displayName), + future: () => + matrix.client.updateDevice(device.deviceId, displayName: displayName), ); if (success.error == null) { reload(); @@ -105,17 +111,20 @@ class DevicesSettingsController extends State { } Future verifyDeviceAction(Device device) async { + final l10n = L10n.of(context); + final matrix = Matrix.of(context); final consent = await showOkCancelAlertDialog( context: context, - title: L10n.of(context).verifyOtherDevice, - message: L10n.of(context).verifyOtherDeviceDescription, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, + title: l10n.verifyOtherDevice, + message: l10n.verifyOtherDeviceDescription, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, ); if (consent != OkCancelResult.ok) return; - final req = await Matrix.of(context) + if (!mounted) return; + final req = await matrix .client - .userDeviceKeys[Matrix.of(context).client.userID!]! + .userDeviceKeys[matrix.client.userID!]! .deviceKeys[device.deviceId]! .startVerification(); req.onUpdate = () { @@ -126,6 +135,7 @@ class DevicesSettingsController extends State { setState(() {}); } }; + if (!mounted) return; await KeyVerificationDialog(request: req).show(context); } diff --git a/lib/pages/image_viewer/video_player.dart b/lib/pages/image_viewer/video_player.dart index 7c17f80f..f97b3320 100644 --- a/lib/pages/image_viewer/video_player.dart +++ b/lib/pages/image_viewer/video_player.dart @@ -92,10 +92,12 @@ class EventVideoPlayerState extends State { ); }); } on IOException catch (e) { + if (!mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context)))); } catch (e, s) { + if (!mounted) return; ErrorReporter(context, 'Unable to play video').onErrorCallback(e, s); } } diff --git a/lib/pages/intro/flows/restore_backup_flow.dart b/lib/pages/intro/flows/restore_backup_flow.dart index 2e8e6d00..1db7a9d4 100644 --- a/lib/pages/intro/flows/restore_backup_flow.dart +++ b/lib/pages/intro/flows/restore_backup_flow.dart @@ -4,6 +4,7 @@ import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; Future restoreBackupFlow(BuildContext context) async { + final matrix = Matrix.of(context); final picked = await selectFiles(context); final file = picked.firstOrNull; if (file == null) return; @@ -12,9 +13,9 @@ Future restoreBackupFlow(BuildContext context) async { await showFutureLoadingDialog( context: context, future: () async { - final client = await Matrix.of(context).getLoginClient(); + final client = await matrix.getLoginClient(); await client.importDump(String.fromCharCodes(await file.readAsBytes())); - Matrix.of(context).initMatrix(); + matrix.initMatrix(); }, ); } diff --git a/lib/pages/intro/intro_page.dart b/lib/pages/intro/intro_page.dart index f57878a5..49fa0b70 100644 --- a/lib/pages/intro/intro_page.dart +++ b/lib/pages/intro/intro_page.dart @@ -166,6 +166,7 @@ class IntroPage extends StatelessWidget { final client = await Matrix.of( context, ).getLoginClient(); + if (!context.mounted) return; context.go( '${GoRouterState.of(context).uri.path}/login', extra: client, diff --git a/lib/pages/intro/intro_page_presenter.dart b/lib/pages/intro/intro_page_presenter.dart index 27cb86bb..34bddfc7 100644 --- a/lib/pages/intro/intro_page_presenter.dart +++ b/lib/pages/intro/intro_page_presenter.dart @@ -75,7 +75,8 @@ class _IntroPagePresenterState extends State { final client = await Matrix.of(context).getLoginClient(); await client.checkHomeserver(homeserverUrl); await client.oidcLogin(session: session, code: code, state: state); - if (context.mounted) context.go('/backup'); + if (!mounted) return; + context.go('/backup'); } catch (e, s) { Logs().w('Unable to login via OIDC', e, s); if (mounted) { diff --git a/lib/pages/invitation_selection/invitation_selection.dart b/lib/pages/invitation_selection/invitation_selection.dart index ff769106..e0941233 100644 --- a/lib/pages/invitation_selection/invitation_selection.dart +++ b/lib/pages/invitation_selection/invitation_selection.dart @@ -54,6 +54,8 @@ class InvitationSelectionController extends State { String id, String displayname, ) async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); final room = Matrix.of(context).client.getRoomById(roomId!)!; final success = await showFutureLoadingDialog( @@ -61,10 +63,9 @@ class InvitationSelectionController extends State { future: () => room.invite(id), ); if (success.error == null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(L10n.of(context).contactHasBeenInvitedToTheGroup), - ), + if (!context.mounted) return; + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.contactHasBeenInvitedToTheGroup)), ); } } @@ -91,6 +92,7 @@ class InvitationSelectionController extends State { try { response = await matrix.client.searchUserDirectory(text, limit: 10); } catch (e) { + if (!context.mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text((e).toLocalizedString(context)))); diff --git a/lib/pages/key_verification/key_verification_dialog.dart b/lib/pages/key_verification/key_verification_dialog.dart index d1329992..52c8868a 100644 --- a/lib/pages/key_verification/key_verification_dialog.dart +++ b/lib/pages/key_verification/key_verification_dialog.dart @@ -82,6 +82,7 @@ class KeyVerificationPageState extends State { }, ); if (valid.error != null) { + if (!mounted) return; await showOkAlertDialog( useRootNavigator: false, context: context, @@ -178,9 +179,10 @@ class KeyVerificationPageState extends State { ); buttons.add( AdaptiveDialogAction( - onPressed: () => widget.request.rejectVerification().then( - (_) => Navigator.of(context, rootNavigator: false).pop(false), - ), + onPressed: () => widget.request.rejectVerification().then((_) { + if (!context.mounted) return; + Navigator.of(context, rootNavigator: false).pop(false); + }), child: Text( L10n.of(context).reject, style: TextStyle(color: theme.colorScheme.error), diff --git a/lib/pages/login/login.dart b/lib/pages/login/login.dart index 8fb9ec73..4ebd3278 100644 --- a/lib/pages/login/login.dart +++ b/lib/pages/login/login.dart @@ -130,15 +130,19 @@ class LoginController extends State { Logs().v( '$newDomain is not running a homeserver, asking to use $oldHomeserver', ); + if (!mounted) return; + final l10n = L10n.of(context); final dialogResult = await showOkCancelAlertDialog( context: context, useRootNavigator: false, - title: L10n.of( - context, - ).noMatrixServer(newDomain.toString(), oldHomeserver.toString()), - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, + title: l10n.noMatrixServer( + newDomain.toString(), + oldHomeserver.toString(), + ), + okLabel: l10n.ok, + cancelLabel: l10n.cancel, ); + if (!mounted) return; if (dialogResult == OkCancelResult.ok) { if (mounted) setState(() => usernameError = null); } else { @@ -156,26 +160,30 @@ class LoginController extends State { } } catch (e) { widget.client.homeserver = oldHomeserver; + if (!mounted) return; usernameError = e.toLocalizedString(context); if (mounted) setState(() {}); } } Future passwordForgotten() async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); final input = await showTextInputDialog( useRootNavigator: false, context: context, - title: L10n.of(context).passwordForgotten, - message: L10n.of(context).enterAnEmailAddress, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, + title: l10n.passwordForgotten, + message: l10n.enterAnEmailAddress, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, initialText: usernameController.text.isEmail ? usernameController.text : '', - hintText: L10n.of(context).enterAnEmailAddress, + hintText: l10n.enterAnEmailAddress, keyboardType: TextInputType.emailAddress, ); if (input == null) return; + if (!mounted) return; final clientSecret = DateTime.now().millisecondsSinceEpoch.toString(); final response = await showFutureLoadingDialog( context: context, @@ -186,27 +194,30 @@ class LoginController extends State { ), ); if (response.error != null) return; + if (!mounted) return; final password = await showTextInputDialog( useRootNavigator: false, context: context, - title: L10n.of(context).passwordForgotten, - message: L10n.of(context).chooseAStrongPassword, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, + title: l10n.passwordForgotten, + message: l10n.chooseAStrongPassword, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, hintText: '******', obscureText: true, minLines: 1, maxLines: 1, ); if (password == null) return; + if (!mounted) return; final ok = await showOkAlertDialog( useRootNavigator: false, context: context, - title: L10n.of(context).weSentYouAnEmail, - message: L10n.of(context).pleaseClickOnLink, - okLabel: L10n.of(context).iHaveClickedOnLink, + title: l10n.weSentYouAnEmail, + message: l10n.pleaseClickOnLink, + okLabel: l10n.iHaveClickedOnLink, ); if (ok != OkCancelResult.ok) return; + if (!mounted) return; final data = { 'new_password': password, 'logout_devices': false, @@ -226,9 +237,10 @@ class LoginController extends State { data: data, ), ); + if (!mounted) return; if (success.error == null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(L10n.of(context).passwordHasBeenChanged)), + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.passwordHasBeenChanged)), ); usernameController.text = input; passwordController.text = password; diff --git a/lib/pages/new_private_chat/new_private_chat.dart b/lib/pages/new_private_chat/new_private_chat.dart index faf30114..6135b473 100644 --- a/lib/pages/new_private_chat/new_private_chat.dart +++ b/lib/pages/new_private_chat/new_private_chat.dart @@ -81,17 +81,19 @@ class NewPrivateChatController extends State { void inviteAction() => FluffyShare.shareInviteLink(context); Future openScannerAction() async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); if (PlatformInfos.isAndroid) { final info = await DeviceInfoPlugin().androidInfo; + if (!mounted) return; if (info.version.sdkInt < 21) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(L10n.of(context).unsupportedAndroidVersionLong), - ), + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.unsupportedAndroidVersionLong)), ); return; } } + if (!mounted) return; await showAdaptiveBottomSheet( context: context, builder: (_) => QrScannerModal( @@ -101,12 +103,15 @@ class NewPrivateChatController extends State { } Future copyUserId() async { + final scaffoldMessenger = ScaffoldMessenger.of(context); + final l10n = L10n.of(context); await Clipboard.setData( ClipboardData(text: Matrix.of(context).client.userID!), ); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(L10n.of(context).copiedToClipboard))); + if (!mounted) return; + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.copiedToClipboard)), + ); } void openUserModal(Profile profile) => diff --git a/lib/pages/new_private_chat/qr_scanner_modal.dart b/lib/pages/new_private_chat/qr_scanner_modal.dart index 53a822b0..1983c1a6 100644 --- a/lib/pages/new_private_chat/qr_scanner_modal.dart +++ b/lib/pages/new_private_chat/qr_scanner_modal.dart @@ -66,6 +66,7 @@ class QrScannerModalState extends State { late StreamSubscription sub; sub = controller.scannedDataStream.listen((scanData) { sub.cancel(); + if (!mounted) return; Navigator.of(context).pop(); final data = scanData.code; if (data != null) widget.onScan(data); diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index 2b988d08..e8b21a54 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -35,18 +35,20 @@ class SettingsController extends State { }); Future setDisplaynameAction() async { + final l10n = L10n.of(context); + final matrix = Matrix.of(context); final profile = await profileFuture; + if (!mounted) return; final input = await showTextInputDialog( useRootNavigator: false, context: context, - title: L10n.of(context).editDisplayname, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - initialText: - profile?.displayName ?? Matrix.of(context).client.userID!.localpart, + title: l10n.editDisplayname, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, + initialText: profile?.displayName ?? matrix.client.userID!.localpart, ); if (input == null) return; - final matrix = Matrix.of(context); + if (!mounted) return; final success = await showFutureLoadingDialog( context: context, future: () => matrix.client.setProfileField( @@ -61,19 +63,21 @@ class SettingsController extends State { } Future logoutAction() async { + final l10n = L10n.of(context); + final matrix = Matrix.of(context); if (await showOkCancelAlertDialog( useRootNavigator: false, context: context, - title: L10n.of(context).areYouSureYouWantToLogout, - message: L10n.of(context).noBackupWarning, + title: l10n.areYouSureYouWantToLogout, + message: l10n.noBackupWarning, isDestructive: cryptoIdentityConnected == false, - okLabel: L10n.of(context).logout, - cancelLabel: L10n.of(context).cancel, + okLabel: l10n.logout, + cancelLabel: l10n.cancel, ) == OkCancelResult.cancel) { return; } - final matrix = Matrix.of(context); + if (!mounted) return; await showFutureLoadingDialog( context: context, future: () => matrix.client.logout(), @@ -81,24 +85,27 @@ class SettingsController extends State { } Future setAvatarAction() async { + final l10n = L10n.of(context); + final matrix = Matrix.of(context); final profile = await profileFuture; + if (!mounted) return; final actions = [ if (PlatformInfos.isMobile) AdaptiveModalAction( value: AvatarAction.camera, - label: L10n.of(context).openCamera, + label: l10n.openCamera, isDefaultAction: true, icon: const Icon(Icons.camera_alt_outlined), ), AdaptiveModalAction( value: AvatarAction.file, - label: L10n.of(context).openGallery, + label: l10n.openGallery, icon: const Icon(Icons.photo_outlined), ), if (profile?.avatarUrl != null) AdaptiveModalAction( value: AvatarAction.remove, - label: L10n.of(context).removeYourAvatar, + label: l10n.removeYourAvatar, isDestructive: true, icon: const Icon(Icons.delete_outlined), ), @@ -107,12 +114,12 @@ class SettingsController extends State { ? actions.single.value : await showModalActionPopup( context: context, - title: L10n.of(context).changeYourAvatar, - cancelLabel: L10n.of(context).cancel, + title: l10n.changeYourAvatar, + cancelLabel: l10n.cancel, actions: actions, ); if (action == null) return; - final matrix = Matrix.of(context); + if (!mounted) return; if (action == AvatarAction.remove) { final success = await showFutureLoadingDialog( context: context, @@ -134,6 +141,7 @@ class SettingsController extends State { if (result == null) return; file = MatrixFile(bytes: await result.readAsBytes(), name: result.path); } else { + if (!mounted) return; final result = await selectFiles(context, type: FileType.image); final pickedFile = result.firstOrNull; if (pickedFile == null) return; @@ -142,6 +150,7 @@ class SettingsController extends State { name: pickedFile.name, ); } + if (!mounted) return; final success = await showFutureLoadingDialog( context: context, future: () => matrix.client.setAvatar(file), @@ -168,6 +177,7 @@ class SettingsController extends State { } final state = await client.getCryptoIdentityState(); + if (!mounted) return; setState(() { cryptoIdentityConnected = state.initialized && state.connected; }); diff --git a/lib/pages/settings_3pid/settings_3pid.dart b/lib/pages/settings_3pid/settings_3pid.dart index 18f89be7..25f738f8 100644 --- a/lib/pages/settings_3pid/settings_3pid.dart +++ b/lib/pages/settings_3pid/settings_3pid.dart @@ -19,41 +19,48 @@ class Settings3Pid extends StatefulWidget { class Settings3PidController extends State { Future add3PidAction() async { + final l10n = L10n.of(context); + final matrix = Matrix.of(context); final input = await showTextInputDialog( useRootNavigator: false, context: context, - title: L10n.of(context).enterAnEmailAddress, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - hintText: L10n.of(context).enterAnEmailAddress, + title: l10n.enterAnEmailAddress, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, + hintText: l10n.enterAnEmailAddress, keyboardType: TextInputType.emailAddress, ); if (input == null) return; + if (!mounted) return; final clientSecret = DateTime.now().millisecondsSinceEpoch.toString(); final response = await showFutureLoadingDialog( context: context, - future: () => Matrix.of(context).client.requestTokenToRegisterEmail( + future: () => matrix.client.requestTokenToRegisterEmail( clientSecret, input, Settings3Pid.sendAttempt++, ), ); if (response.error != null) return; + if (!mounted) return; final ok = await showOkAlertDialog( useRootNavigator: false, context: context, - title: L10n.of(context).weSentYouAnEmail, - message: L10n.of(context).pleaseClickOnLink, - okLabel: L10n.of(context).iHaveClickedOnLink, + title: l10n.weSentYouAnEmail, + message: l10n.pleaseClickOnLink, + okLabel: l10n.iHaveClickedOnLink, ); if (ok != OkCancelResult.ok) return; + if (!mounted) return; final success = await showFutureLoadingDialog( context: context, delay: false, - future: () => Matrix.of(context).client.uiaRequestBackground( - (auth) => Matrix.of( - context, - ).client.add3PID(clientSecret, response.result!.sid, auth: auth), + future: () => matrix.client.uiaRequestBackground( + (auth) => matrix.client.add3PID( + clientSecret, + response.result!.sid, + auth: auth, + ), ), ); if (success.error != null) return; @@ -63,21 +70,25 @@ class Settings3PidController extends State { Future?>? request; Future delete3Pid(ThirdPartyIdentifier identifier) async { + final l10n = L10n.of(context); + final matrix = Matrix.of(context); if (await showOkCancelAlertDialog( useRootNavigator: false, context: context, - title: L10n.of(context).areYouSure, - okLabel: L10n.of(context).yes, - cancelLabel: L10n.of(context).cancel, + title: l10n.areYouSure, + okLabel: l10n.yes, + cancelLabel: l10n.cancel, ) != OkCancelResult.ok) { return; } + if (!mounted) return; final success = await showFutureLoadingDialog( context: context, - future: () => Matrix.of( - context, - ).client.delete3pidFromAccount(identifier.address, identifier.medium), + future: () => matrix.client.delete3pidFromAccount( + identifier.address, + identifier.medium, + ), ); if (success.error != null) return; setState(() => request = null); diff --git a/lib/pages/settings_emotes/import_archive_dialog.dart b/lib/pages/settings_emotes/import_archive_dialog.dart index 20b1ffde..80b9dcf0 100644 --- a/lib/pages/settings_emotes/import_archive_dialog.dart +++ b/lib/pages/settings_emotes/import_archive_dialog.dart @@ -91,6 +91,7 @@ class _ImportEmoteArchiveDialogState extends State { } Future _addEmotePack() async { + final matrix = Matrix.of(context); setState(() { _loading = true; _progress = 0; @@ -148,7 +149,7 @@ class _ImportEmoteArchiveDialogState extends State { } else { mxcFile = thumbnail; } - final uri = await Matrix.of(context).client.uploadContent( + final uri = await matrix.client.uploadContent( mxcFile.bytes, filename: mxcFile.name, contentType: mxcFile.mimeType, @@ -178,6 +179,7 @@ class _ImportEmoteArchiveDialogState extends State { } } + if (!mounted) return; await widget.controller.save(context); _importMap.removeWhere( (key, value) => successfulUploads.contains(key.name), diff --git a/lib/pages/settings_emotes/settings_emotes.dart b/lib/pages/settings_emotes/settings_emotes.dart index 9825daa9..13cd4819 100644 --- a/lib/pages/settings_emotes/settings_emotes.dart +++ b/lib/pages/settings_emotes/settings_emotes.dart @@ -293,6 +293,7 @@ class EmotesSettingsController extends State { } Future createStickers() async { + final matrix = Matrix.of(context); final pickedFiles = await selectFiles( context, type: FileType.image, @@ -315,7 +316,7 @@ class EmotesSettingsController extends State { nativeImplementations: ClientManager.nativeImplementations, ) ?? file; - final uri = await Matrix.of(context).client.uploadContent( + final uri = await matrix.client.uploadContent( file.bytes, filename: file.name, contentType: file.mimeType, @@ -361,6 +362,7 @@ class EmotesSettingsController extends State { final buffer = InputMemoryStream(await result.single.readAsBytes()); final archive = ZipDecoder().decodeStream(buffer); + if (!mounted) return; await showDialog( context: context, @@ -375,7 +377,7 @@ class EmotesSettingsController extends State { Future exportAsZip() async { final client = Matrix.of(context).client; - await showFutureLoadingDialog( + final result = await showFutureLoadingDialog( context: context, future: () async { final pack = _getPack(); @@ -397,11 +399,12 @@ class EmotesSettingsController extends State { '${pack.pack.displayName ?? client.userID?.localpart ?? 'emotes'}.zip'; final output = ZipEncoder().encode(archive); - MatrixFile( - name: fileName, - bytes: Uint8List.fromList(output), - ).save(context); + return MatrixFile(name: fileName, bytes: Uint8List.fromList(output)); }, ); + final file = result.result; + if (file == null) return; + if (!mounted) return; + file.save(context); } } diff --git a/lib/pages/settings_notifications/settings_notifications.dart b/lib/pages/settings_notifications/settings_notifications.dart index 1f61c33b..8874d1d3 100644 --- a/lib/pages/settings_notifications/settings_notifications.dart +++ b/lib/pages/settings_notifications/settings_notifications.dart @@ -40,6 +40,7 @@ class SettingsNotificationsController extends State { ], ); if (delete != true) return; + if (!mounted) return; final success = await showFutureLoadingDialog( context: context, diff --git a/lib/pages/settings_password/settings_password.dart b/lib/pages/settings_password/settings_password.dart index acceda75..e248e7c0 100644 --- a/lib/pages/settings_password/settings_password.dart +++ b/lib/pages/settings_password/settings_password.dart @@ -24,6 +24,8 @@ class SettingsPasswordController extends State { bool loading = false; Future changePassword() async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); setState(() { oldPasswordError = newPassword1Error = newPassword2Error = null; }); @@ -51,13 +53,13 @@ class SettingsPasswordController extends State { loading = true; }); try { - final scaffoldMessenger = ScaffoldMessenger.of(context); await Matrix.of(context).client.changePassword( newPassword1Controller.text, oldPassword: oldPasswordController.text, ); + if (!mounted) return; scaffoldMessenger.showSnackBar( - SnackBar(content: Text(L10n.of(context).passwordHasBeenChanged)), + SnackBar(content: Text(l10n.passwordHasBeenChanged)), ); if (mounted) context.pop(); } catch (e) { diff --git a/lib/pages/settings_security/settings_security.dart b/lib/pages/settings_security/settings_security.dart index 57ea44f3..a0c5754a 100644 --- a/lib/pages/settings_security/settings_security.dart +++ b/lib/pages/settings_security/settings_security.dart @@ -19,20 +19,21 @@ class SettingsSecurity extends StatefulWidget { class SettingsSecurityController extends State { Future setAppLockAction() async { + final l10n = L10n.of(context); if (AppLock.of(context).isActive) { 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, + title: l10n.pleaseChooseAPasscode, + message: l10n.pleaseEnter4Digits, + cancelLabel: l10n.cancel, validator: (text) { if (text.isEmpty || (text.length == 4 && int.tryParse(text)! >= 0)) { return null; } - return L10n.of(context).pleaseEnter4Digits; + return l10n.pleaseEnter4Digits; }, keyboardType: TextInputType.number, obscureText: true, @@ -41,53 +42,55 @@ class SettingsSecurityController extends State { maxLength: 4, ); if (newLock != null) { + if (!mounted) return; await AppLock.of(context).changePincode(newLock); } } Future deleteAccountAction() async { + final l10n = L10n.of(context); + final matrix = Matrix.of(context); 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, + title: l10n.warning, + message: l10n.deactivateAccountWarning, + okLabel: l10n.ok, + cancelLabel: l10n.cancel, isDestructive: true, ) == OkCancelResult.cancel) { return; } - final supposedMxid = Matrix.of(context).client.userID!; + if (!mounted) return; + final supposedMxid = matrix.client.userID!; final mxid = await showTextInputDialog( useRootNavigator: false, context: context, - title: L10n.of(context).confirmMatrixId, - validator: (text) => text == supposedMxid - ? null - : L10n.of(context).supposedMxid(supposedMxid), + title: l10n.confirmMatrixId, + validator: (text) => + text == supposedMxid ? null : l10n.supposedMxid(supposedMxid), isDestructive: true, - okLabel: L10n.of(context).delete, - cancelLabel: L10n.of(context).cancel, + okLabel: l10n.delete, + cancelLabel: l10n.cancel, ); if (mxid == null || mxid.isEmpty || mxid != supposedMxid) { return; } + if (!mounted) return; final resp = await showFutureLoadingDialog( context: context, delay: false, - future: () => - Matrix.of(context).client.uiaRequestBackground( - (auth) => Matrix.of( - context, - ).client.deactivateAccount(auth: auth, erase: true), - ), + future: () => matrix.client.uiaRequestBackground( + (auth) => matrix.client.deactivateAccount(auth: auth, erase: true), + ), ); if (!resp.isError) { + if (!mounted) return; await showFutureLoadingDialog( context: context, - future: () => Matrix.of(context).client.logout(), + future: () => matrix.client.logout(), ); } } diff --git a/lib/pages/settings_style/settings_style.dart b/lib/pages/settings_style/settings_style.dart index 0feff1c4..7e8e45f4 100644 --- a/lib/pages/settings_style/settings_style.dart +++ b/lib/pages/settings_style/settings_style.dart @@ -29,6 +29,7 @@ class SettingsStyleController extends State { final picked = await selectFiles(context, type: FileType.image); final pickedFile = picked.firstOrNull; if (pickedFile == null) return; + if (!mounted) return; await showFutureLoadingDialog( context: context, diff --git a/lib/utils/fluffy_share.dart b/lib/utils/fluffy_share.dart index 45699616..7a52d286 100644 --- a/lib/utils/fluffy_share.dart +++ b/lib/utils/fluffy_share.dart @@ -12,6 +12,8 @@ abstract class FluffyShare { BuildContext context, { bool copyOnly = false, }) async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); if (PlatformInfos.isMobile && !copyOnly) { final box = context.findRenderObject() as RenderBox; await SharePlus.instance.share( @@ -24,21 +26,20 @@ abstract class FluffyShare { } await Clipboard.setData(ClipboardData(text: text)); if (!PlatformInfos.isMobile) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - showCloseIcon: true, - content: Text(L10n.of(context).copiedToClipboard), - ), + scaffoldMessenger.showSnackBar( + SnackBar(showCloseIcon: true, content: Text(l10n.copiedToClipboard)), ); } return; } static Future shareInviteLink(BuildContext context) async { + final l10n = L10n.of(context); final client = Matrix.of(context).client; final ownProfile = await client.fetchOwnProfile(); + if (!context.mounted) return; await FluffyShare.share( - L10n.of(context).inviteText( + l10n.inviteText( ownProfile.displayName ?? client.userID!, 'https://matrix.to/#/${client.userID}?client=im.fluffychat', ), diff --git a/lib/utils/matrix_sdk_extensions/event_extension.dart b/lib/utils/matrix_sdk_extensions/event_extension.dart index 84b9f2d8..641898ac 100644 --- a/lib/utils/matrix_sdk_extensions/event_extension.dart +++ b/lib/utils/matrix_sdk_extensions/event_extension.dart @@ -25,12 +25,14 @@ extension LocalizedBody on Event { Future saveFile(BuildContext context) async { final matrixFile = await _getFile(context); + if (!context.mounted) return; matrixFile.result?.save(context); } Future shareFile(BuildContext context) async { final matrixFile = await _getFile(context); + if (!context.mounted) return; matrixFile.result?.share(context); } diff --git a/lib/utils/platform_infos.dart b/lib/utils/platform_infos.dart index f5addae6..4d4a0dfb 100644 --- a/lib/utils/platform_infos.dart +++ b/lib/utils/platform_infos.dart @@ -50,15 +50,17 @@ abstract class PlatformInfos { } static Future showDialog(BuildContext context) async { + final l10n = L10n.of(context); final version = await PlatformInfos.getVersion(); + if (!context.mounted) return; showAboutDialog( context: context, children: [ - Text(L10n.of(context).versionWithNumber(version)), + Text(l10n.versionWithNumber(version)), TextButton.icon( onPressed: () => launchUrlString(AppConfig.sourceCodeUrl), icon: const Icon(Icons.source_outlined), - label: Text(L10n.of(context).sourceCode), + label: Text(l10n.sourceCode), ), Builder( builder: (innerContext) { @@ -68,7 +70,7 @@ abstract class PlatformInfos { Navigator.of(innerContext).pop(); }, icon: const Icon(Icons.list_outlined), - label: Text(L10n.of(context).logs), + label: Text(l10n.logs), ); }, ), @@ -80,7 +82,7 @@ abstract class PlatformInfos { Navigator.of(innerContext).pop(); }, icon: const Icon(Icons.settings_applications_outlined), - label: Text(L10n.of(context).advancedConfigs), + label: Text(l10n.advancedConfigs), ); }, ), diff --git a/lib/utils/show_update_snackbar.dart b/lib/utils/show_update_snackbar.dart index 8735a941..c0366046 100644 --- a/lib/utils/show_update_snackbar.dart +++ b/lib/utils/show_update_snackbar.dart @@ -10,6 +10,7 @@ abstract class UpdateNotifier { static Future showUpdateSnackBar(BuildContext context) async { final scaffoldMessenger = ScaffoldMessenger.of(context); + final l10n = L10n.of(context); final currentVersion = await PlatformInfos.getVersion(); final store = await SharedPreferences.getInstance(); final storedVersion = store.getString(versionStoreKey); @@ -20,9 +21,9 @@ abstract class UpdateNotifier { SnackBar( duration: const Duration(seconds: 30), showCloseIcon: true, - content: Text(L10n.of(context).updateInstalled(currentVersion)), + content: Text(l10n.updateInstalled(currentVersion)), action: SnackBarAction( - label: L10n.of(context).changelog, + label: l10n.changelog, onPressed: () => launchUrlString(AppConfig.changelogUrl), ), ), diff --git a/lib/utils/sign_in_flows/check_homeserver.dart b/lib/utils/sign_in_flows/check_homeserver.dart index 213ad909..ca77b5ca 100644 --- a/lib/utils/sign_in_flows/check_homeserver.dart +++ b/lib/utils/sign_in_flows/check_homeserver.dart @@ -38,6 +38,7 @@ Future connectToHomeserverFlow( if ((kIsWeb || PlatformInfos.isLinux) && (supportsSso || authMetadata != null || (signUp && regLink != null))) { + if (!context.mounted) return; final consent = await showOkCancelAlertDialog( context: context, title: l10n.appWantsToUseForLogin(homeserverInput), @@ -45,7 +46,9 @@ Future connectToHomeserverFlow( okLabel: l10n.continueText, ); if (consent != OkCancelResult.ok) return; + if (!context.mounted) return; } + if (!context.mounted) return; if (authMetadata != null && AppSettings.enableMatrixNativeOIDC.value) { await oidcLoginFlow(client, context, signUp); @@ -55,6 +58,7 @@ Future connectToHomeserverFlow( if (signUp && regLink != null) { await launchUrlString(regLink); } + if (!context.mounted) return; final pathSegments = List.of( GoRouter.of(context).routeInformationProvider.value.uri.pathSegments, ); diff --git a/lib/utils/url_launcher.dart b/lib/utils/url_launcher.dart index fcc64113..34649cd1 100644 --- a/lib/utils/url_launcher.dart +++ b/lib/utils/url_launcher.dart @@ -27,6 +27,8 @@ class UrlLauncher { const UrlLauncher(this.context, this.url, [this.name]); Future launchUrl() async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); if (url!.toLowerCase().startsWith(AppConfig.deepLinkPrefix) || url!.toLowerCase().startsWith(AppConfig.inviteLinkPrefix) || {'#', '@', '!', '+', '\$'}.contains(url![0]) || @@ -36,8 +38,8 @@ class UrlLauncher { final uri = Uri.tryParse(url!); if (uri == null) { // we can't open this thing - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(L10n.of(context).cantOpenUri(url!))), + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.cantOpenUri(url!))), ); return; } @@ -47,10 +49,10 @@ class UrlLauncher { // that the user can see the actual url before opening the browser. final consent = await showOkCancelAlertDialog( context: context, - title: L10n.of(context).openLinkInBrowser, + title: l10n.openLinkInBrowser, message: url, - okLabel: L10n.of(context).open, - cancelLabel: L10n.of(context).cancel, + okLabel: l10n.open, + cancelLabel: l10n.cancel, ); if (consent != OkCancelResult.ok) return; } @@ -90,8 +92,8 @@ class UrlLauncher { return; } if (uri.host.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(L10n.of(context).cantOpenUri(url!))), + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.cantOpenUri(url!))), ); return; } @@ -161,6 +163,7 @@ class UrlLauncher { } } servers.addAll(identityParts.via); + if (!context.mounted) return; if (room != null) { if (room.isSpace) { // TODO: Implement navigate to space @@ -178,6 +181,7 @@ class UrlLauncher { } return; } else { + if (!context.mounted) return; await showAdaptiveDialog( context: context, builder: (c) => @@ -185,6 +189,7 @@ class UrlLauncher { ); } if (roomIdOrAlias.sigil == '!') { + if (!context.mounted) return; if (await showOkCancelAlertDialog( useRootNavigator: false, context: context, @@ -192,6 +197,7 @@ class UrlLauncher { ) == OkCancelResult.ok) { roomId = roomIdOrAlias; + if (!context.mounted) return; final response = await showFutureLoadingDialog( context: context, future: () => matrix.client.joinRoom( @@ -200,11 +206,13 @@ class UrlLauncher { ), ); if (response.error != null) return; + if (!context.mounted) return; // wait for two seconds so that it probably came down /sync await showFutureLoadingDialog( context: context, future: () => Future.delayed(const Duration(seconds: 2)), ); + if (!context.mounted) return; if (event != null) { context.go( Uri( @@ -228,6 +236,7 @@ class UrlLauncher { return Profile(userId: userId); }), ); + if (!context.mounted) return; await UserDialog.show( context: context, profile: profileResult.result!, diff --git a/lib/widgets/adaptive_dialogs/public_room_dialog.dart b/lib/widgets/adaptive_dialogs/public_room_dialog.dart index adb0865d..6149be12 100644 --- a/lib/widgets/adaptive_dialogs/public_room_dialog.dart +++ b/lib/widgets/adaptive_dialogs/public_room_dialog.dart @@ -25,6 +25,7 @@ class PublicRoomDialog extends StatelessWidget { const PublicRoomDialog({super.key, this.roomAlias, this.chunk, this.via}); Future _joinRoom(BuildContext context) async { + final l10n = L10n.of(context); final client = Matrix.of(context).client; final chunk = this.chunk; final knock = chunk?.joinRule == 'knock'; @@ -48,12 +49,13 @@ class PublicRoomDialog extends StatelessWidget { ); final roomId = result.result; if (roomId == null) return; + if (!context.mounted) return; if (knock && client.getRoomById(roomId) == null) { Navigator.of(context).pop(true); await showOkAlertDialog( context: context, - title: L10n.of(context).youHaveKnocked, - message: L10n.of(context).pleaseWaitUntilInvited, + title: l10n.youHaveKnocked, + message: l10n.pleaseWaitUntilInvited, ); return; } @@ -73,6 +75,7 @@ class PublicRoomDialog extends StatelessWidget { bool _testRoom(PublishedRoomsChunk r) => r.canonicalAlias == roomAlias; Future _search(BuildContext context) async { + final l10n = L10n.of(context); final chunk = this.chunk; if (chunk != null) return chunk; final query = await Matrix.of(context).client.queryPublicRooms( @@ -80,7 +83,7 @@ class PublicRoomDialog extends StatelessWidget { filter: PublicRoomQueryFilter(genericSearchTerm: roomAlias), ); if (!query.chunk.any(_testRoom)) { - throw (L10n.of(context).noRoomsFound); + throw (l10n.noRoomsFound); } return query.chunk.firstWhere(_testRoom); } @@ -248,6 +251,7 @@ class PublicRoomDialog extends StatelessWidget { hintText: L10n.of(context).reason, ); if (reason == null || reason.isEmpty) return; + if (!context.mounted) return; await showFutureLoadingDialog( context: context, future: () => Matrix.of(context).client.reportRoom( diff --git a/lib/widgets/adaptive_dialogs/user_dialog.dart b/lib/widgets/adaptive_dialogs/user_dialog.dart index 764a6ed8..4e30a5b3 100644 --- a/lib/widgets/adaptive_dialogs/user_dialog.dart +++ b/lib/widgets/adaptive_dialogs/user_dialog.dart @@ -220,6 +220,7 @@ class UserDialog extends StatelessWidget { hintText: L10n.of(context).reason, ); if (reason == null || reason.isEmpty) return; + if (!context.mounted) return; await showFutureLoadingDialog( context: context, future: () => Matrix.of( diff --git a/lib/widgets/chat_settings_popup_menu.dart b/lib/widgets/chat_settings_popup_menu.dart index 8ba7952c..a33cc911 100644 --- a/lib/widgets/chat_settings_popup_menu.dart +++ b/lib/widgets/chat_settings_popup_menu.dart @@ -53,16 +53,18 @@ class ChatSettingsPopupMenuState extends State { onSelected: (choice) async { switch (choice) { case ChatPopupMenuActions.leave: + final l10n = L10n.of(context); final router = GoRouter.of(context); final confirmed = await showOkCancelAlertDialog( context: context, - title: L10n.of(context).areYouSure, - message: L10n.of(context).archiveRoomDescription, - okLabel: L10n.of(context).leave, - cancelLabel: L10n.of(context).cancel, + title: l10n.areYouSure, + message: l10n.archiveRoomDescription, + okLabel: l10n.leave, + cancelLabel: l10n.cancel, isDestructive: true, ); if (confirmed != OkCancelResult.ok) return; + if (!context.mounted) return; final result = await showFutureLoadingDialog( context: context, future: () => widget.room.leave(), diff --git a/lib/widgets/future_loading_dialog.dart b/lib/widgets/future_loading_dialog.dart index 4cf2ee6d..9afb7b8f 100644 --- a/lib/widgets/future_loading_dialog.dart +++ b/lib/widgets/future_loading_dialog.dart @@ -5,6 +5,7 @@ import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart'; import 'package:flutter/material.dart'; +import 'package:matrix/matrix_api_lite/utils/logs.dart'; /// Displays a loading dialog which reacts to the given [future]. The dialog /// will be dismissed and the value will be returned when the future completes. @@ -40,6 +41,15 @@ Future> showFutureLoadingDialog({ } } + if (!context.mounted) { + Logs().e( + 'Unable to show loading dialog!', + Exception('The BuildContext is not mounted!'), + StackTrace.current, + ); + return Result.capture(futureExec); + } + final result = await showAdaptiveDialog>( context: context, barrierDismissible: barrierDismissible, diff --git a/lib/widgets/local_notifications_extension.dart b/lib/widgets/local_notifications_extension.dart index 47597286..cf97ad50 100644 --- a/lib/widgets/local_notifications_extension.dart +++ b/lib/widgets/local_notifications_extension.dart @@ -17,6 +17,7 @@ import 'package:universal_html/html.dart' as html; extension LocalNotificationsExtension on MatrixState { Future showLocalNotification(Event event) async { + final l10n = L10n.of(context); final roomId = event.room.id; if (activeRoomId == roomId) { if (WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) { @@ -114,11 +115,11 @@ extension LocalNotificationsExtension on MatrixState { actions: [ NotificationAction( DesktopNotificationActions.openChat.name, - L10n.of(context).openChat, + l10n.openChat, ), NotificationAction( DesktopNotificationActions.seen.name, - L10n.of(context).markAsRead, + l10n.markAsRead, ), ], hints: hints, diff --git a/lib/widgets/matrix.dart b/lib/widgets/matrix.dart index 2f7a3291..614bdb87 100644 --- a/lib/widgets/matrix.dart +++ b/lib/widgets/matrix.dart @@ -265,12 +265,19 @@ class MatrixState extends State with WidgetsBindingObserver { InitWithRestoreExtension.deleteSessionBackup(name); if (loggedInWithMultipleClients) { + final snackbarContext = + FluffyChatApp + .router + .routerDelegate + .navigatorKey + .currentContext ?? + context; + + if (!snackbarContext.mounted) return; + final l10n = L10n.of(snackbarContext); ScaffoldMessenger.of( - FluffyChatApp.router.routerDelegate.navigatorKey.currentContext ?? - context, - ).showSnackBar( - SnackBar(content: Text(L10n.of(context).oneClientLoggedOut)), - ); + snackbarContext, + ).showSnackBar(SnackBar(content: Text(l10n.oneClientLoggedOut))); return; } FluffyChatApp.router.go('/'); @@ -382,15 +389,17 @@ class MatrixState extends State with WidgetsBindingObserver { } Future dehydrateAction(BuildContext context) async { + final l10n = L10n.of(context); final response = await showOkCancelAlertDialog( context: context, isDestructive: true, - title: L10n.of(context).dehydrate, - message: L10n.of(context).dehydrateWarning, + title: l10n.dehydrate, + message: l10n.dehydrateWarning, ); if (response != OkCancelResult.ok) { return; } + if (!context.mounted) return; final result = await showFutureLoadingDialog( context: context, future: client.exportDump, @@ -404,6 +413,7 @@ class MatrixState extends State with WidgetsBindingObserver { 'fluffychat-export-${DateFormat(DateFormat.YEAR_MONTH_DAY).format(DateTime.now())}.fluffybackup'; final file = MatrixFile(bytes: exportBytes, name: exportFileName); + if (!context.mounted) return; file.save(context); } } diff --git a/lib/widgets/member_actions_popup_menu_button.dart b/lib/widgets/member_actions_popup_menu_button.dart index 46ec8e96..1ae71387 100644 --- a/lib/widgets/member_actions_popup_menu_button.dart +++ b/lib/widgets/member_actions_popup_menu_button.dart @@ -14,6 +14,8 @@ Future showMemberActionsPopupMenu({ required User user, void Function()? onMention, }) async { + final l10n = L10n.of(context); + final scaffoldMessenger = ScaffoldMessenger.of(context); final theme = Theme.of(context); final displayname = user.calcDisplayname(); final isMe = user.room.client.userID == user.id; @@ -245,12 +247,13 @@ Future showMemberActionsPopupMenu({ case _MemberActions.kick: if (await showOkCancelAlertDialog( context: context, - title: L10n.of(context).areYouSure, - okLabel: L10n.of(context).yes, - cancelLabel: L10n.of(context).no, - message: L10n.of(context).kickUserDescription, + title: l10n.areYouSure, + okLabel: l10n.yes, + cancelLabel: l10n.no, + message: l10n.kickUserDescription, ) == OkCancelResult.ok) { + if (!context.mounted) return; await showFutureLoadingDialog( context: context, future: () => user.kick(), @@ -260,12 +263,13 @@ Future showMemberActionsPopupMenu({ case _MemberActions.ban: if (await showOkCancelAlertDialog( context: context, - title: L10n.of(context).areYouSure, - okLabel: L10n.of(context).yes, - cancelLabel: L10n.of(context).no, - message: L10n.of(context).banUserDescription, + title: l10n.areYouSure, + okLabel: l10n.yes, + cancelLabel: l10n.no, + message: l10n.banUserDescription, ) == OkCancelResult.ok) { + if (!context.mounted) return; await showFutureLoadingDialog( context: context, future: () => user.ban(), @@ -275,20 +279,22 @@ Future showMemberActionsPopupMenu({ case _MemberActions.report: final reason = await showTextInputDialog( context: context, - title: L10n.of(context).whyDoYouWantToReportThis, - okLabel: L10n.of(context).report, - cancelLabel: L10n.of(context).cancel, - hintText: L10n.of(context).reason, + title: l10n.whyDoYouWantToReportThis, + okLabel: l10n.report, + cancelLabel: l10n.cancel, + hintText: l10n.reason, ); if (reason == null || reason.isEmpty) return; + if (!context.mounted) return; final result = await showFutureLoadingDialog( context: context, future: () => user.room.client.reportUser(user.id, reason), ); if (result.error != null) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(L10n.of(context).contentHasBeenReported)), + if (!context.mounted) return; + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(l10n.contentHasBeenReported)), ); return; case _MemberActions.info: @@ -304,12 +310,13 @@ Future showMemberActionsPopupMenu({ case _MemberActions.unban: if (await showOkCancelAlertDialog( context: context, - title: L10n.of(context).areYouSure, - okLabel: L10n.of(context).yes, - cancelLabel: L10n.of(context).no, - message: L10n.of(context).unbanUserDescription, + title: l10n.areYouSure, + okLabel: l10n.yes, + cancelLabel: l10n.no, + message: l10n.unbanUserDescription, ) == OkCancelResult.ok) { + if (!context.mounted) return; await showFutureLoadingDialog( context: context, future: () => user.unban(), From 9c90db876d7b6bebb59f78028422db89484f5aa9 Mon Sep 17 00:00:00 2001 From: Alex Katon Date: Thu, 26 Mar 2026 13:06:24 +0100 Subject: [PATCH 12/75] chore(translations): Translated using Weblate (Belarusian) Currently translated at 100.0% (763 of 763 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/be/ --- lib/l10n/intl_be.arb | 411 ++++++++----------------------------------- 1 file changed, 69 insertions(+), 342 deletions(-) diff --git a/lib/l10n/intl_be.arb b/lib/l10n/intl_be.arb index 338e33f9..021578d1 100644 --- a/lib/l10n/intl_be.arb +++ b/lib/l10n/intl_be.arb @@ -4,28 +4,19 @@ "description": "Set to true to always display time of day in 24 hour format." }, "repeatPassword": "Паўтарыце пароль", - "@repeatPassword": {}, "notAnImage": "Не файл выявы.", - "@notAnImage": {}, "ignoreUser": "Ігнараваць карыстальніка", - "@ignoreUser": {}, "remove": "Прыбраць", "@remove": { "type": "String", "placeholders": {} }, "importNow": "Імпартаваць зараз", - "@importNow": {}, "importEmojis": "Імпартаваць эмодзі", - "@importEmojis": {}, "importFromZipFile": "Імпартаваць з файла .zip", - "@importFromZipFile": {}, "exportEmotePack": "Экспартаваць пак эмодзі як .zip", - "@exportEmotePack": {}, "replace": "Замяніць", - "@replace": {}, "about": "Пра праграму", - "@about": {}, "aboutHomeserver": "Пра {homeserver}", "@aboutHomeserver": { "type": "String", @@ -69,7 +60,6 @@ "placeholders": {} }, "confirmMatrixId": "Калі ласка, спраўдзіце свой Matrix ID перад выдаленнем свайго ўліковага запісу.", - "@confirmMatrixId": {}, "supposedMxid": "Гэта павінна быць накшталт {mxid}", "@supposedMxid": { "type": "String", @@ -80,7 +70,6 @@ } }, "addToSpace": "Дадаць у прастору", - "@addToSpace": {}, "admin": "Адмін", "@admin": { "type": "String", @@ -102,13 +91,9 @@ "placeholders": {} }, "commandHint_roomupgrade": "Абнавіце гэты пакой да згаданай версіі", - "@commandHint_roomupgrade": {}, "commandHint_googly": "Даслаць смешныя вочы", - "@commandHint_googly": {}, "commandHint_cuddle": "Даслаць усмешку", - "@commandHint_cuddle": {}, "commandHint_hug": "Даслаць абдымашкі", - "@commandHint_hug": {}, "googlyEyesContent": "{senderName} даслаў(-ла) вам смешныя вочы", "@googlyEyesContent": { "type": "String", @@ -156,13 +141,12 @@ "placeholders": {} }, "appLockDescription": "Блакіруе праграму, пакуль вы не ўвядзіце пін-код", - "@appLockDescription": {}, "archive": "Архіў", "@archive": { "type": "String", "placeholders": {} }, - "areGuestsAllowedToJoin": "Ці дазволена карыстальнікам-гасцям далучыцца", + "areGuestsAllowedToJoin": "Ці дазволена карыстальнікам-гасцям далучыцца?", "@areGuestsAllowedToJoin": { "type": "String", "placeholders": {} @@ -209,21 +193,13 @@ } }, "sendTypingNotifications": "Дасылаць паведамленне пра друк", - "@sendTypingNotifications": {}, "swipeRightToLeftToReply": "Змахніце ўлева, каб адказаць", - "@swipeRightToLeftToReply": {}, "sendOnEnter": "Дасылаць на enter", - "@sendOnEnter": {}, "noMoreChatsFound": "Болей чатаў не знойдзена...", - "@noMoreChatsFound": {}, "noChatsFoundHere": "Здаецца, тут пуста. Пачніце новы чат з кімсьці праз кнопку ніжэй. ⤵️", - "@noChatsFoundHere": {}, "unread": "Непрачытаные", - "@unread": {}, "space": "Прастора", - "@space": {}, "spaces": "Прасторы", - "@spaces": {}, "banFromChat": "Заблакіраваць ў чаце", "@banFromChat": { "type": "String", @@ -402,7 +378,6 @@ } }, "noMessagesYet": "Паведамленняў пакуль што няма", - "@noMessagesYet": {}, "changedTheRoomAliases": "{username} змяніў псеўданімы пакою", "@changedTheRoomAliases": { "type": "String", @@ -457,7 +432,6 @@ "placeholders": {} }, "yourChatBackupHasBeenSetUp": "Рэзервовае капіраванне чатаў было наладжана.", - "@yourChatBackupHasBeenSetUp": {}, "chatBackup": "Рэзервовае капіраванне чатаў", "@chatBackup": { "type": "String", @@ -484,16 +458,13 @@ "placeholders": {} }, "clearArchive": "Ачысціць архіў", - "@clearArchive": {}, "close": "Закрыць", "@close": { "type": "String", "placeholders": {} }, "commandHint_markasdm": "Пазначыць як пакой асабоных паведамленняў для дадання Matrix ID", - "@commandHint_markasdm": {}, "commandHint_markasgroup": "Пазначыць як групу", - "@commandHint_markasgroup": {}, "commandHint_ban": "Заблакіраваць карыстальніка у гэтым пакое", "@commandHint_ban": { "type": "String", @@ -648,7 +619,6 @@ } }, "checkList": "Кантрольны спіс", - "@checkList": {}, "countParticipants": "{count} удзельніка(-ў)", "@countParticipants": { "type": "String", @@ -682,7 +652,6 @@ } }, "createGroup": "Стварыць групу", - "@createGroup": {}, "createNewSpace": "Новая прастора", "@createNewSpace": { "type": "String", @@ -776,7 +745,6 @@ "placeholders": {} }, "chatPermissions": "Дазволы чату", - "@chatPermissions": {}, "editDisplayname": "Змяніць адлюстроўваемае імя", "@editDisplayname": { "type": "String", @@ -818,17 +786,11 @@ "placeholders": {} }, "globalChatId": "ID габальнага чату", - "@globalChatId": {}, "accessAndVisibility": "Даступнасць і бачнасць", - "@accessAndVisibility": {}, "accessAndVisibilityDescription": "Каму дазволена далучацца да гэтага чату і як ён можа быць знойдзены.", - "@accessAndVisibilityDescription": {}, "calls": "Выклікі", - "@calls": {}, "customEmojisAndStickers": "Карыстальніцкія эмодзі і стыкеры", - "@customEmojisAndStickers": {}, "customEmojisAndStickersBody": "Дадаць ці падзяліцца карыстальніцкімі эмодзі ці стыкерамі, што могуць быць ужыты ў любым чаце.", - "@customEmojisAndStickersBody": {}, "emoteShortcode": "Скарачэнне эмодзі", "@emoteShortcode": { "type": "String", @@ -879,14 +841,12 @@ } }, "enableMultiAccounts": "(БЭТА) Уключыць некалькі ўліковых запісаў на гэтай прыладзе", - "@enableMultiAccounts": {}, "enterAnEmailAddress": "Увядзіце электроную пошту (email)", "@enterAnEmailAddress": { "type": "String", "placeholders": {} }, "homeserver": "Дамашні сервер", - "@homeserver": {}, "errorObtainingLocation": "Памылка атрымання месцазнаходжання: {error}", "@errorObtainingLocation": { "type": "String", @@ -942,9 +902,7 @@ "placeholders": {} }, "chatDescription": "Апісанне чату", - "@chatDescription": {}, "chatDescriptionHasBeenChanged": "Апісанне чату зменена", - "@chatDescriptionHasBeenChanged": {}, "groupIsPublic": "Група публічная", "@groupIsPublic": { "type": "String", @@ -997,11 +955,8 @@ "placeholders": {} }, "hideRedactedMessages": "Схаваць адрэдагаваныя паведамленні", - "@hideRedactedMessages": {}, "hideRedactedMessagesBody": "Калі хтосьці рэдагуе паведамленне, яно будзе схавана ў чаце.", - "@hideRedactedMessagesBody": {}, "hideInvalidOrUnknownMessageFormats": "Хаваць памылковыя ці невядомыя фарматы паведамленняў", - "@hideInvalidOrUnknownMessageFormats": {}, "howOffensiveIsThisContent": "Наколькі абражальны гэты кантэнт?", "@howOffensiveIsThisContent": { "type": "String", @@ -1013,13 +968,9 @@ "placeholders": {} }, "block": "Заблакіраваць", - "@block": {}, "blockedUsers": "Заблакіраваныя карыстальнікі", - "@blockedUsers": {}, "blockListDescription": "Вы можаце заблакіраваць карыстальнікаў, якія вам перашкаджаюць. Вы не атрымаеце ад іх ні паведамленняў, ні запрашэнняў.", - "@blockListDescription": {}, "blockUsername": "Ігнараваць імя карыстальніка", - "@blockUsername": {}, "iHaveClickedOnLink": "Я перайшоў па спасылцы", "@iHaveClickedOnLink": { "type": "String", @@ -1050,20 +1001,15 @@ } }, "noChatDescriptionYet": "Апісанне чату яшчэ няма.", - "@noChatDescriptionYet": {}, "tryAgain": "Паспрабуйце зноў", - "@tryAgain": {}, "invalidServerName": "Недапушчальная назва сервера", - "@invalidServerName": {}, "invited": "Запрошаны", "@invited": { "type": "String", "placeholders": {} }, "redactMessageDescription": "Гэта паведамленне будзе адрэдагавана для усіх карыстальнікаў. Вы не зможаце яго адмяніць.", - "@redactMessageDescription": {}, "optionalRedactReason": "(Неабавязкова) Прычына рэдагавання паведамлення...", - "@optionalRedactReason": {}, "invitedUser": "📩 {username} запрасіў(-ла) {targetName}", "@invitedUser": { "type": "String", @@ -1175,11 +1121,8 @@ } }, "dehydrate": "Экспарт сеансу і ачыстка прылады", - "@dehydrate": {}, "dehydrateWarning": "Гэта дзеянне не можа быць адменена. Пераканайцеся, што вы бяспечна захавалі файл рэзервовай копіі.", - "@dehydrateWarning": {}, "hydrate": "Аднавіць з рэзервовай копіі", - "@hydrate": {}, "loadingPleaseWait": "Загрузка... Калі ласка, пачакайце.", "@loadingPleaseWait": { "type": "String", @@ -1230,7 +1173,6 @@ "placeholders": {} }, "messagesStyle": "Паведамленні:", - "@messagesStyle": {}, "moderator": "Мадэратар", "@moderator": { "type": "String", @@ -1304,9 +1246,7 @@ } }, "shareInviteLink": "Падзяліцца запрашальнай спасылкай", - "@shareInviteLink": {}, "scanQrCode": "Сканіраваць QR-код", - "@scanQrCode": {}, "none": "Нічога", "@none": { "type": "String", @@ -1333,7 +1273,6 @@ "placeholders": {} }, "setChatDescription": "Задаць апісанне чату", - "@setChatDescription": {}, "setStatus": "Задаць статус", "@setStatus": { "type": "String", @@ -1543,29 +1482,17 @@ "placeholders": {} }, "messageInfo": "Інфармацыя пра паведамленне", - "@messageInfo": {}, "time": "Час", - "@time": {}, "messageType": "Тып паведамлення", - "@messageType": {}, "sender": "Адпраўшчык", - "@sender": {}, "openGallery": "Адкрыць галерэю", - "@openGallery": {}, "removeFromSpace": "Выдаліць з прасторы", - "@removeFromSpace": {}, "start": "Пачаць", - "@start": {}, "pleaseEnterRecoveryKeyDescription": "Каб разблакіраваць вашы мінулыя паведамленні, калі ласка, увядзіце ключ аднаўлення, што быў згенерыраваны ў мінулай сесіі. Ключ аднаўлення гэта НЕ ваш пароль.", - "@pleaseEnterRecoveryKeyDescription": {}, "openChat": "Адкрыць чат", - "@openChat": {}, "markAsRead": "Адзначыць як прачытанае", - "@markAsRead": {}, "reportUser": "Паскардзіцца на карыстальніка", - "@reportUser": {}, "dismiss": "Адхіліць", - "@dismiss": {}, "reactedWith": "{sender} рэагуе з {reaction}", "@reactedWith": { "type": "String", @@ -1579,29 +1506,17 @@ } }, "pinMessage": "Прымацаваць да пакою", - "@pinMessage": {}, "confirmEventUnpin": "Вы ўпэўнены ў тым, што хаціце назаўсёды адмацаваць гэту падзею?", - "@confirmEventUnpin": {}, "emojis": "Эмодзі", - "@emojis": {}, "placeCall": "Здзейсніць выклік", - "@placeCall": {}, "voiceCall": "Галасавы выклік", - "@voiceCall": {}, "unsupportedAndroidVersion": "Непадтрымліваемая версія Android", - "@unsupportedAndroidVersion": {}, "unsupportedAndroidVersionLong": "Гэта функцыя патрабуе навейшай версіі Android. Калі ласка, праверце наяўнасць абнаўленняў ці падтрымку Linage OS.", - "@unsupportedAndroidVersionLong": {}, "videoCallsBetaWarning": "Звярніце ўвагу, што відэа выклікі знаходзяцца ў бэце. Яны могуць працаваць некарэктна ці не на ўсіх платформах.", - "@videoCallsBetaWarning": {}, "experimentalVideoCalls": "Эксперыментальныя відэа выклікі", - "@experimentalVideoCalls": {}, "youRejectedTheInvitation": "Вы скасавалі запрашэнне", - "@youRejectedTheInvitation": {}, "youJoinedTheChat": "Вы далучыліся да чату", - "@youJoinedTheChat": {}, "youAcceptedTheInvitation": "👍 Вы прынялі запрашэнне", - "@youAcceptedTheInvitation": {}, "youBannedUser": "Вы заблакіравалі {user}", "@youBannedUser": { "placeholders": { @@ -1675,25 +1590,15 @@ } }, "usersMustKnock": "Карыстальнікі абавязаны пагрукацца", - "@usersMustKnock": {}, "noOneCanJoin": "Ніхто не можа далучыцца", - "@noOneCanJoin": {}, "knock": "Пагрукацца", - "@knock": {}, "users": "Карыстальнікі", - "@users": {}, "unlockOldMessages": "Адкрыць старыя паведамленні", - "@unlockOldMessages": {}, "storeInSecureStorageDescription": "Захаваць код аднаўлення ў бяспечным месцы на прыладзе.", - "@storeInSecureStorageDescription": {}, "saveKeyManuallyDescription": "Захаваць гэты ключ самастойна, выклікам сістэмнага акна Падзяліцца ці праз буфер.", - "@saveKeyManuallyDescription": {}, "storeInAndroidKeystore": "Захаваць у Android KeyStore", - "@storeInAndroidKeystore": {}, "storeInAppleKeyChain": "Захаваць у Apple KeyChain", - "@storeInAppleKeyChain": {}, "storeSecurlyOnThisDevice": "Захаваць на гэтай прыладзе", - "@storeSecurlyOnThisDevice": {}, "countFiles": "{count} файлаў", "@countFiles": { "placeholders": { @@ -1703,29 +1608,17 @@ } }, "user": "Карыстальнік", - "@user": {}, "custom": "Карыстальніцкае", - "@custom": {}, "foregroundServiceRunning": "Гэта паведамленне з'явіцца, калі асноўныя службы запрацуюць.", - "@foregroundServiceRunning": {}, "screenSharingTitle": "падзяліцца экранам", - "@screenSharingTitle": {}, "screenSharingDetail": "Вы дзеліцеся экранам у FluffyChat", - "@screenSharingDetail": {}, "whyIsThisMessageEncrypted": "Чаму гэта паведамленне нельга прачытаць?", - "@whyIsThisMessageEncrypted": {}, "noKeyForThisMessage": "Гэта можа здарыцца з-за таго, што паведамленне было даслана да таго, як вы увайшлі ў уліковы запіс на гэтай прыладзе.\n\nТаксама верагодна, што адпраўшчык заблакіраваў вашу прыладу ці ў вас хібы з інтэрнэтам.\n\nВы можаце чытаць гэта паведамленне з іншага сеансу? Тад дашліце паведамленне адтуль! Перайдзіце ў Налады > Прылады і пераканайцеся ў тым, што вашы прылады верыфікавалі адна адну. Калі вы адкрыеце пакой наступны раз і абодве сэсіі будуць запушчаны, ключы павінны сінхранізавацца аўтаматычна.\n\nВы не хаціце згубіць клбчы, калі будзеце выходзіць ці змяняць прылады? Пераканайцеся ў тым, што вы уключылі рэзервовае капіраванне чатаў у наладах.", - "@noKeyForThisMessage": {}, "newGroup": "Новая група", - "@newGroup": {}, "newSpace": "Новая прастора", - "@newSpace": {}, "allSpaces": "Усе прасторы", - "@allSpaces": {}, "hidePresences": "Хаваць спіс статусаў?", - "@hidePresences": {}, "doNotShowAgain": "Не паказваць зноў", - "@doNotShowAgain": {}, "wasDirectChatDisplayName": "Пусты чат (быў {oldDisplayName})", "@wasDirectChatDisplayName": { "type": "String", @@ -1736,21 +1629,13 @@ } }, "newSpaceDescription": "Прасторы дазваляюць аб'ядноўваць вашы чаты і ствараць агульныя ці асобныя супольнасці.", - "@newSpaceDescription": {}, "encryptThisChat": "Шыфраваць гэты чат", - "@encryptThisChat": {}, "disableEncryptionWarning": "У мэтах бяспекі, вы не можаце адклбчауь шыфраванне ў гэтым чаце, дзе яно было ўключана.", - "@disableEncryptionWarning": {}, "sorryThatsNotPossible": "Прабачце... Гэта немагчыма", - "@sorryThatsNotPossible": {}, "deviceKeys": "Ключы прылад:", - "@deviceKeys": {}, "reopenChat": "Адкрыць чат зноў", - "@reopenChat": {}, "noBackupWarning": "Увага! Без уключэння рэзервовага капіравання чатаў, вы страціце доступ да вашых зашыфраваных паведамленняў. Настойліва рэкамендуецца уключыць фукнцыю да таго, як выйсці.", - "@noBackupWarning": {}, "noOtherDevicesFound": "Іншыя прылады не знойдзены", - "@noOtherDevicesFound": {}, "fileIsTooBigForServer": "Немагчыма даслаць! Сервер падтрымлівае файлы да {max}.", "@fileIsTooBigForServer": { "type": "String", @@ -1770,55 +1655,30 @@ } }, "jumpToLastReadMessage": "Перайсці да апошняга паведамлення", - "@jumpToLastReadMessage": {}, "readUpToHere": "Чытаць тут", - "@readUpToHere": {}, "jump": "Перайсці", - "@jump": {}, "openLinkInBrowser": "Адкрыць спасылку ў браўзеры", - "@openLinkInBrowser": {}, "reportErrorDescription": "😭 О не, штосьці пайшло не так. Калі жадаеце, можаце падаць справаздачу аб памылке распрауоўшчыкам.", - "@reportErrorDescription": {}, "report": "паскардзіцца", - "@report": {}, "manageAccount": "Кіраванне ўліковым запісам", - "@manageAccount": {}, "noContactInformationProvided": "Сервер не мае ніякай вернай кантактнай інфармацыі", - "@noContactInformationProvided": {}, "contactServerAdmin": "Звязацца з адміністратарам сервера", - "@contactServerAdmin": {}, "contactServerSecurity": "Звязацца з сервернай бяспекай", - "@contactServerSecurity": {}, "supportPage": "Падтрымка", - "@supportPage": {}, "serverInformation": "Серверная інфармацыя:", - "@serverInformation": {}, "name": "Імя", - "@name": {}, "version": "Версія", - "@version": {}, "website": "Сайт", - "@website": {}, "compress": "Сцісканне", - "@compress": {}, "boldText": "Цёмны", - "@boldText": {}, "italicText": "Курсіў", - "@italicText": {}, "strikeThrough": "Перакрэслены", - "@strikeThrough": {}, "pleaseFillOut": "Калі ласка, запоўніце", - "@pleaseFillOut": {}, "invalidUrl": "Няслушны url", - "@invalidUrl": {}, "addLink": "Дадаць спасылку", - "@addLink": {}, "unableToJoinChat": "Немагчыма далучыцца да чату. Магчыма, іншы бок ужо скончыў размову.", - "@unableToJoinChat": {}, "previous": "Мінулы", - "@previous": {}, "otherPartyNotLoggedIn": "Іншы бок зараз не увайшоў, таму не можа атрымліваць паведамленні!", - "@otherPartyNotLoggedIn": {}, "appWantsToUseForLogin": "Выкарыстоўваць '{server}' для ўвахода", "@appWantsToUseForLogin": { "type": "String", @@ -1829,75 +1689,40 @@ } }, "appWantsToUseForLoginDescription": "Тым самым, вы дазваляеце праграме і сайту дзяліцца інфармацыяй пра вас.", - "@appWantsToUseForLoginDescription": {}, "open": "Адкрыць", - "@open": {}, "waitingForServer": "Чаканне сервера...", - "@waitingForServer": {}, "newChatRequest": "📩 Запыт новага чату", - "@newChatRequest": {}, "contentNotificationSettings": "Налады паведамленняў кантэнту", - "@contentNotificationSettings": {}, "generalNotificationSettings": "Агульныя налады паведамленняў", - "@generalNotificationSettings": {}, "roomNotificationSettings": "Налады паведамленняў пакою", - "@roomNotificationSettings": {}, "userSpecificNotificationSettings": "Налады паведамленняў карыстальніка", - "@userSpecificNotificationSettings": {}, "otherNotificationSettings": "Іншыя налады паведамленняў", - "@otherNotificationSettings": {}, "notificationRuleContainsUserName": "Змяшчае імя карыстальніка", - "@notificationRuleContainsUserName": {}, "notificationRuleContainsUserNameDescription": "Паведамляе пра тое, што паведамленне мае імя карыстальніка.", - "@notificationRuleContainsUserNameDescription": {}, "notificationRuleMaster": "Заглушыць усе паведамленні", - "@notificationRuleMaster": {}, "notificationRuleMasterDescription": "Перазапісвае ўсе іншыя правілы і адключае паведамленні.", - "@notificationRuleMasterDescription": {}, "notificationRuleSuppressNotices": "Адключыць аўтаматычныя паведамленні", - "@notificationRuleSuppressNotices": {}, "notificationRuleSuppressNoticesDescription": "Адключыць паведамленні ад аўтаматызаваных кліентаў, накшталт ботаў.", - "@notificationRuleSuppressNoticesDescription": {}, "notificationRuleInviteForMe": "Запрашэнне мяне", - "@notificationRuleInviteForMe": {}, "notificationRuleInviteForMeDescription": "Паведамляе карыстальніка, калі яго запрашаюць у пакой.", - "@notificationRuleInviteForMeDescription": {}, "allDevices": "Усе прылады", - "@allDevices": {}, "crossVerifiedDevicesIfEnabled": "З перакрыжаваным спраўджваннем прылад, калі ўключана", - "@crossVerifiedDevicesIfEnabled": {}, "crossVerifiedDevices": "Перакрыжавана спраўджаныя прылады", - "@crossVerifiedDevices": {}, "verifiedDevicesOnly": "Толькі спраўджаныя прылады", - "@verifiedDevicesOnly": {}, "takeAPhoto": "Зрабіць здымак", - "@takeAPhoto": {}, "recordAVideo": "Запісаць відэа", - "@recordAVideo": {}, "optionalMessage": "(Апцыянальна) паведамленне...", - "@optionalMessage": {}, "notSupportedOnThisDevice": "Не падтрымліваецца на гэтай прыладзе", - "@notSupportedOnThisDevice": {}, "enterNewChat": "Увядзіце новы чат", - "@enterNewChat": {}, "approve": "Пацвердзіць", - "@approve": {}, "youHaveKnocked": "Вы былі выгнаны", - "@youHaveKnocked": {}, "pleaseWaitUntilInvited": "Калі ласка, пачакайце, пакуль хтосьці з пакою вас не запрасіць.", - "@pleaseWaitUntilInvited": {}, "commandHint_logout": "Выйсці з бягуяай прылады", - "@commandHint_logout": {}, "commandHint_logoutall": "Выйсці на ўсіх актыўных прыладах", - "@commandHint_logoutall": {}, "displayNavigationRail": "Паказваць навігацыйны след на тэлефоне", - "@displayNavigationRail": {}, "customReaction": "Карыстальніцкая рэакцыя", - "@customReaction": {}, "moreEvents": "Больш падзей", - "@moreEvents": {}, "declineInvitation": "Скасаваць запрашэнне", - "@declineInvitation": {}, "numUsersTyping": "{count} карыстальнікаў пішуць…", "@numUsersTyping": { "type": "String", @@ -1958,26 +1783,18 @@ "placeholders": {} }, "oneClientLoggedOut": "Адзін з вашых кліентаў выйшаў", - "@oneClientLoggedOut": {}, "addAccount": "Дадаць уліковы запіс", - "@addAccount": {}, "editBundlesForAccount": "Змяніць пакеты для гэтага ўліковага запісу", - "@editBundlesForAccount": {}, "addToBundle": "Дадаць у пакет", - "@addToBundle": {}, "removeFromBundle": "Выдаліць з пакета", - "@removeFromBundle": {}, "bundleName": "Назва пакета", - "@bundleName": {}, "openInMaps": "Адкрыць на картах", "@openInMaps": { "type": "String", "placeholders": {} }, "link": "Спасылка", - "@link": {}, "serverRequiresEmail": "Гэты сервер павінен спраўдзіць ваш email для рэгістрацыі.", - "@serverRequiresEmail": {}, "or": "Ці", "@or": { "type": "String", @@ -2009,9 +1826,7 @@ "placeholders": {} }, "overview": "Агляд", - "@overview": {}, "passwordRecoverySettings": "Налады скіду пароля", - "@passwordRecoverySettings": {}, "passwordRecovery": "Аднаўленне пароля", "@passwordRecovery": { "type": "String", @@ -2106,7 +1921,6 @@ } }, "directChat": "Асобны чат", - "@directChat": {}, "redactedByBecause": "Адрэдагавана {username}, прычына: \"{reason}\"", "@redactedByBecause": { "type": "String", @@ -2222,9 +2036,7 @@ "placeholders": {} }, "recoveryKey": "Ключ аднаўлення", - "@recoveryKey": {}, "recoveryKeyLost": "Ключ абнаўлення страчаны?", - "@recoveryKeyLost": {}, "send": "Даслаць", "@send": { "type": "String", @@ -2458,7 +2270,6 @@ } }, "unverified": "Не спраўджана", - "@unverified": {}, "verified": "Спраўджана", "@verified": { "type": "String", @@ -2490,19 +2301,12 @@ "placeholders": {} }, "verifyOtherUserDescription": "Калі вы спраўдзілі іншага карыстальніка, вы можаце быць упэўненым з кім вы сапраўды перапісваецеся.💪\n\nКалі вы пачнеце спраўджванне, вы і іншы карыстальнік, убачыце ўсплывальнае акно ў праграме. У ім вы ўбачыце некалькі эмодзі ці лічб, якія вы павінны параўнаць адзін з адным.\n\nЛепшы метад зрабіць гэта - пачаць відэа выклік. 👭", - "@verifyOtherUserDescription": {}, "pleaseEnterANumber": "Калі ласка, увядзіце лічбу большую за 0", - "@pleaseEnterANumber": {}, "verifyOtherDeviceDescription": "Калі вы спраўдзіце другую прыладу, яны абмяняюцца ключамі, якія ўзмоцняць вашу бяспеку. 💪 Калі вы пачнеце спраўджванне, у праграмах прылад з'явіцца ўсплывальнае паведамленне. Потым, вы ўбачыце некалькі эмодзі ці лічбаў, якія вы павінны параўнаць паміж сабой. Прасцей за ўсё гэта зрабіць, маючы дзве прылады побач. 🤳", - "@verifyOtherDeviceDescription": {}, "verifyOtherUser": "🔐 Спраўдзіць іншага карыстальніка", - "@verifyOtherUser": {}, "verifyOtherDevice": "🔐 Спраўдзіць іншую прыладу", - "@verifyOtherDevice": {}, "changeTheCanonicalRoomAlias": "Змяніць публічны адрас чату", - "@changeTheCanonicalRoomAlias": {}, "wrongRecoveryKey": "Прабачце... гэта не выглядае як ключ аднаўлення.", - "@wrongRecoveryKey": {}, "restoreSessionBody": "Праграма спрабуе аднавіць вашу сесію з рэзервовай копіі. Калі ласка, паведаміце пра памылку распрацоўшчыкам па спасылцы {url}. Паведамленне памылкі: {error}", "@restoreSessionBody": { "type": "String", @@ -2516,7 +2320,6 @@ } }, "longPressToRecordVoiceMessage": "Доўга цісніце, каб запісаць галасавое паведамленне.", - "@longPressToRecordVoiceMessage": {}, "visibilityOfTheChatHistory": "Бачнасць гісторыі чату", "@visibilityOfTheChatHistory": { "type": "String", @@ -2528,13 +2331,9 @@ "placeholders": {} }, "setColorTheme": "Каляровая схема:", - "@setColorTheme": {}, "invite": "Запрасіць", - "@invite": {}, "inviteGroupChat": "📨 Запрашэнне ў групавы чат", - "@inviteGroupChat": {}, "invalidInput": "Недапушчальны ўвод!", - "@invalidInput": {}, "wrongPinEntered": "Няверны пін-код! Паспрабуйце праз {seconds} секунд...", "@wrongPinEntered": { "type": "String", @@ -2545,29 +2344,17 @@ } }, "archiveRoomDescription": "Чат перамясціцца ў архіў. Іншыя карыстальнікі будуць бачыць гэта так, быццам вы выйшлі з чату.", - "@archiveRoomDescription": {}, "roomUpgradeDescription": "Чат будзе пераствораны з новай версіяй пакою. Усе ўдзельнікі будуць паведамлены пра неабходнасць перайсці ў новы чат. Вы можаце даведацца пра версіі пакояў тут: https://spec.matrix.org/latest/rooms/", - "@roomUpgradeDescription": {}, "removeDevicesDescription": "Вы выйдзеце з гэтай прылады і больш не будзеце атрымліваць паведамленні.", - "@removeDevicesDescription": {}, "sendTypingNotificationsDescription": "Іншыя ўдзельнікі чату могуць бачыць, калі вы пішаце новае паведамленне.", - "@sendTypingNotificationsDescription": {}, "continueText": "Працягнуць", - "@continueText": {}, "banUserDescription": "Карыстальнік будзе заблакіраваны з чату і больш не зможа ўвайсці, пакуль вы яго не разблакіруеце.", - "@banUserDescription": {}, "unbanUserDescription": "Карыстальнік зможа зноў далучыцца да чату.", - "@unbanUserDescription": {}, "kickUserDescription": "Карыстальнік будзе выгнаны, але не заблакіраваны. У публічных чатах, ён зможа далучыцца зноў у любы час.", - "@kickUserDescription": {}, "makeAdminDescription": "Калі вы зробіце карыстальніка адміністратарам, вы не зможаце адмяніць гэта дзеянне, бо ён будзе мець такія ж правы, як і вы.", - "@makeAdminDescription": {}, "pushNotificationsNotAvailable": "Пуш-паведамленні недаступны", - "@pushNotificationsNotAvailable": {}, "learnMore": "Даведацца больш", - "@learnMore": {}, "yourGlobalUserIdIs": "Ваш глабальны ID-карыстальніка: ", - "@yourGlobalUserIdIs": {}, "noUsersFoundWithQuery": "На жаль, мы не змаглі знайсці карыстальніка з імём \"{query}\". Калі ласка, праверце наяўнасць памылак.", "@noUsersFoundWithQuery": { "type": "String", @@ -2578,9 +2365,7 @@ } }, "knocking": "Грукацца", - "@knocking": {}, "knockRestricted": "Грук абмежаваны", - "@knockRestricted": {}, "spaceMemberOfCanKnock": "Удзельнікі прасторы з {spaces} могуць грукацца", "@spaceMemberOfCanKnock": { "type": "String", @@ -2600,51 +2385,28 @@ } }, "searchChatsRooms": "Пошук #чатаў, @карыстальнікаў...", - "@searchChatsRooms": {}, "nothingFound": "Нічога не знойдзена...", - "@nothingFound": {}, "groupName": "Назва групы", - "@groupName": {}, "createGroupAndInviteUsers": "Стварыць групу і запрасіць карыстальнікаў", - "@createGroupAndInviteUsers": {}, "groupCanBeFoundViaSearch": "Група можа быць знойдзена праз пошук", - "@groupCanBeFoundViaSearch": {}, "commandHint_sendraw": "Даслаць толькі json", - "@commandHint_sendraw": {}, "databaseMigrationTitle": "База даных аптымізавана", - "@databaseMigrationTitle": {}, "databaseMigrationBody": "Калі ласка, пачакайце. Гэта можа заняць некаторы час.", - "@databaseMigrationBody": {}, "leaveEmptyToClearStatus": "Пакіньце пустым, каб ачысціць свой статус.", - "@leaveEmptyToClearStatus": {}, "select": "Выбраць", - "@select": {}, "searchForUsers": "Пошук @карыстальнікаў...", - "@searchForUsers": {}, "pleaseEnterYourCurrentPassword": "Калі ласка, увядзіце свой бягучы пароль", - "@pleaseEnterYourCurrentPassword": {}, "newPassword": "Новы пароль", - "@newPassword": {}, "pleaseChooseAStrongPassword": "Калі ласка, падбярыце больш надзейны пароль", - "@pleaseChooseAStrongPassword": {}, "passwordsDoNotMatch": "Паролі не супадаюць", - "@passwordsDoNotMatch": {}, "passwordIsWrong": "Вы ўвялі няверны пароль", - "@passwordIsWrong": {}, "publicChatAddresses": "Публічныя адрасы чату", - "@publicChatAddresses": {}, "createNewAddress": "Стварыць новы адрас", - "@createNewAddress": {}, "joinSpace": "Далучыцца да прасторы", - "@joinSpace": {}, "publicSpaces": "Публічныя прасторы", - "@publicSpaces": {}, "addChatOrSubSpace": "Дадаць чат ці пад-прастору", - "@addChatOrSubSpace": {}, "thisDevice": "Гэта прылада:", - "@thisDevice": {}, "initAppError": "Адбылася памылка пры ініцыялізацыі праграмы", - "@initAppError": {}, "searchIn": "Пошук у чаце \"{chat}\"...", "@searchIn": { "type": "String", @@ -2655,11 +2417,8 @@ } }, "searchMore": "Шукаць яшчэ...", - "@searchMore": {}, "gallery": "Галерэя", - "@gallery": {}, "files": "Файлы", - "@files": {}, "sessionLostBody": "Ваш сеанс страчаны. Калі ласка, паведаміце пра гэта распрацоўшчыкам: {url}. Паведамленне памылкі: {error}", "@sessionLostBody": { "type": "String", @@ -2673,13 +2432,9 @@ } }, "sendReadReceipts": "Дасылаць адзнаку аб чытанні", - "@sendReadReceipts": {}, "sendReadReceiptsDescription": "Іншыя карыстальнікі чатаў будуць бачыць, калі вы прачыталі паведамленні.", - "@sendReadReceiptsDescription": {}, "formattedMessages": "Фармаціраваныя паведамленні", - "@formattedMessages": {}, "formattedMessagesDescription": "Адлюстроўваць пашыраныя паведамленні разметкай markdown.", - "@formattedMessagesDescription": {}, "acceptedKeyVerification": "{sender} прыняў(-ла) спраўджванне ключэй", "@acceptedKeyVerification": { "type": "String", @@ -2735,17 +2490,11 @@ } }, "transparent": "Празрысты", - "@transparent": {}, "incomingMessages": "Уваходныя паведамленні", - "@incomingMessages": {}, "stickers": "Стыкеры", - "@stickers": {}, "discover": "Даследаваць", - "@discover": {}, "commandHint_ignore": "Ігнараваць дадзены matrix ID", - "@commandHint_ignore": {}, "commandHint_unignore": "Перастаць ігнараваць дадзены matrix ID", - "@commandHint_unignore": {}, "unreadChatsInApp": "{appname}: {unread} непрачытаных чатаў", "@unreadChatsInApp": { "type": "String", @@ -2759,21 +2508,18 @@ } }, "noDatabaseEncryption": "Шыфраванне базы даных не падтрымліваецца гэтай платформай", - "@noDatabaseEncryption": {}, "thereAreCountUsersBlocked": "На гэты момант, {count} карыстальнікаў заблакіравана.", "@thereAreCountUsersBlocked": { "type": "String", "count": {} }, "restricted": "Абмежавана", - "@restricted": {}, "goToSpace": "Перайсці да прасторы: {space}", "@goToSpace": { "type": "String", "space": {} }, "markAsUnread": "Адзначыць як непрачытанае", - "@markAsUnread": {}, "userLevel": "{level} - Карыстальнік", "@userLevel": { "type": "String", @@ -2802,19 +2548,12 @@ } }, "changeGeneralChatSettings": "Змяніць агульныя налады чату", - "@changeGeneralChatSettings": {}, "inviteOtherUsers": "Запрасіць іншых карыстальнікаў у гэты чат", - "@inviteOtherUsers": {}, "changeTheChatPermissions": "Змяніць дазволы чату", - "@changeTheChatPermissions": {}, "changeTheVisibilityOfChatHistory": "Змяніць бачнасць гісторыі чату", - "@changeTheVisibilityOfChatHistory": {}, "sendRoomNotifications": "Дасылаць паведамленні @room", - "@sendRoomNotifications": {}, "changeTheDescriptionOfTheGroup": "Змяніць апісанне чату", - "@changeTheDescriptionOfTheGroup": {}, "chatPermissionsDescription": "Задаць узровень дазволаў, які неабходны для некаторых дзеянняў у чаце. Узроўні 0, 50 і 100 звычайна адлюстроўваюць карыстальнікаў, мадэратараў і адміністратараў, але любая градацыя магчыма.", - "@chatPermissionsDescription": {}, "updateInstalled": "🎉 Абнаўленне {version} усталявана!", "@updateInstalled": { "type": "String", @@ -2825,23 +2564,14 @@ } }, "changelog": "Спіс змен", - "@changelog": {}, "sendCanceled": "Адпраўка скасавана", - "@sendCanceled": {}, "loginWithMatrixId": "Увайсці з Matrix-ID", - "@loginWithMatrixId": {}, "doesNotSeemToBeAValidHomeserver": "Гэта не выглядае як дамашні сервер. Няслушны URL?", - "@doesNotSeemToBeAValidHomeserver": {}, "calculatingFileSize": "Вылічэнне памеру файла...", - "@calculatingFileSize": {}, "prepareSendingAttachment": "Падрыхтоўка адпраўкі прыкладання...", - "@prepareSendingAttachment": {}, "sendingAttachment": "Адпраўка прыкладання...", - "@sendingAttachment": {}, "generatingVideoThumbnail": "Стварэнне вокладкі відэа...", - "@generatingVideoThumbnail": {}, "compressVideo": "Сцісканне відэа...", - "@compressVideo": {}, "sendingAttachmentCountOfCount": "Адпраўляецца прыкладанне {index} з {length}...", "@sendingAttachmentCountOfCount": { "type": "integer", @@ -2864,81 +2594,43 @@ } }, "oneOfYourDevicesIsNotVerified": "Адна з вашых прылад не спраўджана", - "@oneOfYourDevicesIsNotVerified": {}, "noticeChatBackupDeviceVerification": "Заўвага: Калі вы падключыце ўсе свае прылады да рэзервовага капіравання, яны аўтаматычна спраўдзяцца.", - "@noticeChatBackupDeviceVerification": {}, "welcomeText": "Вітаначкі 👋 Гэта FluffyChat. Вы можаце ўвайсці на любы дамашні сервер, што сумяшчальны з https://matrix.org, а потым паразмаўляць з кім-небудзь. Гэта вялізная дэцэнтралізаваная сетка абмену паведамленнямі!", - "@welcomeText": {}, "blur": "Размыццё:", - "@blur": {}, "opacity": "Празрыстасць:", - "@opacity": {}, "setWallpaper": "Задаць шпалеры", - "@setWallpaper": {}, "notificationRuleMemberEvent": "Падзеі ўдзельніцтва", - "@notificationRuleMemberEvent": {}, "notificationRuleMemberEventDescription": "Спыніць усе паведамленні пра ўдзельніцтва.", - "@notificationRuleMemberEventDescription": {}, "notificationRuleIsUserMention": "Згадванні карыстальніка", - "@notificationRuleIsUserMention": {}, "notificationRuleIsUserMentionDescription": "Паведамляе, калі карыстальніка згадалі ў паведамленні.", - "@notificationRuleIsUserMentionDescription": {}, "notificationRuleContainsDisplayName": "Мае адлюстроўваемае імя", - "@notificationRuleContainsDisplayName": {}, "notificationRuleContainsDisplayNameDescription": "Паведамляе, калі паведамленне мае іх адлюстроўваемае імя.", - "@notificationRuleContainsDisplayNameDescription": {}, "notificationRuleIsRoomMention": "Згадванні пакою", - "@notificationRuleIsRoomMention": {}, "notificationRuleIsRoomMentionDescription": "Паведамляе карыстальніка, калі згадваюць пакой.", - "@notificationRuleIsRoomMentionDescription": {}, "notificationRuleRoomnotif": "Паведамленні пакою", - "@notificationRuleRoomnotif": {}, "notificationRuleRoomnotifDescription": "Паведамляе пра згадванні '@room'.", - "@notificationRuleRoomnotifDescription": {}, "notificationRuleTombstone": "Помнік", - "@notificationRuleTombstone": {}, "notificationRuleTombstoneDescription": "Паведамляе пра дэактывацыю пакою.", - "@notificationRuleTombstoneDescription": {}, "notificationRuleReaction": "Рэакцыя", - "@notificationRuleReaction": {}, "notificationRuleReactionDescription": "Адключыць усе паведамленні пра рэакцыі.", - "@notificationRuleReactionDescription": {}, "notificationRuleRoomServerAcl": "ACL сервера пакою", - "@notificationRuleRoomServerAcl": {}, "notificationRuleRoomServerAclDescription": "Адключыць паведамленні пра серверныя спісы кантролю пакою (ACL).", - "@notificationRuleRoomServerAclDescription": {}, "notificationRuleSuppressEdits": "Заглушыць змены", - "@notificationRuleSuppressEdits": {}, "notificationRuleSuppressEditsDescription": "Заглушыць паведамленні пра адрэдагаваныя паведамленні.", - "@notificationRuleSuppressEditsDescription": {}, "notificationRuleCall": "Выклік", - "@notificationRuleCall": {}, "notificationRuleCallDescription": "Паведамляе пра выклікі.", - "@notificationRuleCallDescription": {}, "notificationRuleEncryptedRoomOneToOne": "Шыфраваны пакой One-to-One", - "@notificationRuleEncryptedRoomOneToOne": {}, "notificationRuleEncryptedRoomOneToOneDescription": "Паведамляе пра паведамленні ў шыфраваных one-to-one пакоях.", - "@notificationRuleEncryptedRoomOneToOneDescription": {}, "notificationRuleRoomOneToOne": "Пакой One-to-One", - "@notificationRuleRoomOneToOne": {}, "notificationRuleRoomOneToOneDescription": "Паведамляе пра паведамленні ў пакоях one-to-one.", - "@notificationRuleRoomOneToOneDescription": {}, "notificationRuleMessage": "Паведамленне", - "@notificationRuleMessage": {}, "notificationRuleMessageDescription": "Паведамляе пра звычайныя паведамленні.", - "@notificationRuleMessageDescription": {}, "notificationRuleEncrypted": "Зашыфравана", - "@notificationRuleEncrypted": {}, "notificationRuleEncryptedDescription": "Паведамляе пра паведамленні ў зашыфраваных пакоях.", - "@notificationRuleEncryptedDescription": {}, "notificationRuleJitsi": "Jitsi", - "@notificationRuleJitsi": {}, "notificationRuleJitsiDescription": "Паведамляе пра падзеі віджэту Jitsi.", - "@notificationRuleJitsiDescription": {}, "notificationRuleServerAcl": "Заглушыць серверныя падзеі ACL", - "@notificationRuleServerAcl": {}, "notificationRuleServerAclDescription": "Заглушыць паведамленні пра серверныя падзеі ACL.", - "@notificationRuleServerAclDescription": {}, "unknownPushRule": "Невядомае правіла пуша '{rule}'", "@unknownPushRule": { "type": "String", @@ -2961,19 +2653,12 @@ } }, "deletePushRuleCanNotBeUndone": "Калі вы выдаліце гэтыя налады паведамленняў, гэта не можа быць адменена.", - "@deletePushRuleCanNotBeUndone": {}, "more": "Больш", - "@more": {}, "shareKeysWith": "Падзяліцца ключамі з...", - "@shareKeysWith": {}, "shareKeysWithDescription": "Якім прыладам вы давяраеце настолькі, каб яны маглі чытаць вашыя зашыфраваныя паведамленні?", - "@shareKeysWithDescription": {}, "pause": "Паўза", - "@pause": {}, "resume": "Працягнуць", - "@resume": {}, "removeFromSpaceDescription": "Гэты чат будзе выдалены з прасторы, але з'явіцца ў вашым спісе чатаў.", - "@removeFromSpaceDescription": {}, "countChats": "{chats} чатаў", "@countChats": { "type": "String", @@ -3002,23 +2687,14 @@ } }, "poll": "Апытанне", - "@poll": {}, "startPoll": "Пачаць апытанне", - "@startPoll": {}, "endPoll": "Скончыць апытанне", - "@endPoll": {}, "answersVisible": "Адказы бачны", - "@answersVisible": {}, "pollQuestion": "Пытанне апытання", - "@pollQuestion": {}, "answerOption": "Варыянт адказу", - "@answerOption": {}, "addAnswerOption": "Дадаць варыянт адказу", - "@addAnswerOption": {}, "allowMultipleAnswers": "Дазволіць некалькі адказаў", - "@allowMultipleAnswers": {}, "pollHasBeenEnded": "Апытанне было скончана", - "@pollHasBeenEnded": {}, "countVotes": "{count, plural, =1{Адзін голас} other{{count} галасы(-оў)}}", "@countVotes": { "type": "int", @@ -3029,9 +2705,7 @@ } }, "answersWillBeVisibleWhenPollHasEnded": "Вынікі будуць бачны, калі апытанне скончыцца", - "@answersWillBeVisibleWhenPollHasEnded": {}, "replyInThread": "Адказаць у гутарку", - "@replyInThread": {}, "countReplies": "{count, plural, =1{Адзін адказ} other{{count} адказа(-ў)}}", "@countReplies": { "type": "int", @@ -3042,31 +2716,84 @@ } }, "thread": "Гутарка", - "@thread": {}, "backToMainChat": "Вярнуцца ў галоўны чат", - "@backToMainChat": {}, "saveChanges": "Захаваць змены", - "@saveChanges": {}, "createSticker": "Стварыць стыкер ці эмадзі", - "@createSticker": {}, "useAsSticker": "Ужыць як стыкер", - "@useAsSticker": {}, "useAsEmoji": "Ужыць як эмадзі", - "@useAsEmoji": {}, "stickerPackNameAlreadyExists": "Назва набору стыкераў ужо існуе", - "@stickerPackNameAlreadyExists": {}, "newStickerPack": "Новы набор стыкераў", - "@newStickerPack": {}, "stickerPackName": "Назва набору стыкераў", - "@stickerPackName": {}, "attribution": "Атрыбуцыя", - "@attribution": {}, "skipChatBackup": "Прапусціць рэзервовае капіраванне чатаў", - "@skipChatBackup": {}, "skipChatBackupWarning": "Вы ўпэўнены? Без наладжвання рэзервовага капіравання чатаў, вы можаце згубіць доступ да ўсіх вашых чатаў, калі вы зменіце прыладу.", - "@skipChatBackupWarning": {}, "loadingMessages": "Загрузка паведамленняў", - "@loadingMessages": {}, "setupChatBackup": "Наладзіць рэзервовае капіраванне чатаў", - "@setupChatBackup": {} -} \ No newline at end of file + "changedTheChatDescription": "{username} змяніў апісанне чата", + "changedTheChatName": "{username} змяніў назву чата", + "noMoreResultsFound": "Нічога не знойдзена", + "chatSearchedUntil": "Пошук у чаце да {time}", + "@chatSearchedUntil": { + "type": "String", + "placeholders": { + "time": { + "type": "String" + } + } + }, + "federationBaseUrl": "Асноўны URL федэрацыі", + "clientWellKnownInformation": "Client-Well-Known інфармацыя:", + "baseUrl": "Базавы URL", + "identityServer": "Сервер профілей:", + "versionWithNumber": "Версія: {version}", + "@versionWithNumber": { + "type": "String", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "logs": "Логі", + "advancedConfigs": "Пашыраныя налады", + "advancedConfigurations": "Пашыраная канфігурацыя", + "signIn": "Увайсці", + "createNewAccount": "Стварыць новы ўліковы запіс", + "signUpGreeting": "FluffyChat дэцэнтралізаваны! Выберыце свой сервер, дзе вы хаціце стварыць уліковы запіс і прайягвайце!", + "signInGreeting": "Вы ўжо маеце ўліковы запіс у Matrix? З вяртаннем! Выберыце свой хатні сервер і аўтарызуйцеся.", + "appIntro": "З дапамогай FluffyChat вы можаце размаўляць з вашымі сябрамі. Гэта бяспечны дэцэнтралізаваны [matrix] мэнэджэр! Даведайцеся больш на https://matrix.org, калі хаціце ці проста ўвайдзіце.", + "theProcessWasCanceled": "Працэс быў скасаваны.", + "join": "Далучыцца", + "searchOrEnterHomeserverAddress": "Пашукайце ці ўвядзіце адрас хатняга сервера", + "matrixId": "Matrix ID", + "setPowerLevel": "Прызначыць узровень магчымасцей", + "makeModerator": "Прызначыць мадэратарам", + "makeAdmin": "Прызначыць адміністратарам", + "removeModeratorRights": "Адабраць правы мадэратара", + "removeAdminRights": "Прыбраць адміністратарскія правы", + "powerLevel": "Узровень дазволаў", + "setPowerLevelDescription": "Узровень дазволаў вызначае, што ўдзельнік можа рабіць у пакое і звычайна знаходзіцца паміж 0 і 100.", + "owner": "Уладальнік", + "mute": "Сцішыць", + "@mute": { + "description": "This should be a very short string because there is not much space in the button!" + }, + "createNewChat": "Стварыць новы чат", + "reset": "Скінуць", + "supportFluffyChat": "Падтрымаць FluffyChat", + "support": "Падтрымаць", + "fluffyChatSupportBannerMessage": "FluffyChat патрэбна ВАША дапамога!\n❤️❤️❤️\nFluffyChat будзе заўсёды бясплатным, bале распрацоўка і арэнда сервероў мае свой кошт.\nБудучыня праекту залежыць ад падтрымкі людзей як вы.", + "skipSupportingFluffyChat": "Прапусціць падтрымку FluffyChat", + "iDoNotWantToSupport": "Я не хачу падтрымаць", + "iAlreadySupportFluffyChat": "Я ўжо падтрымаў FluffyChat", + "setLowPriority": "Прызначыць нізкі прыярытэт", + "unsetLowPriority": "Скасаваць нізкі прыярытэт", + "removeCallFromChat": "Прыбраць выклік з чату", + "removeCallFromChatDescription": "Вы хаціце прыбраць выклік з чату для ўсіх удзельнікаў?", + "removeCallForEveryone": "Прыбраць выклікі для ўсіх", + "startVoiceCall": "Пачаць галасавы выклік", + "startVideoCall": "Пачаць відэа-выклік", + "joinVoiceCall": "Далучыцца да галасавога выкліка", + "joinVideoCall": "Далучыцца да відэа-выкліка", + "live": "Трансляцыя" +} From f170989122e36be77b4b0ae02ea41aa72a524f32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 28 Mar 2026 08:16:59 +0000 Subject: [PATCH 13/75] build: (deps): bump wakelock_plus from 1.5.0 to 1.5.1 Bumps [wakelock_plus](https://github.com/fluttercommunity/wakelock_plus) from 1.5.0 to 1.5.1. - [Commits](https://github.com/fluttercommunity/wakelock_plus/compare/wakelock_plus_1.5.0...wakelock_plus_1.5.1) --- updated-dependencies: - dependency-name: wakelock_plus dependency-version: 1.5.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 63be6383..789b0182 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2053,10 +2053,10 @@ packages: dependency: "direct main" description: name: wakelock_plus - sha256: e4e125b7c1a2f0e491e5452afdc0e25ab77b2d2775a7caa231fcc1c1f2162c47 + sha256: "8b12256f616346910c519a35606fb69b1fe0737c06b6a447c6df43888b097f39" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.5.1" wakelock_plus_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d7d056dc..5034b08d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -77,7 +77,7 @@ dependencies: url_launcher: ^6.3.2 video_compress: ^3.1.4 video_player: ^2.11.1 - wakelock_plus: ^1.5.0 + wakelock_plus: ^1.5.1 webrtc_interface: ^1.5.1 dev_dependencies: From d40cbd1bc06955da421c205a40522fd2345f754a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Sat, 28 Mar 2026 13:15:10 +0100 Subject: [PATCH 14/75] fix: Chat view crashes if events list empty --- lib/pages/chat/chat_event_list.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index d4ff1163..65021ad0 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -77,7 +77,7 @@ class ChatEventList extends StatelessWidget { return Column( mainAxisSize: .min, children: [ - SeenByRow(event: events.first), + if (events.isNotEmpty) SeenByRow(event: events.first), TypingIndicators(controller), ], ); From dbf0894b53df1be5bf45d1713ec03902cfc63ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Sat, 28 Mar 2026 15:06:38 +0100 Subject: [PATCH 15/75] fix: Getting logged out on dialog dismissed closes https://github.com/krille-chan/fluffychat/issues/2777 --- lib/pages/settings/settings.dart | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index e8b21a54..8829f16f 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -65,18 +65,16 @@ class SettingsController extends State { Future logoutAction() async { final l10n = L10n.of(context); final matrix = Matrix.of(context); - if (await showOkCancelAlertDialog( - useRootNavigator: false, - context: context, - title: l10n.areYouSureYouWantToLogout, - message: l10n.noBackupWarning, - isDestructive: cryptoIdentityConnected == false, - okLabel: l10n.logout, - cancelLabel: l10n.cancel, - ) == - OkCancelResult.cancel) { - return; - } + final consent = await showOkCancelAlertDialog( + useRootNavigator: false, + context: context, + title: l10n.areYouSureYouWantToLogout, + message: l10n.noBackupWarning, + isDestructive: cryptoIdentityConnected == false, + okLabel: l10n.logout, + cancelLabel: l10n.cancel, + ); + if (consent != OkCancelResult.ok) return; if (!mounted) return; await showFutureLoadingDialog( context: context, From a34909ad5d897bccc15736b7eda3a273660ce260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Sat, 28 Mar 2026 15:18:06 +0100 Subject: [PATCH 16/75] chore: Small design adjustments --- lib/pages/sign_in/sign_in_page.dart | 1 + lib/widgets/navigation_rail.dart | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/pages/sign_in/sign_in_page.dart b/lib/pages/sign_in/sign_in_page.dart index c5ee696b..d946a3b9 100644 --- a/lib/pages/sign_in/sign_in_page.dart +++ b/lib/pages/sign_in/sign_in_page.dart @@ -108,6 +108,7 @@ class SignInPage extends StatelessWidget { SizedBox.square( dimension: 32, child: IconButton( + tooltip: website, icon: const Icon( Icons.open_in_new_outlined, size: 16, diff --git a/lib/widgets/navigation_rail.dart b/lib/widgets/navigation_rail.dart index 3b0eea3e..7d3966cb 100644 --- a/lib/widgets/navigation_rail.dart +++ b/lib/widgets/navigation_rail.dart @@ -54,11 +54,11 @@ class SpacesNavigationRail extends StatelessWidget { isSelected: activeSpaceId == null, onTap: onGoToChats, icon: const Padding( - padding: EdgeInsets.all(10.0), + padding: EdgeInsets.all(8.0), child: Icon(Icons.forum_outlined), ), selectedIcon: const Padding( - padding: EdgeInsets.all(10.0), + padding: EdgeInsets.all(8.0), child: Icon(Icons.forum), ), toolTip: L10n.of(context).chats, @@ -71,7 +71,7 @@ class SpacesNavigationRail extends StatelessWidget { isSelected: false, onTap: () => context.go('/rooms/newspace'), icon: const Padding( - padding: EdgeInsets.all(8.0), + padding: EdgeInsets.all(6.0), child: Icon(Icons.add), ), toolTip: L10n.of(context).createNewSpace, @@ -94,6 +94,7 @@ class SpacesNavigationRail extends StatelessWidget { icon: Avatar( mxContent: allSpaces[i].avatar, name: displayname, + size: 36, shapeBorder: RoundedSuperellipseBorder( side: BorderSide( width: 1, From 0a59598188d7a96dae0993311b4fb2dd84b3b358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Sat, 28 Mar 2026 15:45:47 +0100 Subject: [PATCH 17/75] refactor: Bring back notification web sound but make configurable --- lib/config/setting_keys.dart | 3 ++- lib/l10n/intl_en.arb | 3 ++- .../settings_notifications_view.dart | 8 ++++++++ lib/widgets/local_notifications_extension.dart | 6 ++++++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/config/setting_keys.dart b/lib/config/setting_keys.dart index ea4d66f5..4caf8aa0 100644 --- a/lib/config/setting_keys.dart +++ b/lib/config/setting_keys.dart @@ -68,7 +68,8 @@ enum AppSettings { tos('chat.fluffy.tos_url', 'https://fluffychat.im/en/tos'), sendTimelineEventTimeout('chat.fluffy.send_timeline_event_timeout', 15), lastSeenSupportBanner('chat.fluffy.last_seen_support_banner', 0), - supportBannerOptOut('chat.fluffy.support_banner_opt_out', false); + supportBannerOptOut('chat.fluffy.support_banner_opt_out', false), + webNotificationSound('chat.fluffy.web_notification_sound', true); final String key; final T defaultValue; diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index dfc8570f..159750d1 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -2797,5 +2797,6 @@ "startVideoCall": "Start video call", "joinVoiceCall": "Join voice call", "joinVideoCall": "Join video call", - "live": "Live" + "live": "Live", + "playSoundOnNotification": "Play sound on notification" } \ No newline at end of file diff --git a/lib/pages/settings_notifications/settings_notifications_view.dart b/lib/pages/settings_notifications/settings_notifications_view.dart index 5464b724..5911306f 100644 --- a/lib/pages/settings_notifications/settings_notifications_view.dart +++ b/lib/pages/settings_notifications/settings_notifications_view.dart @@ -1,7 +1,10 @@ +import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/settings_notifications/push_rule_extensions.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; +import 'package:fluffychat/widgets/settings_switch_list_tile.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; @@ -47,6 +50,11 @@ class SettingsNotificationsView extends StatelessWidget { return SelectionArea( child: Column( children: [ + if (kIsWeb) + SettingsSwitchListTile.adaptive( + title: L10n.of(context).playSoundOnNotification, + setting: AppSettings.webNotificationSound, + ), if (pushRules != null) for (final category in pushCategories) ...[ ListTile( diff --git a/lib/widgets/local_notifications_extension.dart b/lib/widgets/local_notifications_extension.dart index cf97ad50..b6e50eef 100644 --- a/lib/widgets/local_notifications_extension.dart +++ b/lib/widgets/local_notifications_extension.dart @@ -16,6 +16,10 @@ import 'package:matrix/matrix.dart'; import 'package:universal_html/html.dart' as html; extension LocalNotificationsExtension on MatrixState { + static final html.AudioElement _audioPlayer = html.AudioElement() + ..src = 'assets/assets/sounds/notification.ogg' + ..load(); + Future showLocalNotification(Event event) async { final l10n = L10n.of(context); final roomId = event.room.id; @@ -69,6 +73,8 @@ extension LocalNotificationsExtension on MatrixState { ); } + if (AppSettings.webNotificationSound.value) _audioPlayer.play(); + html.Notification( title, body: body, From 2c161ce7daed19076a3ab8100d66e16a6332cc14 Mon Sep 17 00:00:00 2001 From: Lennart Ochel Date: Sat, 28 Mar 2026 13:26:44 +0100 Subject: [PATCH 18/75] chore(translations): Translated using Weblate (Swedish) Currently translated at 85.5% (653 of 763 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/sv/ --- lib/l10n/intl_sv.arb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/l10n/intl_sv.arb b/lib/l10n/intl_sv.arb index 82fb566c..a8e64c0d 100644 --- a/lib/l10n/intl_sv.arb +++ b/lib/l10n/intl_sv.arb @@ -72,7 +72,7 @@ "type": "String", "placeholders": {} }, - "areGuestsAllowedToJoin": "Får gästanvändare gå med", + "areGuestsAllowedToJoin": "Får gästanvändare gå med?", "@areGuestsAllowedToJoin": { "type": "String", "placeholders": {} @@ -2040,7 +2040,7 @@ }, "screenSharingTitle": "skärmdelning", "noKeyForThisMessage": "Detta kan hända om meddelandet skickades innan du loggade in på ditt konto i den här enheten.\n\nDet kan också vara så att avsändaren har blockerat din enhet eller att något gick fel med internetanslutningen.\n\nKan du läsa meddelandet i en annan session? I sådana fall kan du överföra meddelandet från den sessionen! Gå till Inställningar > Enhet och säkerställ att dina enheter har verifierat varandra. När du öppnar rummet nästa gång och båda sessionerna är i förgrunden, så kommer nycklarna att överföras automatiskt.\n\nVill du inte förlora nycklarna vid utloggning eller när du byter enhet? Säkerställ att du har aktiverat säkerhetskopiering för chatten i inställningarna.", - "fileIsTooBigForServer": "Gick inte att skicka! Servern stödjer endast bilagor upp till{max}.", + "fileIsTooBigForServer": "Gick inte att skicka! Servern stödjer endast bilagor upp till {max}.", "deviceKeys": "Enhetsnycklar:", "commandHint_googly": "Skicka några googly ögon", "commandHint_cuddle": "Skicka en omfamning", @@ -2333,7 +2333,7 @@ "stickers": "Klistermärken", "discover": "Upptäck", "ignoreUser": "Ignorera användare", - "aboutHomeserver": "Om{homeserver}", + "aboutHomeserver": "Om {homeserver}", "@aboutHomeserver": { "type": "String", "placeholders": { @@ -2395,7 +2395,7 @@ } } }, - "invitedBy": "📩Inbjuden av{user}", + "invitedBy": "📩Inbjuden av {user}", "@invitedBy": { "placeholders": { "user": { @@ -2644,4 +2644,4 @@ "logs": "Loggar", "signIn": "Logga in", "createNewAccount": "Skapa nytt konto" -} \ No newline at end of file +} From c994744bdc320dd594a7a9e5a944923c419649b5 Mon Sep 17 00:00:00 2001 From: fadelkon Date: Fri, 27 Mar 2026 15:57:24 +0100 Subject: [PATCH 19/75] chore(translations): Translated using Weblate (Catalan) Currently translated at 100.0% (763 of 763 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ca/ --- lib/l10n/intl_ca.arb | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/l10n/intl_ca.arb b/lib/l10n/intl_ca.arb index e2535014..d4fd944f 100644 --- a/lib/l10n/intl_ca.arb +++ b/lib/l10n/intl_ca.arb @@ -72,7 +72,7 @@ "type": "String", "placeholders": {} }, - "areGuestsAllowedToJoin": "Es pot entrar al xat com a convidadi", + "areGuestsAllowedToJoin": "Es pot entrar al xat com a convidadi?", "@areGuestsAllowedToJoin": { "type": "String", "placeholders": {} @@ -2774,5 +2774,38 @@ "signUpGreeting": "El FluffyChat és descentralitzat! Tria un servidor on vulguis crear-t'hi un compte, i som-hi!", "signInGreeting": "Si ja tens un compte a Matrix, benvingudi! Tria el teu servidor i inicia-hi sessió.", "appIntro": "Pots xatejar amb lis tevis amiguis amb Fluffychat. És una app de missatgeria [matrix] descentralitzada! Llegeix-ne més a https://matrix.org si vols, o inicia sessió.", - "theProcessWasCanceled": "S'ha canceŀlat el procés." -} \ No newline at end of file + "theProcessWasCanceled": "S'ha canceŀlat el procés.", + "join": "Entra", + "searchOrEnterHomeserverAddress": "Cerca o introdueix l'adreça del teu servidor", + "matrixId": "ID de Matrix", + "setPowerLevel": "Concedeix permisos", + "makeModerator": "Fes moderadori", + "makeAdmin": "Fes admin", + "removeModeratorRights": "Treu els drets de moderadori", + "removeAdminRights": "Treu els drets d'admin", + "powerLevel": "Nivell de permisos", + "setPowerLevelDescription": "Els nivells de permisos defineixen què pot fer uni membre d'aquesta sala, i es defineix per un número entre 0 i 100.", + "owner": "Propietàriï", + "mute": "Silencia", + "@mute": { + "description": "This should be a very short string because there is not much space in the button!" + }, + "createNewChat": "Crea un nou xat", + "reset": "Reseteja", + "supportFluffyChat": "Dona suport a FluffyChat", + "support": "Aporta", + "fluffyChatSupportBannerMessage": "El FluffyChat necessita la teva ajuda!\n❤️❤️❤️\nFluffyChat serà sempre gratuït, però el seu desenvolupament i allotjament costa diners.\nEl futur del projecte depèn del suport de persones com tu.", + "skipSupportingFluffyChat": "Ignora el suport a FluffyChat", + "iDoNotWantToSupport": "No vull donar suport", + "iAlreadySupportFluffyChat": "Ja estic donant-hi suport", + "setLowPriority": "Estableix una prioritat baixa", + "unsetLowPriority": "Restableix la prioritat", + "removeCallFromChat": "Treu la trucada del xat", + "removeCallFromChatDescription": "Vols treure la trucada del xat per a totis lis membres?", + "removeCallForEveryone": "Treu la trucada per tothom", + "startVoiceCall": "Inicia una trucada", + "startVideoCall": "Fes una videotrucada", + "joinVoiceCall": "Fica't a la trucada", + "joinVideoCall": "Fica't a la videotrucada", + "live": "En directe" +} From 30455601e9be463eb3c82f07181848996c40fab5 Mon Sep 17 00:00:00 2001 From: XblateX Date: Sat, 28 Mar 2026 00:16:29 +0100 Subject: [PATCH 20/75] chore(translations): Translated using Weblate (Ukrainian) Currently translated at 96.0% (733 of 763 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/ --- lib/l10n/intl_uk.arb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/l10n/intl_uk.arb b/lib/l10n/intl_uk.arb index 72b90b9a..bd304a2a 100644 --- a/lib/l10n/intl_uk.arb +++ b/lib/l10n/intl_uk.arb @@ -2775,5 +2775,6 @@ "signUpGreeting": "FluffyChat децентралізований! Виберіть сервер, на якому ви хочете створити свій обліковий запис, і почнімо!", "signInGreeting": "Ви вже маєте обліковий запис у Matrix? Ласкаво просимо! Виберіть свій домашній сервер і ввійдіть.", "appIntro": "За допомогою FluffyChat ви можете спілкуватися зі своїми друзями. Це безпечний децентралізований месенджер [matrix]! Дізнайтеся більше на сайті https://matrix.org або просто зареєструйтеся.", - "theProcessWasCanceled": "Процес скасовано." -} \ No newline at end of file + "theProcessWasCanceled": "Процес скасовано.", + "join": "Приєднатись" +} From 93c8339e1f62da21ed9b507f5a00f6e1ac9cd6da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sat, 28 Mar 2026 18:06:00 +0100 Subject: [PATCH 21/75] chore(translations): Translated using Weblate (Estonian) Currently translated at 100.0% (764 of 764 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/ --- lib/l10n/intl_et.arb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/l10n/intl_et.arb b/lib/l10n/intl_et.arb index 33c9f309..b8dea5f3 100644 --- a/lib/l10n/intl_et.arb +++ b/lib/l10n/intl_et.arb @@ -2808,5 +2808,6 @@ "startVideoCall": "Algata videokõne", "joinVoiceCall": "Liitu häälkõnega", "joinVideoCall": "Liitu videokõnega", - "live": "Reaalajas" + "live": "Reaalajas", + "playSoundOnNotification": "Lisa teavitusele helimärguanne" } From 5aa7ff8dfaf8f34c130e041deb5b0e392d9422b9 Mon Sep 17 00:00:00 2001 From: Frank Paul Silye Date: Sat, 28 Mar 2026 18:54:54 +0100 Subject: [PATCH 22/75] =?UTF-8?q?chore(translations):=20Translated=20using?= =?UTF-8?q?=20Weblate=20(Norwegian=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (764 of 764 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/nb_NO/ --- lib/l10n/intl_nb.arb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/l10n/intl_nb.arb b/lib/l10n/intl_nb.arb index 92e65d4e..31402c18 100644 --- a/lib/l10n/intl_nb.arb +++ b/lib/l10n/intl_nb.arb @@ -2815,5 +2815,6 @@ "startVideoCall": "Start videosamtale", "joinVoiceCall": "Bli med i lydsamtale", "joinVideoCall": "Bli med i videosamtale", - "live": "Direkte" + "live": "Direkte", + "playSoundOnNotification": "Spill av lyd ved varsling" } From 30addb944fab54e6b500d9ff2912a5f577e39fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E7=8E=8B=E5=8F=AB=E6=88=91=E6=9D=A5=E5=B7=A1?= =?UTF-8?q?=E5=B1=B1?= Date: Sat, 28 Mar 2026 16:26:56 +0100 Subject: [PATCH 23/75] chore(translations): Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (764 of 764 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/ --- lib/l10n/intl_zh.arb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 0a678588..65e94285 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -2808,5 +2808,6 @@ "startVideoCall": "开始视频通话", "joinVoiceCall": "加入语音通话", "joinVideoCall": "加入视频通话", - "live": "实时" + "live": "实时", + "playSoundOnNotification": "播放通知声音" } From 53e20515829f54d1e34dc341ba4b2274b22ffe7e Mon Sep 17 00:00:00 2001 From: fadelkon Date: Sat, 28 Mar 2026 18:20:35 +0100 Subject: [PATCH 24/75] chore(translations): Translated using Weblate (Catalan) Currently translated at 100.0% (764 of 764 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ca/ --- lib/l10n/intl_ca.arb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/l10n/intl_ca.arb b/lib/l10n/intl_ca.arb index d4fd944f..0182ccfc 100644 --- a/lib/l10n/intl_ca.arb +++ b/lib/l10n/intl_ca.arb @@ -1781,7 +1781,7 @@ "type": "String", "description": "Usage hint for the command /clearcache" }, - "commandHint_join": "Uneix-te a la sala", + "commandHint_join": "Uneix-te a la sala indicada", "@commandHint_join": { "type": "String", "description": "Usage hint for the command /join" @@ -2754,7 +2754,7 @@ } }, "federationBaseUrl": "URL base de federació", - "clientWellKnownInformation": "Informació de", + "clientWellKnownInformation": "Informació coneguda del client:", "baseUrl": "URL base", "identityServer": "Servidor d'identitats:", "versionWithNumber": "Versió: {version}", @@ -2807,5 +2807,6 @@ "startVideoCall": "Fes una videotrucada", "joinVoiceCall": "Fica't a la trucada", "joinVideoCall": "Fica't a la videotrucada", - "live": "En directe" + "live": "En directe", + "playSoundOnNotification": "Notificacions sonores" } From f0545aaa85c96c331b3f273052cdab469a04965f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Mon, 30 Mar 2026 09:09:31 +0200 Subject: [PATCH 25/75] fix: Send on enter broken on iOS --- lib/pages/chat/input_bar.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart index a8944107..22672b51 100644 --- a/lib/pages/chat/input_bar.dart +++ b/lib/pages/chat/input_bar.dart @@ -392,6 +392,10 @@ class InputBar extends StatelessWidget { controller: controller, focusNode: focusNode, readOnly: readOnly, + onEditingComplete: () { + // To not lose focus on iOS: + // https://github.com/krille-chan/fluffychat/issues/2784 + }, contextMenuBuilder: (c, e) => MarkdownContextBuilder( editableTextState: e, controller: controller, From 6b76b6de3cd49a0819db029646abe61cb44ce0d6 Mon Sep 17 00:00:00 2001 From: xabirequejo Date: Mon, 30 Mar 2026 09:27:19 +0200 Subject: [PATCH 26/75] chore(translations): Translated using Weblate (Basque) Currently translated at 99.8% (763 of 764 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/ --- lib/l10n/intl_eu.arb | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/l10n/intl_eu.arb b/lib/l10n/intl_eu.arb index 15766e64..c4c45cb2 100644 --- a/lib/l10n/intl_eu.arb +++ b/lib/l10n/intl_eu.arb @@ -2792,5 +2792,22 @@ "description": "This should be a very short string because there is not much space in the button!" }, "createNewChat": "Sortu txat berria", - "reset": "Berrezarri" -} \ No newline at end of file + "reset": "Berrezarri", + "supportFluffyChat": "Eman babesa FluffyChat-i", + "support": "Lagundu", + "fluffyChatSupportBannerMessage": "FluffyChat-ek ZURE laguntza behar du!\n❤️❤️❤️\nFluffyChat beti izango da doakoa, baina garapenak eta ostatatzeak dirua eskatzen du.\nProiektuaren etorkizuna zu bezalako pertsonen babesaren menpe dago.", + "iDoNotWantToSupport": "Ez diot babesik eman nahi", + "iAlreadySupportFluffyChat": "Laguntzen ari naiz dagoeneko FluffyChat", + "setLowPriority": "Ezarri lehentasun baxua", + "unsetLowPriority": "Kendu lehentasun baxua", + "removeCallFromChat": "Kendu deia txatetik", + "removeCallFromChatDescription": "Kide guztientzat kendu nahi al duzu deia txatetik?", + "removeCallForEveryone": "Kendu deia guztientzat", + "startVoiceCall": "Hasi ahots-deia", + "startVideoCall": "Hasi bideo-deia", + "joinVoiceCall": "Batu ahots-deira", + "joinVideoCall": "Batu bideo-deira", + "live": "Zuzenean", + "playSoundOnNotification": "Jo soinua jakinarazpenekin", + "skipSupportingFluffyChat": "Muzin egin FluffyChat-en laguntza eskaerari" +} From 383d7085006161a4df291d0840bca4825bcb0d84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Mon, 30 Mar 2026 10:22:15 +0200 Subject: [PATCH 27/75] fix: Open deep link on android --- lib/config/routes.dart | 4 ++-- lib/pages/chat_list/chat_list.dart | 8 +++----- lib/widgets/fluffy_chat_app.dart | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/config/routes.dart b/lib/config/routes.dart index c6c683eb..0063c35e 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -172,8 +172,8 @@ abstract class AppRoutes { context, state, NewPrivateChat( - key: ValueKey('new_chat_${state.uri.query}'), - deeplink: state.uri.queryParameters['deeplink'], + key: ValueKey('new_chat_${state.uri.fragment}'), + deeplink: state.uri.fragment, ), ), redirect: loggedOutRedirect, diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 9ac99e30..1baffc5e 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:developer'; import 'package:cross_file/cross_file.dart'; import 'package:fluffychat/config/app_config.dart'; @@ -311,11 +310,10 @@ class ChatListController extends State String? get activeChat => widget.activeChat; void _processIncomingSharedMedia(List files) { + files.removeWhere( + (file) => file.path.startsWith(AppConfig.deepLinkPrefix) == true, + ); if (files.isEmpty) return; - inspect(files); - if (files.singleOrNull?.path.startsWith(AppConfig.deepLinkPrefix) == true) { - return; - } showScaffoldDialog( context: context, diff --git a/lib/widgets/fluffy_chat_app.dart b/lib/widgets/fluffy_chat_app.dart index 79047cc9..3cdae9f7 100644 --- a/lib/widgets/fluffy_chat_app.dart +++ b/lib/widgets/fluffy_chat_app.dart @@ -43,7 +43,7 @@ class FluffyChatApp extends StatelessWidget { // Pass deep links to app: if (state.uri.toString().startsWith(AppConfig.deepLinkPrefix)) { - return '/rooms/newprivatechat?deeplink=${state.uri}'; + return '/rooms/newprivatechat#${state.uri}'; } return null; }, From c35e3f8b2b0b6b1c83b426b40b05174406c780c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Mon, 30 Mar 2026 13:22:26 +0200 Subject: [PATCH 28/75] fix: DMs in spaces are displayed as empty chats --- lib/pages/chat_list/space_view.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index bc4804ac..8df0f52b 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -527,14 +527,16 @@ class _SpaceViewState extends State { ); } final item = _discoveredChildren[i]; + var joinedRoom = room.client.getRoomById(item.roomId); final displayname = item.name ?? item.canonicalAlias ?? + joinedRoom?.getLocalizedDisplayname() ?? L10n.of(context).emptyChat; + final avatarUrl = item.avatarUrl ?? joinedRoom?.avatar; if (!displayname.toLowerCase().contains(filter)) { return const SizedBox.shrink(); } - var joinedRoom = room.client.getRoomById(item.roomId); if (joinedRoom?.membership == Membership.leave) { joinedRoom = null; } @@ -598,7 +600,7 @@ class _SpaceViewState extends State { ) : Avatar( size: avatarSize, - mxContent: item.avatarUrl, + mxContent: avatarUrl, name: '#', backgroundColor: theme.colorScheme.surfaceContainer, From 6c461ba5f31ca8c3619e995ec24fccec964ef959 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 22:17:32 +0000 Subject: [PATCH 29/75] build: (deps): bump flutter_webrtc from 1.3.1 to 1.4.1 Bumps [flutter_webrtc](https://github.com/cloudwebrtc/flutter-webrtc) from 1.3.1 to 1.4.1. - [Release notes](https://github.com/cloudwebrtc/flutter-webrtc/releases) - [Changelog](https://github.com/flutter-webrtc/flutter-webrtc/blob/main/CHANGELOG.md) - [Commits](https://github.com/cloudwebrtc/flutter-webrtc/compare/v1.3.1...v1.4.1) --- updated-dependencies: - dependency-name: flutter_webrtc dependency-version: 1.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pubspec.lock | 8 ++++---- pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 789b0182..ef5a1a19 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -285,10 +285,10 @@ packages: dependency: transitive description: name: dart_webrtc - sha256: "4ed7b9fa9924e5a81eb39271e2c2356739dd1039d60a13b86ba6c5f448625086" + sha256: f6d615bddea5e458ce180a914f3055c234ffb52fb7397a51b3491e76d6d7edb2 url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.8.1" dbus: dependency: transitive description: @@ -662,10 +662,10 @@ packages: dependency: "direct main" description: name: flutter_webrtc - sha256: c549ea8ffb20167110ad0a28e5f17a2650b5bea8837d984898cd9b0ffd5fa78b + sha256: c7b0a67ca2c878575fc5c146d801cd874f58f5f1ef5fa6e8eb0c93d413beb948 url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.4.1" frontend_server_client: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a8ab2814..44bb5d60 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,7 +37,7 @@ dependencies: flutter_shortcuts_new: ^2.0.0 flutter_vodozemac: ^0.5.0 flutter_web_auth_2: ^5.0.1 - flutter_webrtc: ^1.3.1 + flutter_webrtc: ^1.4.1 geolocator: ^14.0.2 go_router: ^17.1.0 handy_window: ^0.4.2 From 5c03bfa16cd9562335c20b9340f56bce7b6cbac4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 22:17:37 +0000 Subject: [PATCH 30/75] build: (deps): bump package_info_plus from 9.0.0 to 9.0.1 Bumps [package_info_plus](https://github.com/fluttercommunity/plus_plugins/tree/main/packages/package_info_plus) from 9.0.0 to 9.0.1. - [Release notes](https://github.com/fluttercommunity/plus_plugins/releases) - [Commits](https://github.com/fluttercommunity/plus_plugins/commits/package_info_plus-v9.0.1/packages/package_info_plus) --- updated-dependencies: - dependency-name: package_info_plus dependency-version: 9.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 789b0182..ff0a5cdc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1160,10 +1160,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d + sha256: "468c26b4254ab01979fa5e4a98cb343ea3631b9acee6f21028997419a80e1a20" url: "https://pub.dev" source: hosted - version: "9.0.0" + version: "9.0.1" package_info_plus_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a8ab2814..78d415c7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,7 +53,7 @@ dependencies: mime: ^2.0.0 native_imaging: ^0.4.0 opus_caf_converter_dart: ^1.0.1 - package_info_plus: ^9.0.0 + package_info_plus: ^9.0.1 particles_network: ^1.9.3 path: ^1.9.0 path_provider: ^2.1.2 From afaf61bbeb218db6108628259cbcd5211bf98b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Tue, 31 Mar 2026 07:24:57 +0200 Subject: [PATCH 31/75] chore: Highlight unread chats at timestamp --- lib/pages/chat_list/chat_list_item.dart | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index 3cc410e8..624c59d5 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -219,7 +219,15 @@ class ChatListItem extends StatelessWidget { room.latestEventReceivedTime.localizedTimeShort( context, ), - style: TextStyle(fontSize: 11), + style: TextStyle( + fontSize: 11, + fontWeight: room.hasNewMessages + ? FontWeight.bold + : null, + color: hasNotifications + ? theme.colorScheme.primary + : null, + ), ), ), ], From 6bc233ceeee6afac500b48528f991ff610bb28bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Tue, 31 Mar 2026 07:51:48 +0200 Subject: [PATCH 32/75] feat: Persist chat filters --- lib/config/setting_keys.dart | 3 ++- lib/pages/chat_list/chat_list.dart | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/config/setting_keys.dart b/lib/config/setting_keys.dart index 4caf8aa0..1b489241 100644 --- a/lib/config/setting_keys.dart +++ b/lib/config/setting_keys.dart @@ -69,7 +69,8 @@ enum AppSettings { sendTimelineEventTimeout('chat.fluffy.send_timeline_event_timeout', 15), lastSeenSupportBanner('chat.fluffy.last_seen_support_banner', 0), supportBannerOptOut('chat.fluffy.support_banner_opt_out', false), - webNotificationSound('chat.fluffy.web_notification_sound', true); + webNotificationSound('chat.fluffy.web_notification_sound', true), + chatFilter('chat.fluffy.chat_filter', 'allChats'); final String key; final T defaultValue; diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 1baffc5e..fe5dd7e9 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:cross_file/cross_file.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/l10n/l10n.dart'; @@ -359,7 +360,11 @@ class ChatListController extends State @override void initState() { - activeFilter = ActiveFilter.allChats; + activeFilter = + ActiveFilter.values.singleWhereOrNull( + (filter) => AppSettings.chatFilter.value == filter.name, + ) ?? + ActiveFilter.allChats; _initReceiveSharingIntent(); _activeSpaceId = widget.activeSpace; @@ -857,6 +862,7 @@ class ChatListController extends State setState(() { activeFilter = filter; }); + AppSettings.chatFilter.setItem(filter.name); } void setActiveClient(Client client) { From 954797e4f4b1db890945a4bd645ad4f4f2bd3836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jos=C3=A9=20m=2E?= Date: Tue, 31 Mar 2026 05:44:34 +0200 Subject: [PATCH 33/75] chore(translations): Translated using Weblate (Galician) Currently translated at 100.0% (764 of 764 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/ --- lib/l10n/intl_gl.arb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/l10n/intl_gl.arb b/lib/l10n/intl_gl.arb index e6bf530b..cc703e15 100644 --- a/lib/l10n/intl_gl.arb +++ b/lib/l10n/intl_gl.arb @@ -2808,5 +2808,6 @@ "startVideoCall": "Iniciar chamada de vídeo", "joinVoiceCall": "Unirse á chamada de voz", "joinVideoCall": "Unirse á chamada de vídeo", - "live": "En directo" + "live": "En directo", + "playSoundOnNotification": "Reproducir son coas notificacións" } From 5af75321593fbc87e89ab9b61d6a74d4a7490673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aindri=C3=BA=20Mac=20Giolla=20Eoin?= Date: Mon, 30 Mar 2026 13:35:37 +0200 Subject: [PATCH 34/75] chore(translations): Translated using Weblate (Irish) Currently translated at 100.0% (764 of 764 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ga/ --- lib/l10n/intl_ga.arb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/l10n/intl_ga.arb b/lib/l10n/intl_ga.arb index 018e2283..c70490ed 100644 --- a/lib/l10n/intl_ga.arb +++ b/lib/l10n/intl_ga.arb @@ -2814,5 +2814,6 @@ "startVideoCall": "Tosaigh glao físe", "joinVoiceCall": "Glac páirt i nglao gutha", "joinVideoCall": "Glac páirt i nglao físe", - "live": "Beo" + "live": "Beo", + "playSoundOnNotification": "Seinn fuaim ar fhógra" } From 6da3627fabd27089541a93cf26ed6422a31bcc62 Mon Sep 17 00:00:00 2001 From: Jelv Date: Tue, 31 Mar 2026 10:29:52 +0200 Subject: [PATCH 35/75] chore(translations): Translated using Weblate (Dutch) Currently translated at 100.0% (764 of 764 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/nl/ --- lib/l10n/intl_nl.arb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/l10n/intl_nl.arb b/lib/l10n/intl_nl.arb index 18ed4ccd..3e785bb8 100644 --- a/lib/l10n/intl_nl.arb +++ b/lib/l10n/intl_nl.arb @@ -2807,5 +2807,6 @@ "startVoiceCall": "Start audio-gesprek", "startVideoCall": "Start video-gesprek", "joinVoiceCall": "Audio-gesprek opnemen", - "joinVideoCall": "Deelnemen aan video-gesprek" + "joinVideoCall": "Deelnemen aan video-gesprek", + "playSoundOnNotification": "Melding geluid afspelen" } From 2407dca80cae071bb30c34c228f4938870537dce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 21:35:23 +0000 Subject: [PATCH 36/75] build: (deps): bump async from 2.13.0 to 2.13.1 Bumps [async](https://github.com/dart-lang/core/tree/main/pkgs) from 2.13.0 to 2.13.1. - [Release notes](https://github.com/dart-lang/core/releases) - [Commits](https://github.com/dart-lang/core/commits/async-v2.13.1/pkgs) --- updated-dependencies: - dependency-name: async dependency-version: 2.13.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 6c90f1e9..335ec152 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -53,10 +53,10 @@ packages: dependency: "direct main" description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.13.1" audio_session: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6ebec2bd..4cb60b6e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ environment: dependencies: archive: ^4.0.7 - async: ^2.11.0 + async: ^2.13.1 badges: ^3.1.2 blurhash_dart: ^1.2.1 chewie: ^1.13.0 From 2e875dd3c580afeed570c4c27c2a9aa7816b049a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 21:35:36 +0000 Subject: [PATCH 37/75] build: (deps): bump shared_preferences from 2.5.4 to 2.5.5 Bumps [shared_preferences](https://github.com/flutter/packages/tree/main/packages/shared_preferences) from 2.5.4 to 2.5.5. - [Commits](https://github.com/flutter/packages/commits/shared_preferences-v2.5.5/packages/shared_preferences) --- updated-dependencies: - dependency-name: shared_preferences dependency-version: 2.5.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 6c90f1e9..2fe37e0e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1552,10 +1552,10 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.5" shared_preferences_android: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6ebec2bd..2a649367 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -66,7 +66,7 @@ dependencies: record: ^6.2.0 scroll_to_index: ^3.0.1 share_plus: ^12.0.1 - shared_preferences: ^2.5.4 # Pinned because https://github.com/flutter/flutter/issues/118401 + shared_preferences: ^2.5.5 # Pinned because https://github.com/flutter/flutter/issues/118401 slugify: ^2.0.0 sqflite_common_ffi: ^2.3.7+1 sqlcipher_flutter_libs: ^0.6.8 From 101578bc9d393478a01a77baa29588efc71deae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Wed, 1 Apr 2026 08:24:59 +0200 Subject: [PATCH 38/75] feat: Implement room tags --- lib/l10n/intl_en.arb | 6 +- lib/pages/chat_list/chat_list.dart | 136 ++++++++++++++++++++++-- lib/pages/chat_list/chat_list_body.dart | 61 ++++++----- 3 files changed, 165 insertions(+), 38 deletions(-) diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 159750d1..75a720c2 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -2798,5 +2798,9 @@ "joinVoiceCall": "Join voice call", "joinVideoCall": "Join video call", "live": "Live", - "playSoundOnNotification": "Play sound on notification" + "playSoundOnNotification": "Play sound on notification", + "addTag": "Add tag", + "removeTag": "Remove tag", + "tagName": "Tag name", + "createNewTag": "Create new tag" } \ No newline at end of file diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index fe5dd7e9..23ca65fe 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -30,7 +30,7 @@ import '../../config/setting_keys.dart'; import '../../utils/url_launcher.dart'; import '../../widgets/matrix.dart'; -enum ActiveFilter { allChats, messages, groups, unread, spaces } +enum ActiveFilter { allChats, messages, groups, unread, spaces, tag } extension LocalizedActiveFilter on ActiveFilter { String toLocalizedString(BuildContext context) { @@ -45,6 +45,8 @@ extension LocalizedActiveFilter on ActiveFilter { return L10n.of(context).groups; case ActiveFilter.spaces: return L10n.of(context).spaces; + case ActiveFilter.tag: + throw 'Tags should not directly be displayed!'; } } } @@ -73,6 +75,7 @@ class ChatListController extends State StreamSubscription? _intentFileStreamSubscription; late ActiveFilter activeFilter; + String? activeTag; String? _activeSpaceId; String? get activeSpaceId => _activeSpaceId; @@ -141,6 +144,8 @@ class ChatListController extends State return (room) => room.isUnreadOrInvited; case ActiveFilter.spaces: return (room) => room.isSpace; + case ActiveFilter.tag: + return (room) => room.tags.keys.contains(activeTag); } } @@ -358,13 +363,10 @@ class ChatListController extends State } } + StreamSubscription? _onRoomTagUpdate; + @override void initState() { - activeFilter = - ActiveFilter.values.singleWhereOrNull( - (filter) => AppSettings.chatFilter.value == filter.name, - ) ?? - ActiveFilter.allChats; _initReceiveSharingIntent(); _activeSpaceId = widget.activeSpace; @@ -387,6 +389,32 @@ class ChatListController extends State ); }); + _updateRoomTags(); + _onRoomTagUpdate = Matrix.of(context).client.onSync.stream + .where( + (syncUpdate) => + syncUpdate.rooms?.join?.values.any( + (roomUpdate) => + roomUpdate.accountData?.any( + (accountData) => accountData.type == 'm.tag', + ) ?? + false, + ) ?? + false, + ) + .listen(_updateRoomTags); + + if (roomTags.containsKey(AppSettings.chatFilter.value)) { + activeFilter = ActiveFilter.tag; + activeTag = AppSettings.chatFilter.value; + } else { + activeFilter = + ActiveFilter.values.singleWhereOrNull( + (filter) => AppSettings.chatFilter.value == filter.name, + ) ?? + ActiveFilter.allChats; + } + super.initState(); } @@ -394,6 +422,7 @@ class ChatListController extends State void dispose() { _intentDataStreamSubscription?.cancel(); _intentFileStreamSubscription?.cancel(); + _onRoomTagUpdate?.cancel(); scrollController.removeListener(_onScroll); super.dispose(); } @@ -622,6 +651,30 @@ class ChatListController extends State ], ), ), + if (activeTag == null) + PopupMenuItem( + value: ChatContextAction.addTag, + child: Row( + mainAxisSize: .min, + children: [ + Icon(Icons.bookmark_add_outlined), + const SizedBox(width: 12), + Text(L10n.of(context).addTag), + ], + ), + ) + else + PopupMenuItem( + value: ChatContextAction.removeTag, + child: Row( + mainAxisSize: .min, + children: [ + Icon(Icons.bookmark_remove_outlined), + const SizedBox(width: 12), + Text(L10n.of(context).removeTag), + ], + ), + ), if (spacesWithPowerLevels.isNotEmpty) PopupMenuItem( value: ChatContextAction.addToSpace, @@ -762,9 +815,68 @@ class ChatListController extends State future: () => room.setLowPriority(!room.isLowPriority), ); return; + case ChatContextAction.addTag: + final existingTags = List.of(roomTags.keys); + existingTags.removeWhere(room.tags.containsKey); + String? tag; + if (existingTags.isNotEmpty) { + tag = await showModalActionPopup( + context: context, + actions: [ + ...existingTags.map((tag) { + final displayTag = tag.replaceFirst('u.', ''); + return AdaptiveModalAction( + label: displayTag, + value: displayTag, + ); + }), + AdaptiveModalAction( + label: L10n.of(context).createNewTag, + value: null, + ), + ], + ); + if (!mounted) return; + } + tag ??= await showTextInputDialog( + context: context, + title: L10n.of(context).addTag, + hintText: L10n.of(context).tagName, + ); + final newTag = tag; + if (!mounted) return; + if (newTag == null) return; + await showFutureLoadingDialog( + context: context, + future: () => room.addTag('u.$newTag'), + ); + return; + case ChatContextAction.removeTag: + await showFutureLoadingDialog( + context: context, + future: () => room.removeTag(activeTag!), + ); + return; } } + Map roomTags = {}; + + void _updateRoomTags([_]) { + roomTags.clear(); + for (final room in Matrix.of(context).client.rooms) { + for (final tag in room.tags.keys) { + if (tag.startsWith('u.')) roomTags[tag] = (roomTags[tag] ?? 0) + 1; + } + } + setState(() { + if (activeTag != null && !roomTags.keys.contains(activeTag)) { + activeTag = null; + activeFilter = ActiveFilter.allChats; + } + }); + } + Future dismissStatusList() async { final result = await showOkCancelAlertDialog( title: L10n.of(context).hidePresences, @@ -858,11 +970,17 @@ class ChatListController extends State } } - void setActiveFilter(ActiveFilter filter) { + void setActiveFilter(ActiveFilter filter, String? tag) { + if (filter == ActiveFilter.tag && tag == null) { + throw ('Must set a tag when setting filter to tags!'); + } setState(() { + activeTag = tag; activeFilter = filter; }); - AppSettings.chatFilter.setItem(filter.name); + AppSettings.chatFilter.setItem( + filter == ActiveFilter.tag ? tag! : filter.name, + ); } void setActiveClient(Client client) { @@ -978,6 +1096,8 @@ enum ChatContextAction { goToSpace, favorite, lowPriority, + addTag, + removeTag, markUnread, mute, leave, diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index 8489128f..e9de7e2f 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -134,37 +134,40 @@ class ChatListViewBody extends StatelessWidget { padding: const EdgeInsets.all(12.0), shrinkWrap: true, scrollDirection: Axis.horizontal, - children: - [ - ActiveFilter.allChats, - - if (spaces.isNotEmpty && - !AppSettings - .displayNavigationRail - .value && - !FluffyThemes.isColumnMode(context)) - ActiveFilter.spaces, - ActiveFilter.unread, - ActiveFilter.groups, - ActiveFilter.messages, - ] - .map( - (filter) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 4.0, - ), - child: FilterChip( - selected: - filter == controller.activeFilter, - onSelected: (_) => - controller.setActiveFilter(filter), - label: Text( - filter.toLocalizedString(context), - ), + children: [ + ...ActiveFilter.values + .where((filter) => filter != ActiveFilter.tag) + .map( + (filter) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4.0, + ), + child: FilterChip( + selected: filter == controller.activeFilter, + onSelected: (_) => controller + .setActiveFilter(filter, null), + label: Text( + filter.toLocalizedString(context), ), ), - ) - .toList(), + ), + ), + ...controller.roomTags.entries.map( + (entry) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4.0, + ), + child: FilterChip( + selected: entry.key == controller.activeTag, + onSelected: (_) => controller.setActiveFilter( + ActiveFilter.tag, + entry.key, + ), + label: Text(entry.key.replaceFirst('u.', '')), + ), + ), + ), + ], ), ), if (controller.isSearchMode) From fba24b85062db58a9e4d97c49308c8dcd53bbf19 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 02:04:11 +0000 Subject: [PATCH 39/75] build: (deps): bump share_plus from 12.0.1 to 12.0.2 Bumps [share_plus](https://github.com/fluttercommunity/plus_plugins/tree/main/packages/share_plus) from 12.0.1 to 12.0.2. - [Release notes](https://github.com/fluttercommunity/plus_plugins/releases) - [Commits](https://github.com/fluttercommunity/plus_plugins/commits/share_plus-v12.0.2/packages/share_plus) --- updated-dependencies: - dependency-name: share_plus dependency-version: 12.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index c35708a0..19279702 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1536,10 +1536,10 @@ packages: dependency: "direct main" description: name: share_plus - sha256: "14c8860d4de93d3a7e53af51bff479598c4e999605290756bbbe45cf65b37840" + sha256: "223873d106614442ea6f20db5a038685cc5b32a2fba81cdecaefbbae0523f7fa" url: "https://pub.dev" source: hosted - version: "12.0.1" + version: "12.0.2" share_plus_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7b7b1480..3f07b0c7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -65,7 +65,7 @@ dependencies: receive_sharing_intent: ^1.8.1 record: ^6.2.0 scroll_to_index: ^3.0.1 - share_plus: ^12.0.1 + share_plus: ^12.0.2 shared_preferences: ^2.5.5 # Pinned because https://github.com/flutter/flutter/issues/118401 slugify: ^2.0.0 sqflite_common_ffi: ^2.3.7+1 From 35cd1162c2241614a65e7dfcb945f2749e55f4e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 02:04:23 +0000 Subject: [PATCH 40/75] build: (deps): bump flutter_foreground_task from 9.2.1 to 9.2.2 Bumps [flutter_foreground_task](https://github.com/Dev-hwang/flutter_foreground_task) from 9.2.1 to 9.2.2. - [Changelog](https://github.com/Dev-hwang/flutter_foreground_task/blob/master/CHANGELOG.md) - [Commits](https://github.com/Dev-hwang/flutter_foreground_task/commits) --- updated-dependencies: - dependency-name: flutter_foreground_task dependency-version: 9.2.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index c35708a0..b6b78ff5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -471,10 +471,10 @@ packages: dependency: "direct main" description: name: flutter_foreground_task - sha256: "1903697944a31f596622e51a6af55e3a9dfb27762f9763ab2841184098c6b0ba" + sha256: fc5c01a5e1b8f7bb51d0c737714f0c50440dbdf1aeddc5f8cbba313aa6fd4856 url: "https://pub.dev" source: hosted - version: "9.2.1" + version: "9.2.2" flutter_linkify: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 7b7b1480..42593430 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: file_selector: ^1.1.0 flutter: sdk: flutter - flutter_foreground_task: ^9.2.1 + flutter_foreground_task: ^9.2.2 flutter_linkify: ^6.0.0 flutter_local_notifications: ^21.0.0 flutter_localizations: From 9f73ca61fa37154014f674326e1e77db7d8798c7 Mon Sep 17 00:00:00 2001 From: alexander kozlev Date: Thu, 2 Apr 2026 20:35:16 +0200 Subject: [PATCH 41/75] chore(translations): Translated using Weblate (Russian) Currently translated at 94.5% (722 of 764 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ru/ --- lib/l10n/intl_ru.arb | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index 7341066d..28e8aaa7 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -83,7 +83,7 @@ "type": "String", "placeholders": {} }, - "areGuestsAllowedToJoin": "Разрешено ли гостям присоединяться", + "areGuestsAllowedToJoin": "Разрешено ли гостям присоединяться?", "@areGuestsAllowedToJoin": { "type": "String", "placeholders": {} @@ -2694,5 +2694,21 @@ "type": "int" } } - } -} \ No newline at end of file + }, + "versionWithNumber": "Версия: {version}", + "@versionWithNumber": { + "type": "String", + "placeholders": { + "version": { + "type": "String" + } + } + }, + "logs": "Архивные записи", + "advancedConfigs": "Расширенные Настройки", + "advancedConfigurations": "Расширенные конфигурации", + "signUpGreeting": "FluffyChat децентрализорован! Выберите сервер, где вы хотите сделать свой аккаунт и заходите!", + "signInGreeting": "У вас есть уже аккаунт в Matrix? Добро пожаловать! Выберите свой сервер и войдите.", + "appIntro": "С FluffyChat'ом вы можете говорить со своими друзьями. Это защищённый децентрализорованный [matrix] мессенджер! Узнайте больше на https://matrix.org, если вам нравится или просто зарегистрироваться.", + "join": "Присоединиться" +} From 48c317540fc0391c4b45dd710fa7f0e3622883bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 21:35:03 +0000 Subject: [PATCH 42/75] build: (deps): bump go_router from 17.1.0 to 17.2.0 Bumps [go_router](https://github.com/flutter/packages/tree/main/packages) from 17.1.0 to 17.2.0. - [Commits](https://github.com/flutter/packages/commits/go_router-v17.2.0/packages) --- updated-dependencies: - dependency-name: go_router dependency-version: 17.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 67cec15d..f831218a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -755,10 +755,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "7974313e217a7771557add6ff2238acb63f635317c35fa590d348fb238f00896" + sha256: "48fb2f42ad057476fa4b733cb95e9f9ea7b0b010bb349ea491dca7dbdb18ffc4" url: "https://pub.dev" source: hosted - version: "17.1.0" + version: "17.2.0" gsettings: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c2933318..27ad61f3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,7 +39,7 @@ dependencies: flutter_web_auth_2: ^5.0.1 flutter_webrtc: ^1.4.1 geolocator: ^14.0.2 - go_router: ^17.1.0 + go_router: ^17.2.0 handy_window: ^0.4.2 highlight: ^0.7.0 html: ^0.15.4 From f13cbf858d0f2d81950d6fe402ffd1eeb1fa7445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sat, 4 Apr 2026 01:18:40 +0200 Subject: [PATCH 43/75] chore(translations): Translated using Weblate (Estonian) Currently translated at 100.0% (768 of 768 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/ --- lib/l10n/intl_et.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/l10n/intl_et.arb b/lib/l10n/intl_et.arb index b8dea5f3..79a629a4 100644 --- a/lib/l10n/intl_et.arb +++ b/lib/l10n/intl_et.arb @@ -2809,5 +2809,9 @@ "joinVoiceCall": "Liitu häälkõnega", "joinVideoCall": "Liitu videokõnega", "live": "Reaalajas", - "playSoundOnNotification": "Lisa teavitusele helimärguanne" + "playSoundOnNotification": "Lisa teavitusele helimärguanne", + "addTag": "Lisa silt", + "removeTag": "Eemalda silt", + "tagName": "Sildi nimi", + "createNewTag": "Lisa uus silt" } From 42a1047067559c44b1e1b947d082c8da29322982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aindri=C3=BA=20Mac=20Giolla=20Eoin?= Date: Sat, 4 Apr 2026 13:25:06 +0200 Subject: [PATCH 44/75] chore(translations): Translated using Weblate (Irish) Currently translated at 100.0% (768 of 768 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ga/ --- lib/l10n/intl_ga.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/l10n/intl_ga.arb b/lib/l10n/intl_ga.arb index c70490ed..2ca13060 100644 --- a/lib/l10n/intl_ga.arb +++ b/lib/l10n/intl_ga.arb @@ -2815,5 +2815,9 @@ "joinVoiceCall": "Glac páirt i nglao gutha", "joinVideoCall": "Glac páirt i nglao físe", "live": "Beo", - "playSoundOnNotification": "Seinn fuaim ar fhógra" + "playSoundOnNotification": "Seinn fuaim ar fhógra", + "addTag": "Cuir clib leis", + "removeTag": "Bain an chlib", + "tagName": "Ainm an chlib", + "createNewTag": "Cruthaigh clib nua" } From 92549cff66637d7a7ce5fc691205fcc28d76adb6 Mon Sep 17 00:00:00 2001 From: Frank Paul Silye Date: Sat, 4 Apr 2026 07:10:23 +0200 Subject: [PATCH 45/75] =?UTF-8?q?chore(translations):=20Translated=20using?= =?UTF-8?q?=20Weblate=20(Norwegian=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (768 of 768 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/nb_NO/ --- lib/l10n/intl_nb.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/l10n/intl_nb.arb b/lib/l10n/intl_nb.arb index 31402c18..ab89a8f4 100644 --- a/lib/l10n/intl_nb.arb +++ b/lib/l10n/intl_nb.arb @@ -2816,5 +2816,9 @@ "joinVoiceCall": "Bli med i lydsamtale", "joinVideoCall": "Bli med i videosamtale", "live": "Direkte", - "playSoundOnNotification": "Spill av lyd ved varsling" + "playSoundOnNotification": "Spill av lyd ved varsling", + "addTag": "Legg til emneknagg", + "removeTag": "Fjern emneknagg", + "tagName": "Navn på emneknagg", + "createNewTag": "Opprett ny emneknagg" } From 3fd3c575f001fa0bcd6b196c53f6de38b43b722c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E7=8E=8B=E5=8F=AB=E6=88=91=E6=9D=A5=E5=B7=A1?= =?UTF-8?q?=E5=B1=B1?= Date: Sat, 4 Apr 2026 02:42:48 +0200 Subject: [PATCH 46/75] chore(translations): Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (768 of 768 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/ --- lib/l10n/intl_zh.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 65e94285..c08cc07f 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -2809,5 +2809,9 @@ "joinVoiceCall": "加入语音通话", "joinVideoCall": "加入视频通话", "live": "实时", - "playSoundOnNotification": "播放通知声音" + "playSoundOnNotification": "播放通知声音", + "addTag": "添加标签", + "removeTag": "删除标签", + "tagName": "标签名", + "createNewTag": "创建新标签" } From 2a73ccf8d77cd4518aa97dc5301d2eedb99155ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jos=C3=A9=20m=2E?= Date: Sun, 5 Apr 2026 06:56:09 +0200 Subject: [PATCH 47/75] chore(translations): Translated using Weblate (Galician) Currently translated at 100.0% (768 of 768 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/ --- lib/l10n/intl_gl.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/l10n/intl_gl.arb b/lib/l10n/intl_gl.arb index cc703e15..d7dfd304 100644 --- a/lib/l10n/intl_gl.arb +++ b/lib/l10n/intl_gl.arb @@ -2809,5 +2809,9 @@ "joinVoiceCall": "Unirse á chamada de voz", "joinVideoCall": "Unirse á chamada de vídeo", "live": "En directo", - "playSoundOnNotification": "Reproducir son coas notificacións" + "playSoundOnNotification": "Reproducir son coas notificacións", + "addTag": "Engadir etiqueta", + "removeTag": "Retirar etiqueta", + "tagName": "Nome da etiqueta", + "createNewTag": "Crear nova etiqueta" } From 2b17162b941c2599c3d9e144967d06e4daaa6618 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 21:40:33 +0000 Subject: [PATCH 48/75] build: (deps): bump dart_code_linter from 3.2.1 to 4.0.1 Bumps [dart_code_linter](https://github.com/bancolombia/dart-code-linter) from 3.2.1 to 4.0.1. - [Release notes](https://github.com/bancolombia/dart-code-linter/releases) - [Changelog](https://github.com/bancolombia/dart-code-linter/blob/trunk/CHANGELOG.md) - [Commits](https://github.com/bancolombia/dart-code-linter/compare/v3.2.1...v4.0.1) --- updated-dependencies: - dependency-name: dart_code_linter dependency-version: 4.0.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pubspec.lock | 20 ++++++++++---------- pubspec.yaml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index f831218a..83c526ff 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,26 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d + sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" url: "https://pub.dev" source: hosted - version: "91.0.0" + version: "93.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08 + sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b url: "https://pub.dev" source: hosted - version: "8.4.1" + version: "10.0.1" analyzer_plugin: dependency: transitive description: name: analyzer_plugin - sha256: "825071d553c4aef2252196d46a665fbd8e0cb06de07725f25d1b29bd18d65fff" + sha256: "7df504f0c9d6891bacc9f73a5a8c5f6fe4fc49c90ec8e3379916372906ba0b32" url: "https://pub.dev" source: hosted - version: "0.13.6" + version: "0.14.1" ansicolor: dependency: transitive description: @@ -253,10 +253,10 @@ packages: dependency: "direct dev" description: name: dart_code_linter - sha256: "1b53722d9933a5f5d4580acc29c7f16b1fde66d21d1ecf7bb2a811caf3a42b42" + sha256: "5025faeec583db175bbd0d4e8a73fceedc0370bc6db1ddf9f60fa641220acc27" url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "4.0.1" dart_earcut: dependency: transitive description: @@ -277,10 +277,10 @@ packages: dependency: transitive description: name: dart_style - sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b + sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.7" dart_webrtc: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 27ad61f3..2dae7103 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -81,7 +81,7 @@ dependencies: webrtc_interface: ^1.5.1 dev_dependencies: - dart_code_linter: ^3.2.1 + dart_code_linter: ^4.0.1 flutter_lints: ^6.0.0 flutter_native_splash: ^2.4.7 flutter_test: From a51320bae8eda5024e9190ff0e3c589a04bcaca8 Mon Sep 17 00:00:00 2001 From: Alex Katon Date: Mon, 6 Apr 2026 10:56:27 +0200 Subject: [PATCH 49/75] chore(translations): Translated using Weblate (Russian) Currently translated at 96.2% (739 of 768 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ru/ --- lib/l10n/intl_ru.arb | 73 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index 28e8aaa7..fc737379 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -2710,5 +2710,76 @@ "signUpGreeting": "FluffyChat децентрализорован! Выберите сервер, где вы хотите сделать свой аккаунт и заходите!", "signInGreeting": "У вас есть уже аккаунт в Matrix? Добро пожаловать! Выберите свой сервер и войдите.", "appIntro": "С FluffyChat'ом вы можете говорить со своими друзьями. Это защищённый децентрализорованный [matrix] мессенджер! Узнайте больше на https://matrix.org, если вам нравится или просто зарегистрироваться.", - "join": "Присоединиться" + "join": "Присоединиться", + "countFiles": "{count} файлов", + "@countFiles": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "unreadChatsInApp": "{appname}: {unread} непрочитанных чатов", + "@unreadChatsInApp": { + "type": "String", + "placeholders": { + "appname": { + "type": "String" + }, + "unread": { + "type": "String" + } + } + }, + "thereAreCountUsersBlocked": "Сейчас {count} пользователей заблокировано.", + "@thereAreCountUsersBlocked": { + "type": "String", + "count": {} + }, + "serverLimitReached": "Достигнут серверный лимит! Подождите {seconds} секунд...", + "@serverLimitReached": { + "type": "integer", + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "countVotes": "{count, plural, =1{Один голос} other{{count} голоса(-ов)}}", + "@countVotes": { + "type": "int", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "countReplies": "{count, plural, =1{Один ответ} other{{count} ответа(-ов)}}", + "@countReplies": { + "type": "int", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "chatSearchedUntil": "Чат индексируется до {time}", + "@chatSearchedUntil": { + "type": "String", + "placeholders": { + "time": { + "type": "String" + } + } + }, + "federationBaseUrl": "Основной URL федерации", + "clientWellKnownInformation": "Client-Well-Known Информация:", + "baseUrl": "Базовый URL", + "identityServer": "Сервер профилей:", + "signIn": "Войти", + "searchOrEnterHomeserverAddress": "Поищите или введите адрес домашнего сервера", + "matrixId": "Matrix ID", + "setPowerLevel": "Установить уровень возможностей", + "makeModerator": "Назначить модератором", + "makeAdmin": "Назначить администратором" } From b2f5700657cc1799ebf80a4d97a166817b205c31 Mon Sep 17 00:00:00 2001 From: Alex Katon Date: Mon, 6 Apr 2026 10:50:01 +0200 Subject: [PATCH 50/75] chore(translations): Translated using Weblate (Belarusian) Currently translated at 100.0% (768 of 768 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/be/ --- lib/l10n/intl_be.arb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/l10n/intl_be.arb b/lib/l10n/intl_be.arb index 021578d1..209a30fb 100644 --- a/lib/l10n/intl_be.arb +++ b/lib/l10n/intl_be.arb @@ -2795,5 +2795,10 @@ "startVideoCall": "Пачаць відэа-выклік", "joinVoiceCall": "Далучыцца да галасавога выкліка", "joinVideoCall": "Далучыцца да відэа-выкліка", - "live": "Трансляцыя" + "live": "Трансляцыя", + "playSoundOnNotification": "Прайграваць гук апавяшчэння", + "addTag": "Дадаць тэг", + "removeTag": "Выдаліць тэг", + "tagName": "Назва тэга", + "createNewTag": "Стварыць новы тэг" } From d7304e03644658ead65c68c6c7f7771d8326830a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 21:35:35 +0000 Subject: [PATCH 51/75] build: (deps): bump dart_code_linter from 4.0.1 to 4.0.2 Bumps [dart_code_linter](https://github.com/bancolombia/dart-code-linter) from 4.0.1 to 4.0.2. - [Release notes](https://github.com/bancolombia/dart-code-linter/releases) - [Changelog](https://github.com/bancolombia/dart-code-linter/blob/trunk/CHANGELOG.md) - [Commits](https://github.com/bancolombia/dart-code-linter/compare/v4.0.1...v4.0.2) --- updated-dependencies: - dependency-name: dart_code_linter dependency-version: 4.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 83c526ff..899b85a6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -253,10 +253,10 @@ packages: dependency: "direct dev" description: name: dart_code_linter - sha256: "5025faeec583db175bbd0d4e8a73fceedc0370bc6db1ddf9f60fa641220acc27" + sha256: "8ece88f710621ca1c40b6c344b316d78bb2269d728d37d2a44f19a81d9d2cb93" url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.2" dart_earcut: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 2dae7103..b8ea6bb1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -81,7 +81,7 @@ dependencies: webrtc_interface: ^1.5.1 dev_dependencies: - dart_code_linter: ^4.0.1 + dart_code_linter: ^4.0.2 flutter_lints: ^6.0.0 flutter_native_splash: ^2.4.7 flutter_test: From 01c5433cf93b51f0469f96f1b522fe2261469d56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 06:39:41 +0000 Subject: [PATCH 52/75] build: bump addressable in /ios in the bundler group across 1 directory Bumps the bundler group with 1 update in the /ios directory: [addressable](https://github.com/sporkmonger/addressable). Updates `addressable` from 2.8.4 to 2.9.0 - [Changelog](https://github.com/sporkmonger/addressable/blob/main/CHANGELOG.md) - [Commits](https://github.com/sporkmonger/addressable/compare/addressable-2.8.4...addressable-2.9.0) --- updated-dependencies: - dependency-name: addressable dependency-version: 2.9.0 dependency-type: indirect dependency-group: bundler ... Signed-off-by: dependabot[bot] --- ios/Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/Gemfile.lock b/ios/Gemfile.lock index d7d0fcce..a57e8781 100644 --- a/ios/Gemfile.lock +++ b/ios/Gemfile.lock @@ -3,8 +3,8 @@ GEM specs: CFPropertyList (3.0.3) abbrev (0.1.2) - addressable (2.8.4) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.4.0) @@ -168,7 +168,7 @@ GEM os (1.1.1) ostruct (0.6.3) plist (3.6.0) - public_suffix (5.0.3) + public_suffix (7.0.5) rake (13.0.3) representable (3.1.1) declarative (< 0.1.0) From bcc4d4d204b744bf9da554f1830926343c21495d Mon Sep 17 00:00:00 2001 From: Halil Kaskavalci <1646238+kaskavalci@users.noreply.github.com> Date: Fri, 10 Apr 2026 21:51:37 +0200 Subject: [PATCH 53/75] feat(chat): make messagePreviewMaxLines configurable, with default 128 --- config.sample.json | 1 + lib/config/setting_keys.dart | 3 +++ lib/pages/chat/events/html_message.dart | 9 +++++++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/config.sample.json b/config.sample.json index a1ea6f6f..ebc126e8 100644 --- a/config.sample.json +++ b/config.sample.json @@ -12,6 +12,7 @@ "audioRecordingSamplingRate": 44100, "renderHtml": true, "fontSizeFactor": 1, + "messagePreviewMaxLines": 128, "hideRedactedEvents": false, "hideUnknownEvents": true, "separateChatTypes": false, diff --git a/lib/config/setting_keys.dart b/lib/config/setting_keys.dart index 1b489241..4e7cc96f 100644 --- a/lib/config/setting_keys.dart +++ b/lib/config/setting_keys.dart @@ -9,6 +9,9 @@ import 'package:shared_preferences/shared_preferences.dart'; enum AppSettings { textMessageMaxLength('textMessageMaxLength', 16384), + + /// Max lines for unselected HTML/text bubbles; 0 = unlimited (no fade). + messagePreviewMaxLines('chat.fluffy.message_preview_max_lines', 128), audioRecordingNumChannels('audioRecordingNumChannels', 1), audioRecordingAutoGain('audioRecordingAutoGain', true), audioRecordingEchoCancel('audioRecordingEchoCancel', false), diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index e4ded6f1..b30ac2f5 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -1,4 +1,5 @@ import 'package:collection/collection.dart'; +import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/utils/code_highlight_theme.dart'; import 'package:fluffychat/utils/event_checkbox_extension.dart'; import 'package:fluffychat/widgets/avatar.dart'; @@ -509,11 +510,15 @@ class HtmlMessage extends StatelessWidget { @override Widget build(BuildContext context) { final element = parser.parse(html).body ?? dom.Element.html(''); + final configuredMaxLines = AppSettings.messagePreviewMaxLines.value; + final maxLines = !limitHeight || configuredMaxLines <= 0 + ? null + : configuredMaxLines; return Text.rich( _renderHtml(element, context), style: TextStyle(fontSize: fontSize, color: textColor), - maxLines: limitHeight ? 64 : null, - overflow: TextOverflow.fade, + maxLines: maxLines, + overflow: maxLines == null ? TextOverflow.visible : TextOverflow.fade, selectionColor: textColor.withAlpha(128), ); } From 0e0daacff43287d1ee0e9e00b029c834039166cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 21:35:26 +0000 Subject: [PATCH 54/75] build: (deps): bump badges from 3.1.2 to 3.2.0 Bumps [badges](https://github.com/yako-dev/flutter_badges) from 3.1.2 to 3.2.0. - [Release notes](https://github.com/yako-dev/flutter_badges/releases) - [Changelog](https://github.com/yako-dev/flutter_badges/blob/master/CHANGELOG.md) - [Commits](https://github.com/yako-dev/flutter_badges/compare/v3.1.2...v3.2.0) --- updated-dependencies: - dependency-name: badges dependency-version: 3.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 899b85a6..5018915d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -69,10 +69,10 @@ packages: dependency: "direct main" description: name: badges - sha256: a7b6bbd60dce418df0db3058b53f9d083c22cdb5132a052145dc267494df0b84 + sha256: cf1c88fb3777df69ccd630b80de5267f54efa4a39381b0404a7c03d56cb7c041 url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.2.0" barbecue: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b8ea6bb1..28bab176 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,7 @@ environment: dependencies: archive: ^4.0.7 async: ^2.13.1 - badges: ^3.1.2 + badges: ^3.2.0 blurhash_dart: ^1.2.1 chewie: ^1.13.0 collection: ^1.18.0 From c7060413d5e5158e3e42f6672f72f44046fcd621 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sat, 11 Apr 2026 16:35:32 +0900 Subject: [PATCH 55/75] build: Update to flutter 3.41.6 --- .tool_versions.yaml | 2 +- snap/snapcraft.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.tool_versions.yaml b/.tool_versions.yaml index a06ba450..54f80e87 100644 --- a/.tool_versions.yaml +++ b/.tool_versions.yaml @@ -1,2 +1,2 @@ environment: - flutter: 3.41.5 # Keep in sync with snap/snapcraft.yaml \ No newline at end of file + flutter: 3.41.6 # Keep in sync with snap/snapcraft.yaml \ No newline at end of file diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 71245163..273b9902 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -53,7 +53,7 @@ platforms: parts: flutter-git: source: https://github.com/flutter/flutter.git - source-tag: 3.41.5 + source-tag: 3.41.6 source-depth: 1 plugin: nil override-build: | From e994f61baa65f57d8ffd8bc52f8dde125003cfb3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 21:40:42 +0000 Subject: [PATCH 56/75] build: (deps): bump file_picker from 10.3.10 to 11.0.2 Bumps [file_picker](https://github.com/miguelpruivo/flutter_file_picker) from 10.3.10 to 11.0.2. - [Release notes](https://github.com/miguelpruivo/flutter_file_picker/releases) - [Changelog](https://github.com/miguelpruivo/flutter_file_picker/blob/master/CHANGELOG.md) - [Commits](https://github.com/miguelpruivo/flutter_file_picker/compare/v10.3.10...v11.0.2) --- updated-dependencies: - dependency-name: file_picker dependency-version: 11.0.2 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- lib/utils/file_selector.dart | 2 +- lib/utils/matrix_sdk_extensions/matrix_file_extension.dart | 2 +- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/utils/file_selector.dart b/lib/utils/file_selector.dart index a4aacc1d..e151ac1c 100644 --- a/lib/utils/file_selector.dart +++ b/lib/utils/file_selector.dart @@ -13,7 +13,7 @@ Future> selectFiles( final result = await AppLock.of(context).pauseWhile( showFutureLoadingDialog( context: context, - future: () => FilePicker.platform.pickFiles( + future: () => FilePicker.pickFiles( compressionQuality: 0, allowMultiple: allowMultiple, type: type, diff --git a/lib/utils/matrix_sdk_extensions/matrix_file_extension.dart b/lib/utils/matrix_sdk_extensions/matrix_file_extension.dart index 4590cace..67d9aeb7 100644 --- a/lib/utils/matrix_sdk_extensions/matrix_file_extension.dart +++ b/lib/utils/matrix_sdk_extensions/matrix_file_extension.dart @@ -9,7 +9,7 @@ extension MatrixFileExtension on MatrixFile { Future save(BuildContext context) async { final scaffoldMessenger = ScaffoldMessenger.of(context); final l10n = L10n.of(context); - final downloadPath = await FilePicker.platform.saveFile( + final downloadPath = await FilePicker.saveFile( dialogTitle: l10n.saveFile, fileName: name, type: filePickerFileType, diff --git a/pubspec.lock b/pubspec.lock index 5018915d..fd57b5be 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -381,10 +381,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: "57d9a1dd5063f85fa3107fb42d1faffda52fdc948cefd5fe5ea85267a5fc7343" + sha256: f13a03000d942e476bc1ff0a736d2e9de711d2f89a95cd4c1d88f861c3348387 url: "https://pub.dev" source: hosted - version: "10.3.10" + version: "11.0.2" file_selector: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 28bab176..9ea376ac 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: device_info_plus: ^12.3.0 dynamic_color: ^1.8.1 emoji_picker_flutter: ^4.4.0 - file_picker: ^10.3.10 + file_picker: ^11.0.2 file_selector: ^1.1.0 flutter: sdk: flutter From 4467aea69ad7e101d288ab6201b2a15e88992b44 Mon Sep 17 00:00:00 2001 From: Alex Katon Date: Mon, 13 Apr 2026 17:06:25 +0200 Subject: [PATCH 57/75] chore(translations): Translated using Weblate (Belarusian) Currently translated at 100.0% (768 of 768 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/be/ --- lib/l10n/intl_be.arb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/l10n/intl_be.arb b/lib/l10n/intl_be.arb index 209a30fb..08d63f9e 100644 --- a/lib/l10n/intl_be.arb +++ b/lib/l10n/intl_be.arb @@ -1307,7 +1307,7 @@ "type": "String", "placeholders": {} }, - "presencesToggle": "Паказваць паведасленні статусаў іншых карыстальнікаў", + "presencesToggle": "Паказваць паведамленні статусаў іншых карыстальнікаў", "@presencesToggle": { "type": "String", "placeholders": {} @@ -1706,7 +1706,7 @@ "notificationRuleInviteForMe": "Запрашэнне мяне", "notificationRuleInviteForMeDescription": "Паведамляе карыстальніка, калі яго запрашаюць у пакой.", "allDevices": "Усе прылады", - "crossVerifiedDevicesIfEnabled": "З перакрыжаваным спраўджваннем прылад, калі ўключана", + "crossVerifiedDevicesIfEnabled": "Перакрыжавана спраўджаныя прылады, калі ўключаны", "crossVerifiedDevices": "Перакрыжавана спраўджаныя прылады", "verifiedDevicesOnly": "Толькі спраўджаныя прылады", "takeAPhoto": "Зрабіць здымак", From 31afa9367aef0a2c288997eb0ef4f7f1334b8a26 Mon Sep 17 00:00:00 2001 From: PuppyLo Date: Sun, 12 Apr 2026 16:49:51 +0200 Subject: [PATCH 58/75] chore(translations): Translated using Weblate (Russian) Currently translated at 100.0% (768 of 768 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ru/ --- lib/l10n/intl_ru.arb | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index fc737379..c0e5490d 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -2100,8 +2100,8 @@ "notAnImage": "Это не картинка.", "importNow": "Импортировать сейчас", "importEmojis": "Импортировать эмодзи", - "importFromZipFile": "Импортировать из ZIP-файла", - "exportEmotePack": "Экспортировать набор эмодзи как ZIP", + "importFromZipFile": "Импортировать из zip-файла", + "exportEmotePack": "Экспортировать набор эмодзи как zip", "replace": "Заменить", "googlyEyesContent": "{senderName} выпучил глаза", "@googlyEyesContent": { @@ -2466,7 +2466,7 @@ "boldText": "Жирный шрифт", "strikeThrough": "Перечёркнутый", "pleaseFillOut": "Пожалуйста, заполните", - "invalidUrl": "Не верный URL", + "invalidUrl": "Неверный url-адрес", "addLink": "Добавить ссылку", "italicText": "Italic", "unableToJoinChat": "Невозможно присоединиться к чату. Возможно, другая сторона уже закончила разговор.", @@ -2477,7 +2477,7 @@ "manageAccount": "Управление аккаунтом", "contactServerAdmin": "Админ сервера", "contactServerSecurity": "Безопасность контактов сервера", - "supportPage": "Поддержка", + "supportPage": "Страница поддержки", "name": "Имя", "version": "Версия", "website": "Сайт", @@ -2781,5 +2781,37 @@ "matrixId": "Matrix ID", "setPowerLevel": "Установить уровень возможностей", "makeModerator": "Назначить модератором", - "makeAdmin": "Назначить администратором" + "makeAdmin": "Назначить администратором", + "removeModeratorRights": "Удалить права модератора", + "removeAdminRights": "Удалить права администратора", + "powerLevel": "Уровень энергии", + "setPowerLevelDescription": "Уровни прав определяют, что участнику разрешено делать в этой комнате, и обычно варьируются от 0 до 100.", + "owner": "Владелец", + "mute": "Мут", + "@mute": { + "description": "This should be a very short string because there is not much space in the button!" + }, + "createNewChat": "Создать новый чат", + "reset": "Сброс", + "supportFluffyChat": "Поддержите FluffyChat", + "support": "Поддержка", + "fluffyChatSupportBannerMessage": "FluffyChat нуждается в ВАШЕЙ помощи!\n❤️❤️❤️\nFluffyChat всегда будет бесплатным, но разработка и хостинг всё равно требуют затрат.\nБудущее проекта зависит от поддержки таких людей, как вы.", + "skipSupportingFluffyChat": "Пропустить помощь FluffyChat", + "iAlreadySupportFluffyChat": "Я уже поддерживаю FluffyChat", + "iDoNotWantToSupport": "Я не хочу поддерживать", + "setLowPriority": "Установить низкий приоритет", + "unsetLowPriority": "Неопределенный приоритет", + "removeCallFromChat": "Удалить сообщение из чата", + "removeCallFromChatDescription": "Вы хотите удалить это сообщение из чата для всех участников?", + "removeCallForEveryone": "Отменить вызов для всех", + "startVoiceCall": "Начать голосовой вызов", + "startVideoCall": "Начать видеозвонок", + "joinVoiceCall": "Присоединиться к голосовому звонку", + "joinVideoCall": "Присоединиться к видеозвонку", + "live": "Прямой эфир", + "playSoundOnNotification": "Воспроизвести звук при получении уведомления", + "addTag": "Добавить тег", + "removeTag": "Удалить тег", + "tagName": "Название тега", + "createNewTag": "Создать новый тег" } From 6b3ec45d0f5a9895e134524a70b24792b9dbb11b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 22:12:08 +0000 Subject: [PATCH 59/75] build: (deps): bump go_router from 17.2.0 to 17.2.1 Bumps [go_router](https://github.com/flutter/packages/tree/main/packages) from 17.2.0 to 17.2.1. - [Commits](https://github.com/flutter/packages/commits/go_router-v17.2.1/packages) --- updated-dependencies: - dependency-name: go_router dependency-version: 17.2.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index fd57b5be..fae5c63c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -755,10 +755,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "48fb2f42ad057476fa4b733cb95e9f9ea7b0b010bb349ea491dca7dbdb18ffc4" + sha256: "5540e4a3f416dd4a93458257b908eb88353cbd0fb5b0a3d1bd7d849ba1e88735" url: "https://pub.dev" source: hosted - version: "17.2.0" + version: "17.2.1" gsettings: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9ea376ac..b4e3da43 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,7 +39,7 @@ dependencies: flutter_web_auth_2: ^5.0.1 flutter_webrtc: ^1.4.1 geolocator: ^14.0.2 - go_router: ^17.2.0 + go_router: ^17.2.1 handy_window: ^0.4.2 highlight: ^0.7.0 html: ^0.15.4 From 4e5bada20c93c5bdf3e710fe30949bc3f3f7f61a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 22:38:07 +0000 Subject: [PATCH 60/75] build: (deps): bump flutter_web_auth_2 from 5.0.1 to 5.0.2 Bumps [flutter_web_auth_2](https://github.com/ThexXTURBOXx/flutter_web_auth_2) from 5.0.1 to 5.0.2. - [Release notes](https://github.com/ThexXTURBOXx/flutter_web_auth_2/releases) - [Commits](https://github.com/ThexXTURBOXx/flutter_web_auth_2/compare/5.0.1...5.0.2) --- updated-dependencies: - dependency-name: flutter_web_auth_2 dependency-version: 5.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index fae5c63c..b3e688da 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -641,10 +641,10 @@ packages: dependency: "direct main" description: name: flutter_web_auth_2 - sha256: "432ff8c7b2834eaeec3378d99e24a0210b9ac2f453b3f7a7d739a5c09069fba3" + sha256: d354998934ddc338e69b999b2abaeb33c6fd09999d3a5f92ead1a6b49b49712e url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "5.0.2" flutter_web_auth_2_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b4e3da43..04c9fa6c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,7 +36,7 @@ dependencies: flutter_secure_storage: ^10.0.0 flutter_shortcuts_new: ^2.0.0 flutter_vodozemac: ^0.5.0 - flutter_web_auth_2: ^5.0.1 + flutter_web_auth_2: ^5.0.2 flutter_webrtc: ^1.4.1 geolocator: ^14.0.2 go_router: ^17.2.1 From 142ef1135378e51c6b83e12c8d9f64c69b90c68a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:35:37 +0000 Subject: [PATCH 61/75] build: (deps): bump flutter_map from 8.2.2 to 8.3.0 Bumps [flutter_map](https://github.com/fleaflet/flutter_map) from 8.2.2 to 8.3.0. - [Release notes](https://github.com/fleaflet/flutter_map/releases) - [Changelog](https://github.com/fleaflet/flutter_map/blob/master/CHANGELOG.md) - [Commits](https://github.com/fleaflet/flutter_map/compare/v8.2.2...v8.3.0) --- updated-dependencies: - dependency-name: flutter_map dependency-version: 8.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index b3e688da..1a9a02db 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -532,10 +532,10 @@ packages: dependency: "direct main" description: name: flutter_map - sha256: "391e7dc95cc3f5190748210a69d4cfeb5d8f84dcdfa9c3235d0a9d7742ccb3f8" + sha256: "03b71c02806ff20c3718d108cbbb3638142ebafe368d8ce2dd22a33344bcb02b" url: "https://pub.dev" source: hosted - version: "8.2.2" + version: "8.3.0" flutter_native_splash: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index 04c9fa6c..6007ad3e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,7 +31,7 @@ dependencies: flutter_local_notifications: ^21.0.0 flutter_localizations: sdk: flutter - flutter_map: ^8.2.2 + flutter_map: ^8.3.0 flutter_new_badger: ^1.1.1 flutter_secure_storage: ^10.0.0 flutter_shortcuts_new: ^2.0.0 From 37c96a5cc3095689a79b71e638fac904ce40f2f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:35:50 +0000 Subject: [PATCH 62/75] build: (deps): bump wakelock_plus from 1.5.1 to 1.5.2 Bumps [wakelock_plus](https://github.com/fluttercommunity/wakelock_plus) from 1.5.1 to 1.5.2. - [Commits](https://github.com/fluttercommunity/wakelock_plus/compare/wakelock_plus_1.5.1...wakelock_plus_1.5.2) --- updated-dependencies: - dependency-name: wakelock_plus dependency-version: 1.5.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index b3e688da..617637f6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2053,10 +2053,10 @@ packages: dependency: "direct main" description: name: wakelock_plus - sha256: "8b12256f616346910c519a35606fb69b1fe0737c06b6a447c6df43888b097f39" + sha256: ddf3db70eaa10c37558ff817519b85d527dbd21034fd5d8e1c2e85f31588f1c1 url: "https://pub.dev" source: hosted - version: "1.5.1" + version: "1.5.2" wakelock_plus_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 04c9fa6c..7fcfec76 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -77,7 +77,7 @@ dependencies: url_launcher: ^6.3.2 video_compress: ^3.1.4 video_player: ^2.11.1 - wakelock_plus: ^1.5.1 + wakelock_plus: ^1.5.2 webrtc_interface: ^1.5.1 dev_dependencies: From c288a3fb1f33ff039ce92d74dcc6805767fa161d Mon Sep 17 00:00:00 2001 From: Kimby Date: Tue, 14 Apr 2026 01:38:21 +0200 Subject: [PATCH 63/75] chore(translations): Translated using Weblate (Spanish) Currently translated at 100.0% (768 of 768 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/es/ --- lib/l10n/intl_es.arb | 47 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index 0ef41aea..1b973db2 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -1709,7 +1709,7 @@ "type": "String", "placeholders": {} }, - "oopsPushError": "¡UPS¡ Desafortunadamente, se produjo un error al configurar las notificaciones push.", + "oopsPushError": "¡Ups! Desafortunadamente, se produjo un error al configurar las notificaciones push.", "@oopsPushError": { "type": "String", "placeholders": {} @@ -1867,7 +1867,7 @@ "type": "String", "placeholders": {} }, - "pleaseEnterYourPin": "Por favor ingrese su PIN", + "pleaseEnterYourPin": "Por favor ingrese su pin", "@pleaseEnterYourPin": { "type": "String", "placeholders": {} @@ -2576,7 +2576,7 @@ "contactServerAdmin": "Contactar con el administrador del servidor", "contactServerSecurity": "Contactar con seguridad del servidor", "supportPage": "Página de atención", - "invalidUrl": "URL incorrecta", + "invalidUrl": "Url incorrecto", "addLink": "Añadir enlace", "unableToJoinChat": "No se puede entrar al chat. Puede que la otra parte ya haya cerrado la conversación.", "waitingForServer": "Esperando al servidor...", @@ -2718,7 +2718,7 @@ "addAnswerOption": "Añadir respuesta", "allowMultipleAnswers": "Permitir varias respuestas", "pollHasBeenEnded": "La encuesta ha terminado", - "countVotes": "{count, plural, =1{One vote} other{{count} votes}}", + "countVotes": "{count, plural, =1{Un voto} other{{count} votos}}", "@countVotes": { "type": "int", "placeholders": { @@ -2786,5 +2786,40 @@ "theProcessWasCanceled": "El proceso se ha cancelado.", "join": "Unirse", "searchOrEnterHomeserverAddress": "Buscar o pon la dirección de tu servidor local", - "matrixId": "Matrix ID" -} \ No newline at end of file + "matrixId": "Matrix ID", + "setPowerLevel": "Establecer nivel de poder", + "makeModerator": "Convertir en Moderador", + "makeAdmin": "Convertir en administrador", + "removeModeratorRights": "Remover derechos de moderador", + "removeAdminRights": "Remover derechos de administrador", + "powerLevel": "Nivel de Poder", + "setPowerLevelDescription": "El nivel de poder define el nivel de acciones de un miembro, usualmente esta en el rango entre 0 a 100.", + "owner": "Dueño", + "mute": "Silenciar", + "@mute": { + "description": "This should be a very short string because there is not much space in the button!" + }, + "createNewChat": "Crear nuevo chat", + "reset": "Resetear", + "supportFluffyChat": "Apoyar FluffyChat", + "support": "Apoyar", + "fluffyChatSupportBannerMessage": "FluffyChat necesita TU ayuda!\n❤️❤️❤️\nFluffyChat siempre sera gratis, pero el desarrollo y mantenimiento cuesta dinero.\nEl futuro del proyecto depende del apoyo de personas como tu.", + "skipSupportingFluffyChat": "Omitir apoyo a FluffyChat", + "iDoNotWantToSupport": "No quiero apoyar", + "iAlreadySupportFluffyChat": "Ya apoyo FluffyChat", + "setLowPriority": "Colocar baja prioridad", + "unsetLowPriority": "Desactivar baja prioridad", + "removeCallFromChat": "Remover llamadas del chat", + "removeCallFromChatDescription": "Deseas remover la llamada del chat para todos los miembros?", + "removeCallForEveryone": "Remover llamadas para todos", + "startVoiceCall": "Iniciar llamada", + "startVideoCall": "Iniciar videollamada", + "joinVoiceCall": "Ingresar a llamada", + "joinVideoCall": "Ingresar a videollamada", + "live": "En Vivo", + "playSoundOnNotification": "Sonido en notificación", + "addTag": "Agregar etiqueta", + "removeTag": "Remover etiqueta", + "tagName": "Nombre de etiqueta", + "createNewTag": "Crear nueva etiqueta" +} From 89442b3fbef2063c597604d45ff41042330437ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:35:50 +0000 Subject: [PATCH 64/75] build: (deps): bump chewie from 1.13.0 to 1.13.1 Bumps [chewie](https://github.com/fluttercommunity/chewie) from 1.13.0 to 1.13.1. - [Changelog](https://github.com/fluttercommunity/chewie/blob/master/CHANGELOG.md) - [Commits](https://github.com/fluttercommunity/chewie/compare/v1.13.0...v1.13.1) --- updated-dependencies: - dependency-name: chewie dependency-version: 1.13.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index b0fb5c17..d0167bb3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -149,10 +149,10 @@ packages: dependency: "direct main" description: name: chewie - sha256: "44bcfc5f0dfd1de290c87c9d86a61308b3282a70b63435d5557cfd60f54a69ca" + sha256: "53dadd2c5b6748742d7744072b38a417ad22691ca55715850300ee793dc7cb27" url: "https://pub.dev" source: hosted - version: "1.13.0" + version: "1.13.1" cli_config: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 76e555f7..4b60a5ce 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: async: ^2.13.1 badges: ^3.2.0 blurhash_dart: ^1.2.1 - chewie: ^1.13.0 + chewie: ^1.13.1 collection: ^1.18.0 cross_file: ^0.3.5 desktop_drop: ^0.7.0 From 029e85942d3ad64de1d9b07d96a137156b9bc3f7 Mon Sep 17 00:00:00 2001 From: Jelv Date: Thu, 16 Apr 2026 10:34:01 +0200 Subject: [PATCH 65/75] chore(translations): Translated using Weblate (Dutch) Currently translated at 100.0% (768 of 768 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/nl/ --- lib/l10n/intl_nl.arb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/l10n/intl_nl.arb b/lib/l10n/intl_nl.arb index 3e785bb8..2b43eb0c 100644 --- a/lib/l10n/intl_nl.arb +++ b/lib/l10n/intl_nl.arb @@ -2128,7 +2128,7 @@ "replace": "Vervang", "report": "Rapporteer", "reportErrorDescription": "😭 Oh nee. Er is iets misgegaan. Probeer het later nog eens. Als je wilt, kun je de bug rapporteren aan de ontwikkelaars.", - "sendTypingNotifications": "Typemeldingen verzenden", + "sendTypingNotifications": "Toon 'aan het typen'-meldingen", "chatPermissions": "Chatrechten", "chatDescription": "Onderwerp", "chatDescriptionHasBeenChanged": "Onderwerp gewijzigd", @@ -2808,5 +2808,9 @@ "startVideoCall": "Start video-gesprek", "joinVoiceCall": "Audio-gesprek opnemen", "joinVideoCall": "Deelnemen aan video-gesprek", - "playSoundOnNotification": "Melding geluid afspelen" + "playSoundOnNotification": "Meldingsgeluid afspelen", + "addTag": "Tag toevoegen", + "removeTag": "Tag verwijderen", + "tagName": "Tagnaam", + "createNewTag": "Nieuwe tag maken" } From 535cace8ae28377dc5f1caf1dbace89b9954b87e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=84=82=F0=9D=95=A0=F0=9D=95=A0=F0=9D=95=A0=F0=9D=95=9D?= =?UTF-8?q?=20=28=F0=9D=95=98=F0=9D=95=9A=F0=9D=95=A5=F0=9D=95=99?= =?UTF-8?q?=F0=9D=95=A6=F0=9D=95=93=2E=F0=9D=95=94=F0=9D=95=A0=F0=9D=95=9E?= =?UTF-8?q?/=E2=84=82=F0=9D=95=A0=F0=9D=95=A0=F0=9D=95=A0=F0=9D=95=9D=29?= Date: Fri, 17 Apr 2026 18:46:03 +0200 Subject: [PATCH 66/75] chore(translations): Translated using Weblate (Latvian) Currently translated at 94.0% (722 of 768 strings) Translation: FluffyChat/Translations Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/lv/ --- lib/l10n/intl_lv.arb | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/l10n/intl_lv.arb b/lib/l10n/intl_lv.arb index c81ba6ca..543b95b0 100644 --- a/lib/l10n/intl_lv.arb +++ b/lib/l10n/intl_lv.arb @@ -230,7 +230,7 @@ "type": "String", "placeholders": {} }, - "allSpaces": "Visas vietas", + "allSpaces": "Visas kopienas", "supposedMxid": "Tam būtu jābūt {mxid}", "@supposedMxid": { "type": "String", @@ -385,7 +385,7 @@ } }, "tryAgain": "Jāmēģina vēlreiz", - "areGuestsAllowedToJoin": "Vai vieslietotājiem ir ļauts pievienoties", + "areGuestsAllowedToJoin": "Vai vieslietotāji drīkst pievienoties?", "@areGuestsAllowedToJoin": { "type": "String", "placeholders": {} @@ -458,7 +458,7 @@ "placeholders": {} }, "link": "Saite", - "newSpaceDescription": "Vietas ļauj apvienot tērzēšanas un būvēt privātas vai publiskas kopienas.", + "newSpaceDescription": "Kopienas ļauj apvienot tērzēšanas un būvēt privātas vai publiskas cilvēku grupas, kurus vieno kaut kas kopīgs, piemēram, zinātne, matemātika, valoda, reliģija, ķīmija, medicīna, kosmoss, datori, ceļošana, grāmatu lasīšana, kriptovalūta, kiberdrošība, aparātprogrammatūra.", "chatDescription": "Tērzēšanas apraksts", "next": "Nākamais", "@next": { @@ -547,7 +547,7 @@ "type": "String", "placeholders": {} }, - "spaceIsPublic": "Vieta ir publiska", + "spaceIsPublic": "Kopiena ir publiska", "@spaceIsPublic": { "type": "String", "placeholders": {} @@ -790,7 +790,7 @@ } } }, - "spaceName": "Vietas nosaukums", + "spaceName": "Kopienas nosaukums", "@spaceName": { "type": "String", "placeholders": {} @@ -873,7 +873,7 @@ "type": "String", "placeholders": {} }, - "logInTo": "PIeteikties {homeserver}", + "logInTo": "Pieteikties {homeserver}", "@logInTo": { "type": "String", "placeholders": { @@ -1195,7 +1195,7 @@ "type": "String", "placeholders": {} }, - "emoteExists": "Emocija jau pastāv.", + "emoteExists": "Emocija jau pastāv!", "@emoteExists": { "type": "String", "placeholders": {} @@ -1351,7 +1351,7 @@ } } }, - "addToSpace": "Pievienot vietai", + "addToSpace": "Pievienot kopienai", "unbanFromChat": "Atcelt liegumu tērzēšanā", "@unbanFromChat": { "type": "String", @@ -1646,7 +1646,7 @@ "type": "String", "placeholders": {} }, - "createNewSpace": "Jauna vieta", + "createNewSpace": "Jauna kopiena", "@createNewSpace": { "type": "String", "placeholders": {} @@ -1725,7 +1725,7 @@ }, "newGroup": "Jauna kopa", "bundleName": "Komplekta nosaukums", - "removeFromSpace": "Noņemt no vietas", + "removeFromSpace": "Noņemt no kopienas", "dateAndTimeOfDay": "{date}, {timeOfDay}", "@dateAndTimeOfDay": { "type": "String", @@ -2060,7 +2060,7 @@ "type": "String", "placeholders": {} }, - "newSpace": "Jauna vieta", + "newSpace": "Jauna kopiena", "changePassword": "Nomainīt paroli", "@changePassword": { "type": "String", @@ -2144,7 +2144,7 @@ "type": "String", "placeholders": {} }, - "pin": "PIN", + "pin": "Piespraust", "@pin": { "type": "String", "placeholders": {} @@ -2203,8 +2203,8 @@ "transparent": "Caurspīdīgs", "searchForUsers": "Meklēt @lietotājus...", "pleaseEnterYourCurrentPassword": "Lūgums ievadīt savu pašreizējo paroli", - "publicSpaces": "Publiskas vietas", - "joinSpace": "Pievienoties vietai", + "publicSpaces": "Publiskas kopienas", + "joinSpace": "Pievienoties kopienai", "createGroupAndInviteUsers": "Izveidot kopu un uzaicināt lietotājus", "groupCanBeFoundViaSearch": "Kopu var atrast meklēšanā", "commandHint_sendraw": "Nosūtīt neapstrādātu JSON", @@ -2319,7 +2319,7 @@ } } }, - "addChatOrSubSpace": "Pievienot tērzēšanu vai apakšvietu", + "addChatOrSubSpace": "Pievienot tērzēšanu vai apakškopienu", "formattedMessagesDescription": "Attēlot bagātinātu ziņu saturu, piemēram, ar Markdown iezīmētu treknrakstu.", "sessionLostBody": "Sesija ir zaudēta. Lūgums ziņot par šo kļūdu izstrādātājiem {url}. Kļūdas ziņojums ir: {error}", "@sessionLostBody": { @@ -2410,7 +2410,7 @@ "changeTheCanonicalRoomAlias": "Mainīt tērzēšanas galveno publisko adresi", "sendRoomNotifications": "Sūtīt @istaba paziņojumus", "changeTheDescriptionOfTheGroup": "Mainīt tērzēšanas aprakstu", - "alwaysUse24HourFormat": "nē", + "alwaysUse24HourFormat": "true", "@alwaysUse24HourFormat": { "description": "Set to true to always display time of day in 24 hour format." }, @@ -2423,7 +2423,7 @@ } } }, - "goToSpace": "Doties uz vietu: {space}", + "goToSpace": "Doties uz kopienu: {space}", "@goToSpace": { "type": "String", "space": {} @@ -2446,8 +2446,8 @@ "changelog": "Izmaiņu žurnāls", "noMoreChatsFound": "Vairs netika atrasta neviena tērzēšana...", "unread": "Nelasītas", - "space": "Vieta", - "spaces": "Vietas", + "space": "Kopiena", + "spaces": "Kopienas", "markAsUnread": "Atzīmēt kā nelasītu", "sendingAttachment": "Nosūta pielikumu...", "generatingVideoThumbnail": "Izveido video sīktēlu...", @@ -2562,7 +2562,7 @@ "more": "Vairāk", "roomNotificationSettings": "Istabu paziņojumu iestatījumi", "notificationRuleEncrypted": "Šifrēts", - "notificationRuleJitsi": "Jitsi", + "notificationRuleJitsi": "Jitsi videozvani", "notificationRuleIsUserMention": "Lietotāja pieminēšana", "notificationRuleIsRoomMentionDescription": "Paziņo lietotājam, kad tiek pieminēta istaba.", "notificationRuleMessageDescription": "Paziņo lietotājam par vispārējām ziņām.", @@ -2645,7 +2645,7 @@ "longPressToRecordVoiceMessage": "Ilga piespiešana, lai ierakstītu balss ziņu.", "pause": "Apturēt", "resume": "Atsākt", - "removeFromSpaceDescription": "Tērzēšana tiks noņemta no vietas, bet tā joprojām būs redzama tērzēšanu sarakstā.", + "removeFromSpaceDescription": "Tērzēšana tiks noņemta no kopienas, bet tā joprojām būs redzama tērzēšanu sarakstā.", "countChats": "{chats} tērzēšanas", "@countChats": { "type": "String", @@ -2759,4 +2759,4 @@ "signUpGreeting": "FluffyChat ir decentralizēta. Jāatlasa serveris, kurā ir vēlēšanās izveidot savu kontu, un aiziet!", "signInGreeting": "Jau ir Matrix konts? Laipni lūdzam atpakaļ! Jāatlasa savs mājasserveris un jāpiesakās.", "appIntro": "Ar FluffyChat vari tērzēt ar saviem draugiem. Tā ir droša un decentralizēta [matrix] ziņapmaiņas lietotne. Vairāk var uzzināt https://matrix.org, ja ir vēlēšanās, vai vienkārši jāpiesakās." -} \ No newline at end of file +} From 680504ee6b2188c7ba3becb7f77e8a3c8fac43c7 Mon Sep 17 00:00:00 2001 From: Vitalii Date: Sat, 18 Apr 2026 20:41:27 +0300 Subject: [PATCH 67/75] fix: add prepare-web.sh support for both python and go yq versions --- scripts/prepare-web.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/prepare-web.sh b/scripts/prepare-web.sh index b4ec1889..25de197b 100755 --- a/scripts/prepare-web.sh +++ b/scripts/prepare-web.sh @@ -2,7 +2,7 @@ # Compile Vodozemac for web version=$(yq ".dependencies.flutter_vodozemac" < pubspec.yaml) -version=$(expr "$version" : '\^*\(.*\)') +version=$(printf "%s" "$version" | tr -d '"^') git clone https://github.com/famedly/dart-vodozemac.git -b ${version} .vodozemac cd .vodozemac cargo install flutter_rust_bridge_codegen @@ -16,7 +16,7 @@ dart compile js ./web/native_executor.dart -o ./web/native_executor.js -m # Download native_imaging for web: version=$(yq ".dependencies.native_imaging" < pubspec.yaml) -version=$(expr "$version" : '\^*\(.*\)') +version=$(printf "%s" "$version" | tr -d '"^') curl -L "https://github.com/famedly/dart_native_imaging/releases/download/v${version}/native_imaging.zip" > native_imaging.zip unzip native_imaging.zip mv js/* web/ From 3e01544a755c78b466658c78d5936e9198071dab Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 19 Apr 2026 10:42:43 +0900 Subject: [PATCH 68/75] fix: UX feedback for sending events --- lib/pages/chat/chat.dart | 12 +- lib/pages/chat/chat_event_list.dart | 4 +- lib/pages/chat/events/message.dart | 311 ++++++++++++++++------------ 3 files changed, 182 insertions(+), 145 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 7572566a..97374d23 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -463,14 +463,17 @@ class ChatController extends State scrollUpBannerEventId = eventId; }); - bool firstUpdateReceived = false; + String? animateInEventId; + + void _insert(int index) { + if (index > 0) return; + animateInEventId = timeline?.events.firstOrNull?.eventId; + } void updateView() { if (!mounted) return; setReadMarker(); - setState(() { - firstUpdateReceived = true; - }); + setState(() {}); } Future? loadTimelineFuture; @@ -487,6 +490,7 @@ class ChatController extends State timeline?.cancelSubscriptions(); timeline = await room.getTimeline( onUpdate: updateView, + onInsert: _insert, eventContextId: eventContextId, ); } catch (e, s) { diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 65021ad0..d33c398c 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -117,9 +117,7 @@ class ChatEventList extends StatelessWidget { // The message at this index: final event = events[i]; - final animateIn = - event.eventId == timeline.events.first.eventId && - controller.firstUpdateReceived; + final animateIn = event.eventId == controller.animateInEventId; final nextEvent = i + 1 < events.length ? events[i + 1] : null; final previousEvent = i > 0 ? events[i - 1] : null; diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index 9bcd39a9..f85b8604 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -203,6 +203,8 @@ class Message extends StatelessWidget { final enterThread = this.enterThread; final sender = event.senderFromMemoryOrFallback; + final fileSendingStatus = event.fileSendingStatus; + return _AnimateIn( animateIn: animateIn, child: Center( @@ -316,9 +318,33 @@ class Message extends StatelessWidget { height: 16, child: event.status == EventStatus.error ? const Icon(Icons.error, color: Colors.red) - : event.fileSendingStatus != null - ? const CircularProgressIndicator.adaptive( - strokeWidth: 1, + : fileSendingStatus != null + ? Stack( + children: [ + Center( + child: switch (fileSendingStatus) { + FileSendingStatus + .generatingThumbnail => + Icon( + Icons.compress_outlined, + size: 14, + ), + FileSendingStatus.encrypting => + Icon( + Icons.lock_outlined, + size: 14, + ), + FileSendingStatus.uploading => + Icon( + Icons.upload_outlined, + size: 14, + ), + }, + ), + const CircularProgressIndicator.adaptive( + strokeWidth: 1, + ), + ], ) : null, ), @@ -430,147 +456,161 @@ class Message extends StatelessWidget { HapticFeedback.heavyImpact(); onSelect(event); }, - child: Container( - decoration: BoxDecoration( - color: noBubble - ? Colors.transparent - : color, - borderRadius: borderRadius, - ), - clipBehavior: Clip.antiAlias, - child: BubbleBackground( - colors: colors, - ignore: - noBubble || - !ownMessage || - MediaQuery.highContrastOf(context), - scrollController: scrollController, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, + child: AnimatedOpacity( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + opacity: + event.status.isSending || + event.type == EventTypes.Encrypted + ? 0.5 + : 1, + child: Container( + decoration: BoxDecoration( + color: noBubble + ? Colors.transparent + : color, + borderRadius: borderRadius, + ), + clipBehavior: Clip.antiAlias, + child: BubbleBackground( + colors: colors, + ignore: + noBubble || + !ownMessage || + MediaQuery.highContrastOf(context), + scrollController: scrollController, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, + ), ), - ), - constraints: const BoxConstraints( - maxWidth: - FluffyThemes.columnWidth * 1.5, - ), - child: Column( - mainAxisSize: .min, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - if (event.inReplyToEventId( - includingFallback: false, - ) != - null) - FutureBuilder( - future: event.getReplyEvent( - timeline, - ), - builder: (BuildContext context, snapshot) { - final replyEvent = - snapshot.hasData - ? snapshot.data! - : Event( - eventId: - event - .inReplyToEventId() ?? - '\$fake_event_id', - content: { - 'msgtype': 'm.text', - 'body': '...', - }, - senderId: - event.senderId, - type: - 'm.room.message', - room: event.room, - status: - EventStatus.sent, - originServerTs: - DateTime.now(), - ); - return Padding( - padding: - const EdgeInsets.only( - left: 16, - right: 16, - top: 8, - ), - child: Material( - color: Colors.transparent, - borderRadius: ReplyContent - .borderRadius, - child: InkWell( + constraints: const BoxConstraints( + maxWidth: + FluffyThemes.columnWidth * 1.5, + ), + child: Column( + mainAxisSize: .min, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + if (event.inReplyToEventId( + includingFallback: false, + ) != + null) + FutureBuilder( + future: event.getReplyEvent( + timeline, + ), + builder: (BuildContext context, snapshot) { + final replyEvent = + snapshot.hasData + ? snapshot.data! + : Event( + eventId: + event + .inReplyToEventId() ?? + '\$fake_event_id', + content: { + 'msgtype': + 'm.text', + 'body': '...', + }, + senderId: + event.senderId, + type: + 'm.room.message', + room: event.room, + status: EventStatus + .sent, + originServerTs: + DateTime.now(), + ); + return Padding( + padding: + const EdgeInsets.only( + left: 16, + right: 16, + top: 8, + ), + child: Material( + color: + Colors.transparent, borderRadius: ReplyContent .borderRadius, - onTap: () => - scrollToEventId( - replyEvent - .eventId, + child: InkWell( + borderRadius: + ReplyContent + .borderRadius, + onTap: () => + scrollToEventId( + replyEvent + .eventId, + ), + child: AbsorbPointer( + child: ReplyContent( + replyEvent, + ownMessage: + ownMessage, + timeline: + timeline, ), - child: AbsorbPointer( - child: ReplyContent( - replyEvent, - ownMessage: - ownMessage, - timeline: timeline, ), ), ), - ), - ); - }, - ), - MessageContent( - displayEvent, - textColor: textColor, - linkColor: linkColor, - onInfoTab: onInfoTab, - borderRadius: borderRadius, - timeline: timeline, - selected: selected, - bigEmojis: bigEmojis, - ), - if (event.hasAggregatedEvents( - timeline, - RelationshipTypes.edit, - )) - Padding( - padding: const EdgeInsets.only( - bottom: 8.0, - left: 16.0, - right: 16.0, + ); + }, ), - child: Row( - mainAxisSize: - MainAxisSize.min, - spacing: 4.0, - children: [ - Icon( - Icons.edit_outlined, - color: textColor - .withAlpha(164), - size: 14, - ), - Text( - displayEvent - .originServerTs - .localizedTimeShort( - context, - ), - style: TextStyle( + MessageContent( + displayEvent, + textColor: textColor, + linkColor: linkColor, + onInfoTab: onInfoTab, + borderRadius: borderRadius, + timeline: timeline, + selected: selected, + bigEmojis: bigEmojis, + ), + if (event.hasAggregatedEvents( + timeline, + RelationshipTypes.edit, + )) + Padding( + padding: + const EdgeInsets.only( + bottom: 8.0, + left: 16.0, + right: 16.0, + ), + child: Row( + mainAxisSize: + MainAxisSize.min, + spacing: 4.0, + children: [ + Icon( + Icons.edit_outlined, color: textColor .withAlpha(164), - fontSize: 11, + size: 14, ), - ), - ], + Text( + displayEvent + .originServerTs + .localizedTimeShort( + context, + ), + style: TextStyle( + color: textColor + .withAlpha(164), + fontSize: 11, + ), + ), + ], + ), ), - ), - ], + ], + ), ), ), ), @@ -957,15 +997,10 @@ class __AnimateInState extends State<_AnimateIn> { }); }); } - return AnimatedOpacity( + return AnimatedSize( duration: FluffyThemes.animationDuration, curve: FluffyThemes.animationCurve, - opacity: _animationFinished ? 1 : 0, - child: AnimatedSize( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - child: _animationFinished ? widget.child : const SizedBox.shrink(), - ), + child: _animationFinished ? widget.child : const SizedBox.shrink(), ); } } From 8e3f46eee6dbda356fdf4692aa860e1d9c1b5b0b Mon Sep 17 00:00:00 2001 From: krille-chan Date: Sun, 19 Apr 2026 11:44:21 +0900 Subject: [PATCH 69/75] feat: Add Android summary notification --- lib/utils/push_helper.dart | 39 +++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/utils/push_helper.dart b/lib/utils/push_helper.dart index 591549e9..7ca810a8 100644 --- a/lib/utils/push_helper.dart +++ b/lib/utils/push_helper.dart @@ -17,6 +17,8 @@ import 'package:flutter_shortcuts_new/flutter_shortcuts_new.dart'; import 'package:matrix/matrix.dart'; const notificationAvatarDimension = 128; +const String groupKey = 'im.fluffychat.messages'; +const int summaryId = -1; Future pushHelper( PushNotification notification, { @@ -53,6 +55,7 @@ Future pushHelper( AppSettings.applicationName.value, (notification.counts?.unread ?? 0).toString(), ), + groupKey: groupKey, importance: Importance.high, priority: Priority.max, shortcutId: notification.roomId, @@ -252,7 +255,7 @@ Future _tryPushHelper( ), importance: Importance.high, priority: Priority.max, - groupKey: event.room.spaceParents.firstOrNull?.roomId ?? 'rooms', + groupKey: groupKey, actions: event.type == EventTypes.RoomMember || !useNotificationActions ? null : [ @@ -300,6 +303,40 @@ Future _tryPushHelper( event.eventId, ).toString(), ); + + // Send summary notification on Android + if (PlatformInfos.isAndroid) { + final activeNotifications = + (await flutterLocalNotificationsPlugin.getActiveNotifications()) + .where((n) => n.groupKey == groupKey) + .toList(); + + if (activeNotifications.isEmpty) { + return; + } + + final title = l10n.unreadChatsInApp( + AppSettings.applicationName.value, + activeNotifications.length.toString(), + ); + + await flutterLocalNotificationsPlugin.show( + id: summaryId, + notificationDetails: NotificationDetails( + android: AndroidNotificationDetails( + AppConfig.pushNotificationsChannelId, + l10n.incomingMessages, + groupKey: groupKey, + setAsGroupSummary: true, + styleInformation: InboxStyleInformation( + activeNotifications.map((n) => n.body ?? '').toList(), + contentTitle: title, + summaryText: title, + ), + ), + ), + ); + } Logs().v('Push helper has been completed!'); } From 7b2bffc1952c831f99624ed8197311ecf4443779 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:35:29 +0000 Subject: [PATCH 70/75] build: (deps): bump matrix from 6.2.0 to 7.0.0 Bumps [matrix](https://github.com/famedly/matrix-dart-sdk) from 6.2.0 to 7.0.0. - [Release notes](https://github.com/famedly/matrix-dart-sdk/releases) - [Changelog](https://github.com/famedly/matrix-dart-sdk/blob/main/CHANGELOG.md) - [Commits](https://github.com/famedly/matrix-dart-sdk/compare/v6.2.0...v7.0.0) --- updated-dependencies: - dependency-name: matrix dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- lib/pages/chat/chat.dart | 2 +- lib/pages/chat/events/message.dart | 11 ++++++---- lib/pages/chat_details/chat_details_view.dart | 2 +- .../chat_details/participant_list_item.dart | 21 +++++++++++-------- lib/pages/chat_members/chat_members.dart | 5 ++--- .../member_actions_popup_menu_button.dart | 18 ++++++++-------- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 8 files changed, 35 insertions(+), 30 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 97374d23..a89fdbc8 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -1155,7 +1155,7 @@ class ChatController extends State true, false, ); - users.sort((a, b) => a.powerLevel.compareTo(b.powerLevel)); + users.sort((a, b) => a.powerLevel.level.compareTo(b.powerLevel.level)); final via = users .map((user) => user.id.domain) .whereType() diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index f85b8604..353eca0d 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -385,17 +385,20 @@ class Message extends StatelessWidget { ? const SizedBox(height: 12) : Row( children: [ - if (sender.powerLevel >= 50) + if (sender.powerLevel.role != + PowerLevelRole.user) Padding( padding: const EdgeInsets.only( right: 2.0, ), child: Icon( - sender.powerLevel >= 100 + sender.powerLevel.role == + PowerLevelRole + .moderator ? Icons - .admin_panel_settings + .add_moderator_outlined : Icons - .add_moderator_outlined, + .admin_panel_settings, size: 14, color: theme .colorScheme diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index 4260ca5b..03748ab9 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -44,7 +44,7 @@ class ChatDetailsView extends StatelessWidget { ), builder: (context, snapshot) { var members = room.getParticipants().toList() - ..sort((b, a) => a.powerLevel.compareTo(b.powerLevel)); + ..sort((b, a) => a.powerLevel.level.compareTo(b.powerLevel.level)); members = members.take(10).toList(); final actualMembersCount = (room.summary.mInvitedMemberCount ?? 0) + diff --git a/lib/pages/chat_details/participant_list_item.dart b/lib/pages/chat_details/participant_list_item.dart index 2a6ffa89..b1d45d35 100644 --- a/lib/pages/chat_details/participant_list_item.dart +++ b/lib/pages/chat_details/participant_list_item.dart @@ -23,13 +23,16 @@ class ParticipantListItem extends StatelessWidget { Membership.leave => L10n.of(context).leftTheChat, }; - final permissionBatch = user.room.creatorUserIds.contains(user.id) - ? L10n.of(context).owner - : user.powerLevel >= 100 - ? L10n.of(context).admin - : user.powerLevel >= 50 - ? L10n.of(context).moderator - : ''; + final permissionBatch = switch (user.powerLevel.role) { + PowerLevelRole.user => '', + PowerLevelRole.moderator => L10n.of(context).moderator, + PowerLevelRole.admin => L10n.of(context).admin, + PowerLevelRole.owner => L10n.of(context).owner, + }; + + final isAdminOrOwner = + user.powerLevel.role == PowerLevelRole.admin || + user.powerLevel.role == PowerLevelRole.owner; return ListTile( onTap: () => showMemberActionsPopupMenu(context: context, user: user), @@ -45,7 +48,7 @@ class ParticipantListItem extends StatelessWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( - color: user.powerLevel >= 100 + color: isAdminOrOwner ? theme.colorScheme.tertiary : theme.colorScheme.tertiaryContainer, borderRadius: BorderRadius.circular(AppConfig.borderRadius), @@ -53,7 +56,7 @@ class ParticipantListItem extends StatelessWidget { child: Text( permissionBatch, style: theme.textTheme.labelSmall?.copyWith( - color: user.powerLevel >= 100 + color: isAdminOrOwner ? theme.colorScheme.onTertiary : theme.colorScheme.onTertiaryContainer, ), diff --git a/lib/pages/chat_members/chat_members.dart b/lib/pages/chat_members/chat_members.dart index a652eff0..d0ca0f1f 100644 --- a/lib/pages/chat_members/chat_members.dart +++ b/lib/pages/chat_members/chat_members.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; - import 'package:matrix/matrix.dart'; import '../../widgets/matrix.dart'; @@ -39,7 +38,7 @@ class ChatMembersController extends State { if (filter.isEmpty) { setState(() { filteredMembers = members - ?..sort((b, a) => a.powerLevel.compareTo(b.powerLevel)); + ?..sort((b, a) => a.powerLevel.level.compareTo(b.powerLevel.level)); }); return; } @@ -52,7 +51,7 @@ class ChatMembersController extends State { user.id.toLowerCase().contains(filter), ) .toList() - ?..sort((b, a) => a.powerLevel.compareTo(b.powerLevel)); + ?..sort((b, a) => a.powerLevel.level.compareTo(b.powerLevel.level)); }); } diff --git a/lib/widgets/member_actions_popup_menu_button.dart b/lib/widgets/member_actions_popup_menu_button.dart index 1ae71387..a17cf6ff 100644 --- a/lib/widgets/member_actions_popup_menu_button.dart +++ b/lib/widgets/member_actions_popup_menu_button.dart @@ -81,7 +81,7 @@ Future showMemberActionsPopupMenu({ ), ), if (user.canChangeUserPowerLevel) ...[ - if (user.powerLevel < 100) + if (user.powerLevel.level < 100) PopupMenuItem( value: _MemberActions.makeAdmin, child: Row( @@ -92,7 +92,7 @@ Future showMemberActionsPopupMenu({ ], ), ), - if (user.powerLevel < 50) + if (user.powerLevel.level < 50) PopupMenuItem( value: _MemberActions.makeModerator, child: Row( @@ -103,7 +103,7 @@ Future showMemberActionsPopupMenu({ ], ), ), - if (user.powerLevel >= 100) + if (user.powerLevel.role == PowerLevelRole.admin) PopupMenuItem( value: _MemberActions.removeAdmin, child: Row( @@ -114,7 +114,7 @@ Future showMemberActionsPopupMenu({ ], ), ) - else if (user.powerLevel >= 50) + else if (user.powerLevel.role == PowerLevelRole.moderator) PopupMenuItem( value: _MemberActions.removeModerator, child: Row( @@ -127,7 +127,7 @@ Future showMemberActionsPopupMenu({ ), ], if (user.canChangeUserPowerLevel || - !defaultPowerLevels.contains(user.powerLevel)) + !defaultPowerLevels.contains(user.powerLevel.level)) PopupMenuItem( value: _MemberActions.setPowerLevel, enabled: user.canChangeUserPowerLevel, @@ -140,7 +140,7 @@ Future showMemberActionsPopupMenu({ ? L10n.of(context).setPowerLevel : L10n.of(context).powerLevel, ), - if (!defaultPowerLevels.contains(user.powerLevel)) + if (!defaultPowerLevels.contains(user.powerLevel.level)) Text(' (${user.powerLevel})'), ], ), @@ -219,8 +219,8 @@ Future showMemberActionsPopupMenu({ case _MemberActions.setPowerLevel: final power = await showPermissionChooser( context, - currentLevel: user.powerLevel, - maxLevel: user.room.ownPowerLevel, + currentLevel: user.powerLevel.level, + maxLevel: user.room.ownPowerLevel.level, ); if (power == null) return; if (!context.mounted) return; @@ -323,7 +323,7 @@ Future showMemberActionsPopupMenu({ ); } case _MemberActions.makeAdmin: - if (user.room.ownPowerLevel <= 100) { + if (user.room.ownPowerLevel.level <= 100) { final consent = await showOkCancelAlertDialog( context: context, title: L10n.of(context).areYouSure, diff --git a/pubspec.lock b/pubspec.lock index d0167bb3..8e6e7125 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1072,10 +1072,10 @@ packages: dependency: "direct main" description: name: matrix - sha256: "5bb38e98212bc4c3244c762a1af787f7239a38d2cfdf44488258283ff899f77c" + sha256: "0da5f65016c704bda81eae807cdadc18a046a444fa7e5cec83e60e5d04006d17" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "7.0.0" meta: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4b60a5ce..8f5aa735 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,7 +49,7 @@ dependencies: intl: any just_audio: ^0.10.5 latlong2: ^0.9.1 - matrix: ^6.2.0 + matrix: ^7.0.0 mime: ^2.0.0 native_imaging: ^0.4.0 opus_caf_converter_dart: ^1.0.1 From afee95d1999ba0dcaa35451be66fed2d56a6e3bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:04:18 +0000 Subject: [PATCH 71/75] build: (deps): bump go_router from 17.2.1 to 17.2.2 Bumps [go_router](https://github.com/flutter/packages/tree/main/packages) from 17.2.1 to 17.2.2. - [Commits](https://github.com/flutter/packages/commits/go_router-v17.2.2/packages) --- updated-dependencies: - dependency-name: go_router dependency-version: 17.2.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 8e6e7125..602439cd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -755,10 +755,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "5540e4a3f416dd4a93458257b908eb88353cbd0fb5b0a3d1bd7d849ba1e88735" + sha256: "08b742eef4f71c9df5af543751cd0b7f1c679c4088488f4223ecaddc1a813b79" url: "https://pub.dev" source: hosted - version: "17.2.1" + version: "17.2.2" gsettings: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8f5aa735..faaaea17 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,7 +39,7 @@ dependencies: flutter_web_auth_2: ^5.0.2 flutter_webrtc: ^1.4.1 geolocator: ^14.0.2 - go_router: ^17.2.1 + go_router: ^17.2.2 handy_window: ^0.4.2 highlight: ^0.7.0 html: ^0.15.4 From d770a10de37c9ae7adde3ee7fcba68af34351b77 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:04:36 +0000 Subject: [PATCH 72/75] build: (deps): bump desktop_drop from 0.7.0 to 0.7.1 Bumps [desktop_drop](https://github.com/MixinNetwork/flutter-plugins/tree/main/packages) from 0.7.0 to 0.7.1. - [Release notes](https://github.com/MixinNetwork/flutter-plugins/releases) - [Commits](https://github.com/MixinNetwork/flutter-plugins/commits/desktop_drop-v0.7.1/packages) --- updated-dependencies: - dependency-name: desktop_drop dependency-version: 0.7.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 8e6e7125..f666e3ac 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -301,10 +301,10 @@ packages: dependency: "direct main" description: name: desktop_drop - sha256: e70b46b2d61f1af7a81a40d1f79b43c28a879e30a4ef31e87e9c27bea4d784e8 + sha256: aa1e797255bfbc76f9eb5aa4f61e5b68dbf69962ab1be6495816d2f251bc0d1f url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.1" desktop_notifications: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 8f5aa735..c2f5e98c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: chewie: ^1.13.1 collection: ^1.18.0 cross_file: ^0.3.5 - desktop_drop: ^0.7.0 + desktop_drop: ^0.7.1 desktop_notifications: ^0.6.3 device_info_plus: ^12.3.0 dynamic_color: ^1.8.1 From 8f225d3ee7f745dededf853d8b01d8ac49e7735c Mon Sep 17 00:00:00 2001 From: krille-chan Date: Wed, 22 Apr 2026 17:27:20 +0900 Subject: [PATCH 73/75] build: Update to flutter 3.41.7 --- .tool_versions.yaml | 2 +- snap/snapcraft.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.tool_versions.yaml b/.tool_versions.yaml index 54f80e87..767dda76 100644 --- a/.tool_versions.yaml +++ b/.tool_versions.yaml @@ -1,2 +1,2 @@ environment: - flutter: 3.41.6 # Keep in sync with snap/snapcraft.yaml \ No newline at end of file + flutter: 3.41.7 # Keep in sync with snap/snapcraft.yaml \ No newline at end of file diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 273b9902..4a22975c 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -53,7 +53,7 @@ platforms: parts: flutter-git: source: https://github.com/flutter/flutter.git - source-tag: 3.41.6 + source-tag: 3.41.7 source-depth: 1 plugin: nil override-build: | From 3d98cb52219118d5c2f564aa53cc942edacdb965 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Wed, 22 Apr 2026 17:28:18 +0900 Subject: [PATCH 74/75] chore: Update Pull Request template --- .github/pull_request_template.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7f4e66ec..26504d65 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,5 +1,3 @@ -*Thank you so much for your contribution to FluffyChat ❤️❤️❤️* - - [ ] I have read and understood the [contributing guidelines](https://github.com/krille-chan/fluffychat/blob/main/CONTRIBUTING.md). ### Pull Request has been tested on: From 144fbb48723a5ec1ffbd03c14caab5089c6628d3 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Wed, 22 Apr 2026 17:34:33 +0900 Subject: [PATCH 75/75] chore: Follow up summary notification autoCancel false --- lib/utils/push_helper.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/utils/push_helper.dart b/lib/utils/push_helper.dart index 7ca810a8..b2e8c486 100644 --- a/lib/utils/push_helper.dart +++ b/lib/utils/push_helper.dart @@ -333,6 +333,7 @@ Future _tryPushHelper( contentTitle: title, summaryText: title, ), + autoCancel: false, ), ), );