diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index af0a4e863..485bfd8c5 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -1,6 +1,6 @@
name: 🐛 Bug report
description: Create a report to help us improve
-labels: ["Bug"]
+labels: bug
body:
- type: textarea
id: bug-description
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index c91153caa..ef50c0447 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,8 +1,5 @@
blank_issues_enabled: false
contact_links:
- - name: FluffyChat Community
+ - name: 👬 FluffyChat Community
url: https://matrix.to/#/#fluffychat:matrix.org
- about: Please ask and answer questions here.
- - name: Report security vulnerabilities
- url: https://matrix.to/#/@krille:janian.de
- about: Please report security vulnerabilities here.
+ about: Please ask and answer questions here.
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index a29949d66..4007c8837 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -1,6 +1,6 @@
name: 💡 Feature Request
description: Suggest an idea for this project
-labels: ["Enhancement"]
+labels: enhancement
body:
- type: textarea
id: feature-description
diff --git a/.github/ISSUE_TEMPLATE/test_report.md b/.github/ISSUE_TEMPLATE/test_report.md
new file mode 100644
index 000000000..beda783fb
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/test_report.md
@@ -0,0 +1,41 @@
+---
+name: 📝 Test
+about: A detailed protocol for testing all features
+title: 'Test Report'
+label: test
+---
+
+1. App receives push notifications over Firebase Cloud Messaging when it is in background/terminated:
+ - [ ] Android
+ - [ ] iOS
+2. App receives push notifications over Unified Push when it is in background/terminated:
+ - [ ] Android
+3. Notifications for rooms, which are not in foreground, are working:
+ - [ ] Web
+ - [ ] Linux
+4. QR Code scanner can still scan links to start a new chat:
+ - [ ] Android
+ - [ ] iOS
+5. Recording and playing voice messages works:
+ - [ ] Android
+ - [ ] iOS
+ - [ ] Web (play only)
+6. Sending and downloading files/images works:
+ - [ ] Android
+ - [ ] iOS
+ - [ ] Web
+ - [ ] Linux
+7. Sharing texts/files/images from other apps to FluffyChat works:
+ - [ ] Android
+ - [ ] iOS
+8. Login with single sign on works:
+ - [ ] Android
+ - [ ] iOS
+ - [ ] Web
+ - [ ] Linux
+9. Test if the app lock works as intended and appears on opening/resuming the app:
+ - [ ] Android
+ - [ ] iOS
+10. Drag&Drop to send a file into a chat still works:
+ - [ ] Web
+ - [ ] Linux
diff --git a/.github/workflows/auto_merge.yaml b/.github/workflows/auto_merge.yaml
deleted file mode 100644
index 2dab06c40..000000000
--- a/.github/workflows/auto_merge.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-name: Auto merge
-
-on: pull_request_target
-
-jobs:
- auto-approve:
- runs-on: ubuntu-latest
- permissions:
- pull-requests: write
- if: github.actor == 'dependabot[bot]' || github.actor == 'weblate'
- steps:
- - uses: hmarr/auto-approve-action@v3
- - name: automerge
- uses: "pascalgn/automerge-action@v0.15.6"
- env:
- GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
diff --git a/.github/workflows/issue_pr_management.yaml b/.github/workflows/issue_pr_management.yaml
new file mode 100644
index 000000000..490c9a91c
--- /dev/null
+++ b/.github/workflows/issue_pr_management.yaml
@@ -0,0 +1,26 @@
+name: Close Inactive Issues And PRs
+on:
+ schedule:
+ - cron: "30 1 * * *"
+
+jobs:
+ close-issues:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+ steps:
+ - uses: actions/stale@v5
+ with:
+ days-before-issue-stale: 30
+ days-before-issue-close: 14
+ stale-issue-label: "stale"
+ stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
+ close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
+ stale-pr-message: "This pull request is stale because it has been open for 30 days with no activity."
+ close-pr-message: "This pull request was closed because it has been inactive for 14 days since being marked as stale."
+ days-before-pr-stale: 30
+ days-before-pr-close: 14
+ exempt-milestones: true
+ exempt-assignees: krille-chan
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/main_deploy.yaml b/.github/workflows/main_deploy.yaml
index f8603f30e..a29d49a92 100644
--- a/.github/workflows/main_deploy.yaml
+++ b/.github/workflows/main_deploy.yaml
@@ -25,7 +25,7 @@ jobs:
- name: Prepare web
run: ./scripts/prepare-web.sh
- name: Build Release Web
- run: flutter build web --release --verbose --source-maps --base-href "/web/"
+ run: flutter build web --dart-define=FLUTTER_WEB_CANVASKIT_URL=canvaskit/ --release --source-maps --base-href "/web/"
- name: Build Website
run: |
cd docs && npx tailwindcss -o ./tailwind.css --minify && cd ..
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index e8b4af2bb..5f950ea01 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -25,7 +25,7 @@ jobs:
- name: Prepare web
run: ./scripts/prepare-web.sh
- name: Build Release Web
- run: flutter build web --release --source-maps
+ run: flutter build web --dart-define=FLUTTER_WEB_CANVASKIT_URL=canvaskit/ --release --source-maps
- name: Create archive
run: tar -czf fluffychat-web.tar.gz build/web/
- name: Upload Web Build
diff --git a/.github/workflows/versions.env b/.github/workflows/versions.env
index 30e7fdfb8..7ad54f1fe 100644
--- a/.github/workflows/versions.env
+++ b/.github/workflows/versions.env
@@ -1,2 +1,2 @@
-FLUTTER_VERSION=3.13.7
+FLUTTER_VERSION=3.13.9
JAVA_VERSION=17
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 69dd95a8c..97c360d4e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,42 @@
+## v1.15.0
+- feat: Add experimental todo list for rooms (krille-chan)
+- feat: better scroll to last read message handling (krille-chan)
+- build: Add appid suffix to android debug builds (krille-chan)
+- build: Download canvaskit on build for flutter web (krille-chan)
+- build: Update to Flutter 3.13.9 (krille-chan)
+- chore: Add descriptions in the areYouSure dialogs for better UX (krille-chan)
+- chore: Adjust bitrate for smaller voice messages (krille-chan)
+- chore: Change way how to seek in audioplayer (Krille)
+- chore: Limit image file and video picker until we have a background service (krille-chan)
+- chore: Minor design fixes (Krille)
+- design: Make incoming messages color more light (krille-chan)
+- design: Make key verification an adaptive dialog (krille-chan)
+- design: Make own chat bubble primary color for better contrast (krille-chan)
+- fix: Create chat dialog crashes sometimes and power level textfield does not validate input (krille-chan)
+- fix: Remove uncompatible dependencies connectivity_plus and wakelock (Krille)
+- fix: Use correct localization for redactedBy (krille-chan)
+- fix: noFCM warning dialog (krille-chan)
+- fix: render tg-forward as blockquote style (krille-chan)
+- fix: Archive does not update its state
+- refactor: Change audio codec to opus where supported to have better compatibility with Element (Krille)
+- refactor: Make file dialog adaptive and adjust design (krille-chan)
+- refactor: Preload notification sound on web (Krille)
+- refactor: Remove unused config (krille-chan)
+- refactor: Remove unused config params (krille-chan)
+- refactor: Update FutureLoadingDialog (krille-chan)
+- refactor: use locally hosted canvaskit instead of calling google (root)
+- Translated using Weblate (Arabic) (Rex_sa)
+- Translated using Weblate (Basque) (xabirequejo)
+- Translated using Weblate (Chinese (Simplified)) (Eric)
+- Translated using Weblate (Croatian) (Milo Ivir)
+- Translated using Weblate (German) (Christian)
+- Translated using Weblate (German) (Ettore Atalan)
+- Translated using Weblate (Hungarian) (H Tamás)
+- Translated using Weblate (Polish) (Tomasz W)
+- Translated using Weblate (Russian) (v1s7)
+- Translated using Weblate (Slovak) (Jozef Gaal)
+- Translated using Weblate (Thai) (Amy/Atius)
+
## v1.14.5
- Hotfix iOS crashes on start
- Hotfix cannot reset applock
diff --git a/Dockerfile b/Dockerfile
index 7a696ce54..7859f2468 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,7 +4,7 @@ COPY . /app
WORKDIR /app
RUN ./scripts/prepare-web.sh
RUN flutter pub get
-RUN flutter build web --release --source-maps
+RUN flutter build web --dart-define=FLUTTER_WEB_CANVASKIT_URL=canvaskit/ --release --source-maps
FROM docker.io/nginx:alpine
RUN rm -rf /usr/share/nginx/html
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 4f22c6aa2..b6ebfed52 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -33,7 +33,7 @@ if (keystorePropertiesFile.exists()) {
}
android {
- compileSdkVersion 33
+ compileSdkVersion 34
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
@@ -66,6 +66,8 @@ android {
buildTypes {
debug {
signingConfig signingConfigs.debug
+ applicationIdSuffix ".debug"
+ versionNameSuffix "-debug"
}
release {
signingConfig signingConfigs.release
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
deleted file mode 100644
index be6ce4442..000000000
--- a/android/app/src/debug/AndroidManifest.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 06dd48c2b..9e52b2a59 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -99,6 +99,11 @@
+
+
+
+
+
-
-
-
diff --git a/assets/l10n/intl_ar.arb b/assets/l10n/intl_ar.arb
index 0fe4b02d1..4aca37af8 100644
--- a/assets/l10n/intl_ar.arb
+++ b/assets/l10n/intl_ar.arb
@@ -771,7 +771,7 @@
"type": "text",
"placeholders": {}
},
- "noGoogleServicesWarning": "يبدو أنك لا تستخدم خدمات غوغل على هاتفك. هذا قرار جيد للحفاظ على خصوصيتك! من أجل استلام الإشعارات في FluffyChat نقترح استخدام https://microg.org أو https://unifiedpush.org.",
+ "noGoogleServicesWarning": "يبدو أن خدمة Firebase Cloud Messaging غير متاحة على جهازك. لمواصلة تلقي الإشعارات، نوصي بتثبيت ntfy. باستخدام ntfy أو أي مزود خدمة Unified Push آخر، يمكنك تلقي إشعارات الدفع بطريقة آمنة للبيانات. يمكنك تنزيل ntfy من PlayStore أو من F-Droid.",
"@noGoogleServicesWarning": {
"type": "text",
"placeholders": {}
@@ -2619,5 +2619,45 @@
"placeholders": {
"seconds": {}
}
- }
+ },
+ "hasKnocked": "لقد طرق {user}",
+ "@hasKnocked": {
+ "placeholders": {
+ "user": {}
+ }
+ },
+ "pleaseEnterANumber": "الرجاء إدخال رقم أكبر من 0",
+ "@pleaseEnterANumber": {},
+ "banUserDescription": "سيتم حظر المستخدم من الدردشة ولن يتمكن من الدخول إلى الدردشة مرة أخرى حتى يتم رفع الحظر عنه.",
+ "@banUserDescription": {},
+ "removeDevicesDescription": "سيتم تسجيل خروجك من هذا الجهاز ولن تتمكن بعد ذلك من تلقي الرسائل.",
+ "@removeDevicesDescription": {},
+ "unbanUserDescription": "سيتمكن المستخدم من الدخول إلى الدردشة مرة أخرى إذا حاول.",
+ "@unbanUserDescription": {},
+ "todoLists": "(اختباري) لقوائم المهام",
+ "@todoLists": {},
+ "editTodo": "تعديل المهام",
+ "@editTodo": {},
+ "pushNotificationsNotAvailable": "دفع الإخطارات غير متوفرة",
+ "@pushNotificationsNotAvailable": {},
+ "pleaseAddATitle": "يرجى إضافة عنوان",
+ "@pleaseAddATitle": {},
+ "makeAdminDescription": "بمجرد تعيين هذا المستخدم كمسؤول، قد لا تتمكن من التراجع عن هذا لأنه سيكون لديه نفس الأذونات التي تتمتع بها.",
+ "@makeAdminDescription": {},
+ "noTodosYet": "لم تتم إضافة أي مهام إلى هذه الدردشة حتى الآن. أنشئ أول ما يجب عليك فعله وابدأ في التعاون مع الآخرين. 📝",
+ "@noTodosYet": {},
+ "archiveRoomDescription": "سيتم نقل الدردشة إلى الأرشيف. سيتمكن المستخدمون الآخرون من رؤية أنك غادرت الدردشة.",
+ "@archiveRoomDescription": {},
+ "todosUnencrypted": "يرجى ملاحظة أن جميع المهام مرئية للجميع في الدردشة وليست مشفرة تمامًا.",
+ "@todosUnencrypted": {},
+ "newTodo": "ما يجب القيام به جديد",
+ "@newTodo": {},
+ "learnMore": "تعلم المزيد",
+ "@learnMore": {},
+ "todoListChangedError": "عفوًا... لقد تم تغيير قائمة المهام أثناء قيامك بتحريرها.",
+ "@todoListChangedError": {},
+ "roomUpgradeDescription": "سيتم بعد ذلك إعادة إنشاء الدردشة باستخدام إصدار الغرفة الجديد. سيتم إخطار جميع المشاركين بأنهم بحاجة إلى التبديل إلى الدردشة الجديدة. يمكنك معرفة المزيد حول إصدارات الغرف على https://spec.matrix.org/latest/rooms/",
+ "@roomUpgradeDescription": {},
+ "kickUserDescription": "يتم طرد المستخدم من الدردشة ولكن لا يتم حظره. في الدردشات العامة، يمكن للمستخدم الانضمام مرة أخرى في أي وقت.",
+ "@kickUserDescription": {}
}
diff --git a/assets/l10n/intl_de.arb b/assets/l10n/intl_de.arb
index 077f0efd3..e472d80ec 100644
--- a/assets/l10n/intl_de.arb
+++ b/assets/l10n/intl_de.arb
@@ -1159,7 +1159,7 @@
"type": "text",
"placeholders": {}
},
- "noGoogleServicesWarning": "Es sieht so aus, als hättest du keine Google-Dienste auf deinem Gerät. Das ist eine gute Entscheidung für deine Privatsphäre! Um Push-Benachrichtigungen in FluffyChat zu erhalten, empfehlen wir die Verwendung von microG https://microg.org/ oder Unified Push https://unifiedpush.org/.",
+ "noGoogleServicesWarning": "Firebase Cloud Messaging scheint auf deinem Gerät nicht verfügbar zu sein. Um trotzdem Push-Benachrichtigungen zu erhalten, empfehlen wir die Installation von ntfy. Mit ntfy oder einem anderen Unified Push Anbieter kannst du Push-Benachrichtigungen datensicher empfangen. Du kannst ntfy im PlayStore oder bei F-Droid herunterladen.",
"@noGoogleServicesWarning": {
"type": "text",
"placeholders": {}
@@ -1888,7 +1888,7 @@
"type": "text",
"placeholders": {}
},
- "verify": "Bestätigen",
+ "verify": "Verifizieren",
"@verify": {
"type": "text",
"placeholders": {}
@@ -2211,7 +2211,7 @@
"@openChat": {},
"confirmEventUnpin": "Möchtest du das Ereignis wirklich dauerhaft lösen?",
"@confirmEventUnpin": {},
- "dismiss": "Ablehnen",
+ "dismiss": "Verwerfen",
"@dismiss": {},
"switchToAccount": "Zu Konto {number} wechseln",
"@switchToAccount": {
@@ -2607,5 +2607,57 @@
"invitePrivateChat": "📨 Einladungen zum privaten Chat",
"@invitePrivateChat": {},
"invalidInput": "Ungültige Eingabe!",
- "@invalidInput": {}
+ "@invalidInput": {},
+ "hasKnocked": "{user} hat angeklopft",
+ "@hasKnocked": {
+ "placeholders": {
+ "user": {}
+ }
+ },
+ "wrongPinEntered": "Falsche PIN eingegeben! Bitte in {seconds} Sekunden erneut versuchen ...",
+ "@wrongPinEntered": {
+ "type": "text",
+ "placeholders": {
+ "seconds": {}
+ }
+ },
+ "pleaseEnterANumber": "Bitte eine Zahl größer 0 eingeben",
+ "@pleaseEnterANumber": {},
+ "emoteKeyboardNoRecents": "Kürzlich verwendete Emotes werden hier angezeigt ...",
+ "@emoteKeyboardNoRecents": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "banUserDescription": "Der Benutzer wird aus dem Chat gebannt und kann den Chat erst wieder betreten, wenn die Verbannung aufgehoben wird.",
+ "@banUserDescription": {},
+ "removeDevicesDescription": "Du wirst von diesem Gerät abgemeldet und kannst dann dort keine Nachrichten mehr empfangen.",
+ "@removeDevicesDescription": {},
+ "unbanUserDescription": "Der Benutzer kann den Chat dann wieder betreten, wenn er es versucht.",
+ "@unbanUserDescription": {},
+ "todoLists": "(Beta) Aufgabenlisten",
+ "@todoLists": {},
+ "editTodo": "Aufgabe bearbeiten",
+ "@editTodo": {},
+ "pushNotificationsNotAvailable": "Push-Benachrichtigungen nicht verfügbar",
+ "@pushNotificationsNotAvailable": {},
+ "pleaseAddATitle": "Bitte einen Titel hinzufügen",
+ "@pleaseAddATitle": {},
+ "makeAdminDescription": "Sobald du diesen Benutzer zum Administrator gemacht hast, kannst du das möglicherweise nicht mehr rückgängig machen, da er dann über dieselben Berechtigungen wie du verfügt.",
+ "@makeAdminDescription": {},
+ "noTodosYet": "Zu diesem Chat wurden noch keine Aufgaben hinzugefügt. Erstellen die erste Aufgabe und fange an, mit anderen zusammenzuarbeiten. 📝",
+ "@noTodosYet": {},
+ "archiveRoomDescription": "Der Chat wird in das Archiv verschoben. Andere Benutzer können sehen, dass du den Chat verlassen hast.",
+ "@archiveRoomDescription": {},
+ "newTodo": "Neue Aufgabe",
+ "@newTodo": {},
+ "learnMore": "Erfahre mehr",
+ "@learnMore": {},
+ "todoListChangedError": "Hoppla ... Die Aufgabenliste wurde geändert, während Sie sie bearbeitet haben.",
+ "@todoListChangedError": {},
+ "roomUpgradeDescription": "Der Chat wird dann mit der neuen Raumversion neu erstellt. Alle Teilnehmer werden benachrichtigt, dass sie zum neuen Chat wechseln müssen. Mehr über Raumversionen erfährst du unter https://spec.matrix.org/latest/rooms/",
+ "@roomUpgradeDescription": {},
+ "kickUserDescription": "Der Benutzer wird aus dem Chat geworfen, aber nicht gebannt. In öffentlichen Chats kann der Benutzer jederzeit wieder beitreten.",
+ "@kickUserDescription": {},
+ "todosUnencrypted": "Bitte beachte, dass Todos für jeden im Chat sichtbar und nicht Ende zu Ende verschlüsselt sind.",
+ "@todosUnencrypted": {}
}
diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb
index dffe8be24..a0b100845 100644
--- a/assets/l10n/intl_en.arb
+++ b/assets/l10n/intl_en.arb
@@ -1324,7 +1324,7 @@
"type": "text",
"placeholders": {}
},
- "noGoogleServicesWarning": "It seems that you have no google services on your phone. That's a good decision for your privacy! To receive push notifications in FluffyChat we recommend using https://microg.org/ or https://unifiedpush.org/.",
+ "noGoogleServicesWarning": "Firebase Cloud Messaging doesn't appear to be available on your device. To still receive push notifications, we recommend installing ntfy. With ntfy or another Unified Push provider you can receive push notifications in a data secure way. You can download ntfy from the PlayStore or from F-Droid.",
"@noGoogleServicesWarning": {
"type": "text",
"placeholders": {}
@@ -3906,5 +3906,22 @@
"banUserDescription": "The user will be banned from the chat and will not be able to enter the chat again until they are unbanned.",
"unbanUserDescription": "The user will be able to enter the chat again if they try.",
"kickUserDescription": "The user is kicked out of the chat but not banned. In public chats, the user can rejoin at any time.",
- "makeAdminDescription": "Once you make this user admin, you may not be able to undo this as they will then have the same permissions as you."
+ "makeAdminDescription": "Once you make this user admin, you may not be able to undo this as they will then have the same permissions as you.",
+ "pleaseEnterANumber": "Please enter a number greater than 0",
+ "archiveRoomDescription": "The chat will be moved to the archive. Other users will be able to see that you have left the chat.",
+ "roomUpgradeDescription": "The chat will then be recreated with the new room version. All participants will be notified that they need to switch to the new chat. You can find out more about room versions at https://spec.matrix.org/latest/rooms/",
+ "removeDevicesDescription": "You will be logged out of this device and will no longer be able to receive messages.",
+ "banUserDescription": "The user will be banned from the chat and will not be able to enter the chat again until they are unbanned.",
+ "unbanUserDescription": "The user will be able to enter the chat again if they try.",
+ "kickUserDescription": "The user is kicked out of the chat but not banned. In public chats, the user can rejoin at any time.",
+ "makeAdminDescription": "Once you make this user admin, you may not be able to undo this as they will then have the same permissions as you.",
+ "pushNotificationsNotAvailable": "Push notifications not available",
+ "learnMore": "Learn more",
+ "todoLists": "(Beta) Todolists",
+ "newTodo": "New todo",
+ "noTodosYet": "No todos have been added to this chat yet. Create your first todo and start cooperating with others. 📝",
+ "editTodo": "Edit todo",
+ "pleaseAddATitle": "Please add a title",
+ "todoListChangedError": "Oops... The todo list has been changed while you edited it.",
+ "todosUnencrypted": "Please notice that todos are visible by everyone in the chat and are not end to end encrypted."
}
diff --git a/assets/l10n/intl_et.arb b/assets/l10n/intl_et.arb
index 4655b1373..39920bec7 100644
--- a/assets/l10n/intl_et.arb
+++ b/assets/l10n/intl_et.arb
@@ -1164,7 +1164,7 @@
"type": "text",
"placeholders": {}
},
- "noGoogleServicesWarning": "Tundub, et sinu nutiseadmes pole Google teenuseid. Sinu privaatsuse mõttes on see kindlasti hea otsus! Kui sa soovid FluffyChat'is näha tõuketeavitusi, siis soovitame, et selle jaoks kasutad https://microg.org või https://unifiedpush.org liidestust.",
+ "noGoogleServicesWarning": "Tundub, et sinu nutiseadmes pole Firebase Cloud Messaging teenuseid. Sinu privaatsuse mõttes on see kindlasti hea otsus! Kui sa soovid FluffyChat'is näha tõuketeavitusi, siis soovitame, et selle jaoks kasutad ntfy liidestust. Kasutades ntfy'd või mõnda muud Unified Push standardil põhinevat liidestust saad tõuketeavitusi turvalisel moel. Ntfy rakendus on saadaval nii PlayStore kui F-Droid'i rakendusepoodides.",
"@noGoogleServicesWarning": {
"type": "text",
"placeholders": {}
@@ -2617,5 +2617,45 @@
"placeholders": {
"seconds": {}
}
- }
+ },
+ "banUserDescription": "Sellele kasutajale on nüüd selles jututoas seatud suhtluskeeld ning ta ei saa vestluses osaleda seni, kuni suhtluskeeld pole eemaldatud.",
+ "@banUserDescription": {},
+ "removeDevicesDescription": "Sind logitakse sellest seadmest välja ja sa enam ei saa sõnumeid.",
+ "@removeDevicesDescription": {},
+ "unbanUserDescription": "Uuesti proovimisel saab see kasutaja nüüd vestlusega liituda.",
+ "@unbanUserDescription": {},
+ "todoLists": "Tegemiste loendid (beetaversioon)",
+ "@todoLists": {},
+ "editTodo": "Muuda tegevust",
+ "@editTodo": {},
+ "pushNotificationsNotAvailable": "Tõuketeavitused pole saadaval",
+ "@pushNotificationsNotAvailable": {},
+ "pleaseAddATitle": "Palun lisa pealkiri",
+ "@pleaseAddATitle": {},
+ "makeAdminDescription": "Kui annad sellele kasutajale peakasutaja õigused, siis kuna tal on sinuga samad õigused, sa ei saa seda toimingut enam tagasi pöörata.",
+ "@makeAdminDescription": {},
+ "noTodosYet": "Siia vestlusesse pole veel lisatud ühtegi tegemiste loendit. Tee esimene ja jätka selle täitmist üheskoos teistega. 📝",
+ "@noTodosYet": {},
+ "archiveRoomDescription": "Selle vestluse tõstame nüüd arhiivi. Muud osalejad näevad, et sa oled vestlusest lahkunud.",
+ "@archiveRoomDescription": {},
+ "hasKnocked": "{user} on jututoa uksele koputanud",
+ "@hasKnocked": {
+ "placeholders": {
+ "user": {}
+ }
+ },
+ "newTodo": "Uus tegevus",
+ "@newTodo": {},
+ "learnMore": "Loe lisaks",
+ "@learnMore": {},
+ "todoListChangedError": "Hopsti... Seda tegevuste loendit on keegi muutnud just samal ajal, kui sina olid seda muutmas.",
+ "@todoListChangedError": {},
+ "roomUpgradeDescription": "See vestlus luuakse nüüd uuesti jututoa uue versioonina. Kõik senised osalejad saavad teate, et nad peavad liituma uue vestlusega. Jututubade versioonide kohta leiad teavet https://spec.matrix.org/latest/rooms/ lehelt",
+ "@roomUpgradeDescription": {},
+ "pleaseEnterANumber": "Palun sisesta 0'st suurem number",
+ "@pleaseEnterANumber": {},
+ "kickUserDescription": "See kasutaja on nüüd jutuoast välja müksatud, kuid talle pole seatud suhtluskeeldu. Avaliku jututoa puhul saab ta alati uuesti liituda.",
+ "@kickUserDescription": {},
+ "todosUnencrypted": "Palun arvesta, et tegemiste loend on nähtav kõikidele vestluses osalejatele ja pole läbivalt krüptitud.",
+ "@todosUnencrypted": {}
}
diff --git a/assets/l10n/intl_eu.arb b/assets/l10n/intl_eu.arb
index e418d2b22..ea4bfdb3b 100644
--- a/assets/l10n/intl_eu.arb
+++ b/assets/l10n/intl_eu.arb
@@ -84,7 +84,7 @@
"username": {}
}
},
- "banFromChat": "Kanporatu txatetik",
+ "banFromChat": "Txatera batzeko debekua ezarri",
"@banFromChat": {
"type": "text",
"placeholders": {}
@@ -226,7 +226,7 @@
"type": "text",
"placeholders": {}
},
- "changeTheNameOfTheGroup": "Aldatu taldearen izena",
+ "changeTheNameOfTheGroup": "Taldearen izena aldatu",
"@changeTheNameOfTheGroup": {
"type": "text",
"placeholders": {}
@@ -372,7 +372,7 @@
"type": "text",
"placeholders": {}
},
- "deleteMessage": "Ezabatu mezua",
+ "deleteMessage": "Mezuak ezabatu",
"@deleteMessage": {
"type": "text",
"placeholders": {}
@@ -521,12 +521,12 @@
"displayname": {}
}
},
- "guestsAreForbidden": "Bisitariak debekatuta daude",
+ "guestsAreForbidden": "Ez, bisitariak ez daude baimenduta",
"@guestsAreForbidden": {
"type": "text",
"placeholders": {}
},
- "guestsCanJoin": "Bisitariak batu daitezke",
+ "guestsCanJoin": "Bai, bisitariak batu daitezke",
"@guestsCanJoin": {
"type": "text",
"placeholders": {}
@@ -559,7 +559,7 @@
"type": "text",
"placeholders": {}
},
- "inviteContact": "Gonbidatu kontaktua",
+ "inviteContact": "Kontaktuak gonbidatu",
"@inviteContact": {
"type": "text",
"placeholders": {}
@@ -630,7 +630,7 @@
"targetName": {}
}
},
- "kickFromChat": "Kanporatu txatetik",
+ "kickFromChat": "Txatetik kanporatu",
"@kickFromChat": {
"type": "text",
"placeholders": {}
@@ -746,7 +746,7 @@
"type": "text",
"placeholders": {}
},
- "noGoogleServicesWarning": "Dirudienez ez daukazu Googleren zerbitzurik zure mugikorrean. Primerako erabakia zure pribatutasunerako! FluffyChaten jakinarazpenak jasotzeko https://microg.org/ edo https://unifiedpush.org/ erabiltzea gomendatzen dugu.",
+ "noGoogleServicesWarning": "Dirudienez Firebase Cloud Messaging ez dago erabilgarri zure mugikorrean. Jakinarazpenak jasotzeko MicroG edo Unified Push instalatzea gomendatzen dugu.",
"@noGoogleServicesWarning": {
"type": "text",
"placeholders": {}
@@ -1021,7 +1021,7 @@
"type": "text",
"placeholders": {}
},
- "setInvitationLink": "Ezarri gonbidapen-esteka",
+ "setInvitationLink": "Gonbidapen-esteka ezarri",
"@setInvitationLink": {
"type": "text",
"placeholders": {}
@@ -1364,7 +1364,7 @@
"type": "text",
"placeholders": {}
},
- "editRoomAvatar": "Editatu gelaren abatarra",
+ "editRoomAvatar": "Gelaren abatarra editatu",
"@editRoomAvatar": {
"type": "text",
"placeholders": {}
@@ -1697,7 +1697,7 @@
},
"chatHasBeenAddedToThisSpace": "Txata gune honetara gehitu da",
"@chatHasBeenAddedToThisSpace": {},
- "configureChat": "Konfiguratu txata",
+ "configureChat": "Txata konfiguratu",
"@configureChat": {
"type": "text",
"placeholders": {}
@@ -2101,7 +2101,7 @@
"@sendAsText": {
"type": "text"
},
- "sendMessages": "Bidali mezuak",
+ "sendMessages": "Mezuak bidali",
"@sendMessages": {
"type": "text",
"placeholders": {}
@@ -2166,7 +2166,7 @@
"type": "text",
"placeholders": {}
},
- "unverified": "Egiaztatu gabe",
+ "unverified": "Egiaztatu gabe(a)",
"@unverified": {},
"verified": "Egiaztatuta",
"@verified": {
@@ -2569,7 +2569,7 @@
"reason": {}
}
},
- "anyoneCanKnock": "Edonork egin dezake batzeko eskaera",
+ "anyoneCanKnock": "Edonork eska dezake batzeko baimena",
"@anyoneCanKnock": {},
"redactMessageDescription": "Mezua elkarrizketa honetako partaide guztientzat botako da atzera. Ezin da desegin.",
"@redactMessageDescription": {},
@@ -2605,10 +2605,50 @@
"@createGroup": {},
"invite": "Gonbidatu",
"@invite": {},
- "invalidInput": "Sarrerak ez du balio!",
+ "invalidInput": "Sartu duzunak ez du balio!",
"@invalidInput": {},
"inviteGroupChat": "📨 Gonbidatu taldeko txatera",
"@inviteGroupChat": {},
"invitePrivateChat": "📨 Gonbidatu txat pribatura",
- "@invitePrivateChat": {}
+ "@invitePrivateChat": {},
+ "banUserDescription": "Erabiltzailea txatetik kanporatu eta berriro sartzeko debekua ezarriko zaio; ezingo da berriro sartu debekua kendu arte.",
+ "@banUserDescription": {},
+ "removeDevicesDescription": "Gailu honetako saioa amaituko da eta ezingo duzu mezurik jaso aurrerantzean.",
+ "@removeDevicesDescription": {},
+ "unbanUserDescription": "Erabiltzailea txatera berriro sartu ahal izango da berak nahi izanez gero.",
+ "@unbanUserDescription": {},
+ "todoLists": "(Beta) Zeregin-zerrendak",
+ "@todoLists": {},
+ "editTodo": "Editatu zeregina",
+ "@editTodo": {},
+ "pushNotificationsNotAvailable": "Push jakinarazpenak ez daude erabilgarri",
+ "@pushNotificationsNotAvailable": {},
+ "pleaseAddATitle": "Gehitu izenburua",
+ "@pleaseAddATitle": {},
+ "makeAdminDescription": "Behin erabiltzaile hau administratzaile eginda, litekeena da desegin ezin izatea zuk dituzun baimenak izango dituelako.",
+ "@makeAdminDescription": {},
+ "noTodosYet": "Oraindik ez da zereginik gehitu txat honetara. Sortu lehen zeregina eta hasi elkarlanean besteekin. 📝",
+ "@noTodosYet": {},
+ "archiveRoomDescription": "Txata artxibategira mugituko da. Beste erabiltzaileek txatetik alde egin duzula ikusi ahal izango dute.",
+ "@archiveRoomDescription": {},
+ "hasKnocked": "{user}(e)k baimena eskatu du",
+ "@hasKnocked": {
+ "placeholders": {
+ "user": {}
+ }
+ },
+ "newTodo": "Zeregin berria",
+ "@newTodo": {},
+ "learnMore": "Gehiago irakurri",
+ "@learnMore": {},
+ "todoListChangedError": "Hara… zeregin-zerrenda aldatu da editatzen ari zinen bitartean.",
+ "@todoListChangedError": {},
+ "roomUpgradeDescription": "Gela bertsio berri gisa birsortuko da txata. Partaide guztiei jakinaraziko zaie txat berrira aldatu behar direla. Gehiago irakur dezakezu gela bertsioei buruz ondorengo estekan: https://spec.matrix.org/latest/rooms/",
+ "@roomUpgradeDescription": {},
+ "pleaseEnterANumber": "Sartu 0 baino zenbaki handiago bat",
+ "@pleaseEnterANumber": {},
+ "kickUserDescription": "Erabiltzailea txatetik kanporatu da baina ez zaio debekua ezarri. Txat publikoen kasuan, edozein momentutan batu daiteke berriro.",
+ "@kickUserDescription": {},
+ "todosUnencrypted": "Kontuan izan zereginak txateko guztientzat daudela ikusgai, eta ez daudela ertzetik ertzera zifratuta.",
+ "@todosUnencrypted": {}
}
diff --git a/assets/l10n/intl_fi.arb b/assets/l10n/intl_fi.arb
index b0aceb2de..0b3839887 100644
--- a/assets/l10n/intl_fi.arb
+++ b/assets/l10n/intl_fi.arb
@@ -764,7 +764,7 @@
"type": "text",
"placeholders": {}
},
- "inviteText": "{username} kutsui sinutFluffyChattiin. \n1. Asenna FluffyChat osoitteesta: https://fluffychat.im \n2. Rekisteröidy tai kirjaudu sisään\n3. Avaa kutsulinkki: {link}",
+ "inviteText": "{username} kutsui sinut FluffyChattiin.\n1. Viereaile sivulla: https://fluffychat.im ja asenna sovellus\n2. Rekisteröidy tai kirjaudu sisään\n3. Avaa kutsulinkki:\n{link}",
"@inviteText": {
"type": "text",
"placeholders": {
@@ -1249,7 +1249,7 @@
"type": "text",
"placeholders": {}
},
- "noGoogleServicesWarning": "Vaikuttaa siltä, ettei puhelimessasi ole Google-palveluita. Se on hyvä päätös yksityisyytesi kannalta! Vastaanottaaksesi push-notifikaatioita FluffyChätissä suosittelemme https://microg.org/ tai https://unifiedpush.org/ käyttämistä.",
+ "noGoogleServicesWarning": "Firebase Cloud Messaging -palvelu ei vaikuta olevan saatavilla laitteellasi. Saadaksesi push-ilmoituksia silti, suosittelemme Ntfy-sovelluksen asentamista. Käyttämällä Ntfy-sovellusta tai muuta Unified Push -tarjoajaa, saat push-ilmoitukset tietoturvallisella tavalla. Voit ladata Ntfy-sovelluksen Play Kaupasta tai F-Droidista.",
"@noGoogleServicesWarning": {
"type": "text",
"placeholders": {}
@@ -1776,7 +1776,7 @@
"type": "text",
"placeholders": {}
},
- "wallpaper": "Taustakuva",
+ "wallpaper": "Taustakuva:",
"@wallpaper": {
"type": "text",
"placeholders": {}
@@ -2366,7 +2366,7 @@
"@whyIsThisMessageEncrypted": {},
"noKeyForThisMessage": "Tämä voi tapahtua mikäli viesti lähetettiin ennen sisäänkirjautumistasi tälle laitteelle.\n\nOn myös mahdollista, että lähettäjä on estänyt tämän laitteen tai jokin meni pieleen verkkoyhteyden kanssa.\n\nPystytkö lukemaan viestin toisella istunnolla? Siinä tapauksessa voit siirtää viestin siltä! Mene Asetukset > Laitteet ja varmista, että laitteesi ovat varmistaneet toisensa. Seuraavankerran avatessasi huoneen ja molempien istuntojen ollessa etualalla, avaimet siirretään automaattisesti.\n\nHaluatko varmistaa ettet menetä avaimia uloskirjautuessa tai laitteita vaihtaessa? Varmista avainvarmuuskopion käytössäolo asetuksista.",
"@noKeyForThisMessage": {},
- "commandHint_markasdm": "Merkitse yksityiskeskusteluksi",
+ "commandHint_markasdm": "Merkitse yksityiskeskusteluksi syötetyn Matrix IDn kanssa",
"@commandHint_markasdm": {},
"foregroundServiceRunning": "Tämä ilmoitus näkyy etualapalvelun ollessa käynnissä.",
"@foregroundServiceRunning": {},
@@ -2501,5 +2501,124 @@
"continueWith": "Jatka käyttäen:",
"@continueWith": {},
"pleaseTryAgainLaterOrChooseDifferentServer": "Yritä myöhemmin uudelleen tai valitse toinen palvelin.",
- "@pleaseTryAgainLaterOrChooseDifferentServer": {}
+ "@pleaseTryAgainLaterOrChooseDifferentServer": {},
+ "setColorTheme": "Aseta väriteema:",
+ "@setColorTheme": {},
+ "requests": "Pyynnöt",
+ "@requests": {},
+ "tryAgain": "Yritä uudelleen",
+ "@tryAgain": {},
+ "messagesStyle": "Viestit:",
+ "@messagesStyle": {},
+ "chatDescription": "Keskustelun kuvaus",
+ "@chatDescription": {},
+ "invalidServerName": "Virheellinen palvelimen nimi",
+ "@invalidServerName": {},
+ "chatPermissions": "Keskustelun oikeudet",
+ "@chatPermissions": {},
+ "setChatDescription": "Asetti keskustelun kuvauksen",
+ "@setChatDescription": {},
+ "importFromZipFile": "Tuo .zip -tiedostosta",
+ "@importFromZipFile": {},
+ "redactedBy": "Poistanut {username}",
+ "@redactedBy": {
+ "type": "text",
+ "placeholders": {
+ "username": {}
+ }
+ },
+ "signInWith": "Kirjaudu sisään palvelulla {provider}",
+ "@signInWith": {
+ "type": "text",
+ "placeholders": {
+ "provider": {}
+ }
+ },
+ "optionalRedactReason": "(Vapaaehtoinen) Syy tämän viestin poistamiselle...",
+ "@optionalRedactReason": {},
+ "archiveRoomDescription": "Keskustelu siirretään arkistoon. Muut käyttäjät näkevät sinun poistuneen keskustelusta.",
+ "@archiveRoomDescription": {},
+ "exportEmotePack": "Vie emotepaketti .zip-tiedostona",
+ "@exportEmotePack": {},
+ "savedEmotePack": "Tallennettiin emotepaketti sijaintiin {path}!",
+ "@savedEmotePack": {
+ "type": "text",
+ "placeholders": {
+ "path": {}
+ }
+ },
+ "inviteContactToGroupQuestion": "Tahdotko kutsua yhteystiedon {contact} keskusteluun \"{groupName}\"?",
+ "@inviteContactToGroupQuestion": {},
+ "redactedByBecause": "Poistanut {username} syystä: \"{reason}\"",
+ "@redactedByBecause": {
+ "type": "text",
+ "placeholders": {
+ "username": {},
+ "reason": {}
+ }
+ },
+ "importZipFile": "Tuo zip-tiedosto",
+ "@importZipFile": {},
+ "anyoneCanKnock": "Kaikki voivat koputtaa",
+ "@anyoneCanKnock": {},
+ "redactMessageDescription": "Viesti poistetaan kaikilta keskustelun osallistujilta. Tätä ei voida kumota.",
+ "@redactMessageDescription": {},
+ "invalidInput": "Virheellinen syöte!",
+ "@invalidInput": {},
+ "addChatDescription": "Lisää keskustelulle kuvaus",
+ "@addChatDescription": {},
+ "hasKnocked": "{user} on koputtanut",
+ "@hasKnocked": {
+ "placeholders": {
+ "user": {}
+ }
+ },
+ "directChat": "Yksityiskeskustelu",
+ "@directChat": {},
+ "noOneCanJoin": "Kukaan ei voi liittyä",
+ "@noOneCanJoin": {},
+ "wrongPinEntered": "Väärä pin-koodi! Yritä uudelleen {seconds} sekuntin kuluttua...",
+ "@wrongPinEntered": {
+ "type": "text",
+ "placeholders": {
+ "seconds": {}
+ }
+ },
+ "sendTypingNotifications": "Lähetä kirjoitusilmoituksia",
+ "@sendTypingNotifications": {},
+ "inviteGroupChat": "Kutsu ryhmäkeskusteluun",
+ "@inviteGroupChat": {},
+ "invitePrivateChat": "Kutsu yksityiskeskusteluun",
+ "@invitePrivateChat": {},
+ "importEmojis": "Tuo emojit",
+ "@importEmojis": {},
+ "noChatDescriptionYet": "Keskustelun kuvausta ei ole vielä luotu.",
+ "@noChatDescriptionYet": {},
+ "notAnImage": "Tämä ei ole kuvatiedosto.",
+ "@notAnImage": {},
+ "chatDescriptionHasBeenChanged": "Keskustelun kuvaus muutettu",
+ "@chatDescriptionHasBeenChanged": {},
+ "roomUpgradeDescription": "Keskustelu luodaan uudelleen uudella huoneversiolla. Kaikille osallistujille ilmoitetaan, että heidän tulee siirtyä uuteen keskusteluun. Voit lukea lisää huoneversioista osoitteesta https://spec.matrix.org/latest/rooms/",
+ "@roomUpgradeDescription": {},
+ "pleaseEnterANumber": "Syötä suurempi luku kuin 0",
+ "@pleaseEnterANumber": {},
+ "profileNotFound": "Käyttäjää ei löydy palvelimelta. Tämä voi olla yhteysongelma tai käyttäjä ei ole olemassa.",
+ "@profileNotFound": {},
+ "shareInviteLink": "Jaa kutsulinkki",
+ "@shareInviteLink": {},
+ "emoteKeyboardNoRecents": "Viimeaikoina käytetyt emotet tulevat näkymään täällä...",
+ "@emoteKeyboardNoRecents": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "setTheme": "Aseta teema:",
+ "@setTheme": {},
+ "replace": "Korvaa",
+ "@replace": {},
+ "createGroup": "Luo ryhmä",
+ "@createGroup": {},
+ "importNow": "Tuo nyt",
+ "@importNow": {},
+ "invite": "Kutsu",
+ "@invite": {}
}
diff --git a/assets/l10n/intl_gl.arb b/assets/l10n/intl_gl.arb
index 6dec950cf..79329c83e 100644
--- a/assets/l10n/intl_gl.arb
+++ b/assets/l10n/intl_gl.arb
@@ -1164,7 +1164,7 @@
"type": "text",
"placeholders": {}
},
- "noGoogleServicesWarning": "Semella que non tes os servizos de google no teu dispositivo. Ben feito! a túa privacidade agradécecho! Para recibir notificacións push en FluffyChat recomendamos usar https://microg.org/ ou https://unifiedpush.org/.",
+ "noGoogleServicesWarning": "Semella que non tes Firebase Cloud Messaging dispoñible no teu dispositivo. Para recibir notificacións push recomendamos que instales MicroG ou Unified Push.",
"@noGoogleServicesWarning": {
"type": "text",
"placeholders": {}
@@ -2610,5 +2610,52 @@
"@emoteKeyboardNoRecents": {
"type": "text",
"placeholders": {}
- }
+ },
+ "banUserDescription": "Vaise vetar a usuaria na conversa e non poderá entrar outra vez ata que se retire o veto.",
+ "@banUserDescription": {},
+ "removeDevicesDescription": "Vas pechar a sesión neste dispositivo e xa non poderás recibir mensaxes nel.",
+ "@removeDevicesDescription": {},
+ "unbanUserDescription": "A usuaria vai poder entrar outra vez na conversa se quere.",
+ "@unbanUserDescription": {},
+ "todoLists": "(Beta) Lista de tarefas",
+ "@todoLists": {},
+ "editTodo": "Editar tarefa",
+ "@editTodo": {},
+ "pushNotificationsNotAvailable": "Non están dispoñibles as notificacións push",
+ "@pushNotificationsNotAvailable": {},
+ "pleaseAddATitle": "Engade un título",
+ "@pleaseAddATitle": {},
+ "makeAdminDescription": "Cando convirtas a esta usuaria en admin non poderás desfacer a acción xa que terá os mesmos permisos ca ti.",
+ "@makeAdminDescription": {},
+ "noTodosYet": "Non se engadiron tarefas aínda a este chat. Crea primeiro a túa lista de tarefas e comeza a colaborar con outras. 📝",
+ "@noTodosYet": {},
+ "archiveRoomDescription": "Vaise mover o chat ao arquivo. Outras usuarias poderán ver que saíches da conversa.",
+ "@archiveRoomDescription": {},
+ "invalidInput": "Contido non válido!",
+ "@invalidInput": {},
+ "hasKnocked": "déronlle unha labazada a {user}",
+ "@hasKnocked": {
+ "placeholders": {
+ "user": {}
+ }
+ },
+ "wrongPinEntered": "PIN incorrecto! Inténtao outra vez en {seconds} segundos...",
+ "@wrongPinEntered": {
+ "type": "text",
+ "placeholders": {
+ "seconds": {}
+ }
+ },
+ "newTodo": "Nova tarefa",
+ "@newTodo": {},
+ "learnMore": "Saber máis",
+ "@learnMore": {},
+ "todoListChangedError": "Ooi... A lista cambiou mentras ti a editabas.",
+ "@todoListChangedError": {},
+ "roomUpgradeDescription": "Vaise recrear o chat coa nova versión da sala. Todas as participantes recibirán unha notificación para que cambien ao novo chat. Podes ler máis información acerca das versións das salas en https://spec.matrix.org/latest/rooms/",
+ "@roomUpgradeDescription": {},
+ "pleaseEnterANumber": "Escribe un número maior de cero",
+ "@pleaseEnterANumber": {},
+ "kickUserDescription": "A usuaria foi expulsada pero non vetada. En conversas públicas a usuaria pode volver cando queira.",
+ "@kickUserDescription": {}
}
diff --git a/assets/l10n/intl_hr.arb b/assets/l10n/intl_hr.arb
index cb93cd2d7..b64f3ec47 100644
--- a/assets/l10n/intl_hr.arb
+++ b/assets/l10n/intl_hr.arb
@@ -2309,7 +2309,7 @@
"user": {}
}
},
- "youUnbannedUser": "Ponovo si uključio/la si korisnika {user}",
+ "youUnbannedUser": "Ponovo si uključio/la korisnika {user}",
"@youUnbannedUser": {
"placeholders": {
"user": {}
@@ -2617,5 +2617,13 @@
"placeholders": {
"seconds": {}
}
- }
+ },
+ "hasKnocked": "{user} je pokucao/la",
+ "@hasKnocked": {
+ "placeholders": {
+ "user": {}
+ }
+ },
+ "pleaseEnterANumber": "Upiši broj veći od 0",
+ "@pleaseEnterANumber": {}
}
diff --git a/assets/l10n/intl_pl.arb b/assets/l10n/intl_pl.arb
index c66066775..6a9f22c74 100644
--- a/assets/l10n/intl_pl.arb
+++ b/assets/l10n/intl_pl.arb
@@ -844,7 +844,7 @@
"type": "text",
"placeholders": {}
},
- "inviteText": "{username} zaprosił/-a cię do FluffyChat. \n1. Zainstaluj FluffyChat: https://fluffychat.im \n2. Zarejestuj się lub zaloguj \n3. Otwórz link zaproszenia: {link}",
+ "inviteText": "{username} zaprosił/-a cię do FluffyChat. \n1. Odwiedź fluffychat.im i zainstaluj aplikację\n2. Zarejestuj się lub zaloguj \n3. Otwórz link zaproszenia:\n{link}",
"@inviteText": {
"type": "text",
"placeholders": {
@@ -1490,7 +1490,7 @@
"type": "text",
"placeholders": {}
},
- "wallpaper": "Tapeta",
+ "wallpaper": "Tapeta:",
"@wallpaper": {
"type": "text",
"placeholders": {}
@@ -1827,7 +1827,7 @@
"type": "text",
"placeholders": {}
},
- "redactMessage": "Przekaż wiadomość",
+ "redactMessage": "Utajnij wiadomość",
"@redactMessage": {
"type": "text",
"placeholders": {}
@@ -1992,7 +1992,7 @@
"@whoCanSeeMyStoriesDesc": {},
"shareYourInviteLink": "Udostępnij swój link zaproszenia",
"@shareYourInviteLink": {},
- "separateChatTypes": "Oddzielenie czatów bezpośrednich i grup",
+ "separateChatTypes": "Oddzielenie czatów bezpośrednich i grupowych",
"@separateChatTypes": {
"type": "text",
"placeholders": {}
@@ -2202,7 +2202,7 @@
"mxid": {}
}
},
- "commandHint_markasdm": "Oznacz jako pokój wiadomości bezpośrednich",
+ "commandHint_markasdm": "Oznacz jako pokój wiadomości bezpośrednich dla podanego Matrix ID",
"@commandHint_markasdm": {},
"confirmMatrixId": "Potwierdź swój identyfikator Matrix w celu usunięcia konta.",
"@confirmMatrixId": {},
@@ -2483,5 +2483,132 @@
"removeFromBundle": "Usuń z tej paczki",
"@removeFromBundle": {},
"openLinkInBrowser": "Otwórz link w przeglądarce",
- "@openLinkInBrowser": {}
+ "@openLinkInBrowser": {},
+ "allRooms": "Wszystkie czaty grupowe",
+ "@allRooms": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "reportErrorDescription": "O nie. Coś poszło nie tak. Spróbuj ponownie później. Jeśli chcesz, możesz zgłosić błąd programistom.",
+ "@reportErrorDescription": {},
+ "setColorTheme": "Ustal styl kolorów:",
+ "@setColorTheme": {},
+ "requests": "Żądania",
+ "@requests": {},
+ "tryAgain": "Spróbuj ponownie",
+ "@tryAgain": {},
+ "messagesStyle": "Wiadomości:",
+ "@messagesStyle": {},
+ "chatDescription": "Opis czatu",
+ "@chatDescription": {},
+ "invalidServerName": "Nieprawidłowa nazwa serwera",
+ "@invalidServerName": {},
+ "chatPermissions": "Uprawnienia czatu",
+ "@chatPermissions": {},
+ "signInWithPassword": "Zaloguj się z hasłem",
+ "@signInWithPassword": {},
+ "setChatDescription": "Ustaw opis czatu",
+ "@setChatDescription": {},
+ "importFromZipFile": "Zaimportuj z pliku .zip",
+ "@importFromZipFile": {},
+ "discover": "Odkrywanie",
+ "@discover": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "redactedBy": "Utajnione przez {username}",
+ "@redactedBy": {
+ "type": "text",
+ "placeholders": {
+ "username": {}
+ }
+ },
+ "signInWith": "Zaloguj się z {provider}",
+ "@signInWith": {
+ "type": "text",
+ "placeholders": {
+ "provider": {}
+ }
+ },
+ "optionalRedactReason": "(Opcjonalnie) Powód utajnienia tej wiadomości...",
+ "@optionalRedactReason": {},
+ "exportEmotePack": "Eksportuj pakiet Emotikon jako .zip",
+ "@exportEmotePack": {},
+ "savedEmotePack": "Zapisano pakiet emotikon do {path}!",
+ "@savedEmotePack": {
+ "type": "text",
+ "placeholders": {
+ "path": {}
+ }
+ },
+ "inviteContactToGroupQuestion": "Czy chcesz zaprosić {contact} do czatu „{groupName}”?",
+ "@inviteContactToGroupQuestion": {},
+ "redactedByBecause": "Utajnione przez {username} z powodu: \"{reason}\"",
+ "@redactedByBecause": {
+ "type": "text",
+ "placeholders": {
+ "username": {},
+ "reason": {}
+ }
+ },
+ "importZipFile": "Zaimportuj plik .zip",
+ "@importZipFile": {},
+ "anyoneCanKnock": "Każdy może zapukać",
+ "@anyoneCanKnock": {},
+ "redactMessageDescription": "Wiadomość zostanie utajniona u wszystkich uczestników tej rozmowy. Nie można tego cofnąć.",
+ "@redactMessageDescription": {},
+ "invalidInput": "Nieprawidłowe dane!",
+ "@invalidInput": {},
+ "report": "raport",
+ "@report": {},
+ "addChatDescription": "Dodaj opis tego czatu",
+ "@addChatDescription": {},
+ "directChat": "Rozmowa bezpośrednia",
+ "@directChat": {},
+ "noOneCanJoin": "Nikt nie może dołączyć",
+ "@noOneCanJoin": {},
+ "wrongPinEntered": "Wprowadzono nieprawidłowy kod PIN! Spróbuj ponownie za {seconds} sekund...",
+ "@wrongPinEntered": {
+ "type": "text",
+ "placeholders": {
+ "seconds": {}
+ }
+ },
+ "sendTypingNotifications": "Wysyłaj powiadomienie o pisaniu",
+ "@sendTypingNotifications": {},
+ "inviteGroupChat": "📨 Zaproszenie do rozmowy grupowej",
+ "@inviteGroupChat": {},
+ "invitePrivateChat": "📨 Zaproszenie do rozmowy prywatnej",
+ "@invitePrivateChat": {},
+ "importEmojis": "Zaimportuj Emoji",
+ "@importEmojis": {},
+ "noChatDescriptionYet": "Nie utworzono jeszcze opisu czatu.",
+ "@noChatDescriptionYet": {},
+ "notAnImage": "To nie jest plik obrazu.",
+ "@notAnImage": {},
+ "chatDescriptionHasBeenChanged": "Zmieniono opis czatu",
+ "@chatDescriptionHasBeenChanged": {},
+ "profileNotFound": "Nie można odnaleźć użytkownika na serwerze. Być może wystąpił problem z połączeniem lub użytkownik nie istnieje.",
+ "@profileNotFound": {},
+ "shareInviteLink": "Udostępnij link zaproszenia",
+ "@shareInviteLink": {},
+ "emoteKeyboardNoRecents": "Tutaj pojawiają się ostatnio używane emotikony...",
+ "@emoteKeyboardNoRecents": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "setTheme": "Ustaw wygląd:",
+ "@setTheme": {},
+ "replace": "Zastąp",
+ "@replace": {},
+ "pleaseTryAgainLaterOrChooseDifferentServer": "Spróbuj ponownie później lub wybierz inny serwer.",
+ "@pleaseTryAgainLaterOrChooseDifferentServer": {},
+ "createGroup": "Utwórz grupę",
+ "@createGroup": {},
+ "importNow": "Zaimportuj",
+ "@importNow": {},
+ "invite": "Zaproszenie",
+ "@invite": {},
+ "continueWith": "Kontynuuj z:",
+ "@continueWith": {}
}
diff --git a/assets/l10n/intl_th.arb b/assets/l10n/intl_th.arb
index 9e26dfeeb..93d784b63 100644
--- a/assets/l10n/intl_th.arb
+++ b/assets/l10n/intl_th.arb
@@ -1 +1,250 @@
-{}
\ No newline at end of file
+{
+ "hugContent": "{senderName} กอดคุณ",
+ "@hugContent": {
+ "type": "text",
+ "placeholders": {
+ "senderName": {}
+ }
+ },
+ "commandHint_cuddle": "ส่งเคล้าเคลียให้",
+ "@commandHint_cuddle": {},
+ "admin": "แอดมิน",
+ "@admin": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "supposedMxid": "อันนี้ควรเป็น {mxid}",
+ "@supposedMxid": {
+ "type": "text",
+ "placeholders": {
+ "mxid": {}
+ }
+ },
+ "askSSSSSign": "เพื่อให้สามารถลงนามบุคคลอื่นได้ โปรดป้อนรหัสผ่านร้านค้าที่ปลอดภัยหรือรหัสกู้คืนของคุณ",
+ "@askSSSSSign": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "remove": "ลบออก",
+ "@remove": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "areGuestsAllowedToJoin": "ผู้ใช้ทั่วไปได้รับอนุญาตให้เข้าร่วมหรือไม่",
+ "@areGuestsAllowedToJoin": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "pleaseEnterValidEmail": "กรุณาใส่อีเมล์ที่ถูกต้อง",
+ "@pleaseEnterValidEmail": {},
+ "sendOnEnter": "ส่งเมื่อกด enter",
+ "@sendOnEnter": {},
+ "answeredTheCall": "{senderName} รับสายแล้ว",
+ "@answeredTheCall": {
+ "type": "text",
+ "placeholders": {
+ "senderName": {}
+ }
+ },
+ "pleaseChooseAtLeastChars": "กรุณาใส่รหัสอย่างน้อย {min} ตัว",
+ "@pleaseChooseAtLeastChars": {
+ "type": "text",
+ "placeholders": {
+ "min": {}
+ }
+ },
+ "alias": "นามแฝง",
+ "@alias": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "all": "ทั้งหมด",
+ "@all": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "badServerLoginTypesException": "โฮมเซิร์ฟเวอร์รองรับประเภทการเข้าสู่ระบบ:\n{serverVersions}\nแต่แอปนี้รองรับเฉพาะ:\n{supportedVersions}",
+ "@badServerLoginTypesException": {
+ "type": "text",
+ "placeholders": {
+ "serverVersions": {},
+ "supportedVersions": {}
+ }
+ },
+ "updateNow": "เริ่มการอัปเดตในเบื้องหลัง",
+ "@updateNow": {},
+ "edit": "แก้ไข",
+ "@edit": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "copy": "คัดลอก",
+ "@copy": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "importFromZipFile": "นำเข้าจากไฟล์ .zip",
+ "@importFromZipFile": {},
+ "autoplayImages": "เล่นสติ๊กเกอร์และอิโมจิแบบเคลื่อนไหวโดยอัตโนมัติ",
+ "@autoplayImages": {
+ "type": "text",
+ "placeholder": {}
+ },
+ "updateAvailable": "มีการอัปเดต FluffyChat แล้ว",
+ "@updateAvailable": {},
+ "help": "ช่วยเหลือ",
+ "@help": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "chatDetails": "รายละเอียดแชท",
+ "@chatDetails": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "repeatPassword": "ใส่รหัสผ่านอีกรอบ",
+ "@repeatPassword": {},
+ "delete": "ลบออก",
+ "@delete": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "acceptedTheInvitation": "👍 {username} ได้รับการชวนแล้ว",
+ "@acceptedTheInvitation": {
+ "type": "text",
+ "placeholders": {
+ "username": {}
+ }
+ },
+ "send": "ส่ง",
+ "@send": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "exportEmotePack": "ส่งอิโมจิแพ็คออกเป็นไฟล์ .zip",
+ "@exportEmotePack": {},
+ "account": "บัญชี",
+ "@account": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "savedEmotePack": "บันท฿กแพ็คอิโมจิไว้ที่ {path}!",
+ "@savedEmotePack": {
+ "type": "text",
+ "placeholders": {
+ "path": {}
+ }
+ },
+ "chat": "แชท",
+ "@chat": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "areYouSure": "คุณแน่ใจไหม?",
+ "@areYouSure": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "allChats": "แชททั้งหมด",
+ "@allChats": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "passwordsDoNotMatch": "รหัสผ่านของคุณไม่ตรงกัน",
+ "@passwordsDoNotMatch": {},
+ "addToSpace": "เพิ่มไปที่ space",
+ "@addToSpace": {},
+ "importZipFile": "นำเข้าไฟล์ .zip",
+ "@importZipFile": {},
+ "about": "เกี่ยวกับ",
+ "@about": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "activatedEndToEndEncryption": "🔐 {username} เปิดใช้งาน end to end encryption",
+ "@activatedEndToEndEncryption": {
+ "type": "text",
+ "placeholders": {
+ "username": {}
+ }
+ },
+ "fluffychat": "FluffyChat",
+ "@fluffychat": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "googlyEyesContent": "{senderName} ส่งตากวนๆให้คุณ",
+ "@googlyEyesContent": {
+ "type": "text",
+ "placeholders": {
+ "senderName": {}
+ }
+ },
+ "addChatDescription": "เพิ่มคำอธิบายการแชท",
+ "@addChatDescription": {},
+ "appLock": "ล็อคแอป",
+ "@appLock": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "sendTypingNotifications": "ส่งการแจ้งเตือนการพิมพ์",
+ "@sendTypingNotifications": {},
+ "importEmojis": "นำเข้าอ๊โมจิ",
+ "@importEmojis": {},
+ "confirmMatrixId": "กรุณายืนยัน Matrix ID ของคุณเพื่อลบบัญชีของคุณ",
+ "@confirmMatrixId": {},
+ "notAnImage": "ไม่ใช่ไฟล์รูปภาพ",
+ "@notAnImage": {},
+ "areYouSureYouWantToLogout": "คุณแน่ใจว่าคุณต้องการที่จะออกจากระบบ?",
+ "@areYouSureYouWantToLogout": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "cuddleContent": "{senderName} เคล้าเคลียคุณ",
+ "@cuddleContent": {
+ "type": "text",
+ "placeholders": {
+ "senderName": {}
+ }
+ },
+ "askVerificationRequest": "ยอมรับคำขอยืนยันนี้จาก {username} หรือไม่",
+ "@askVerificationRequest": {
+ "type": "text",
+ "placeholders": {
+ "username": {}
+ }
+ },
+ "addEmail": "เพิ่มอีเมล",
+ "@addEmail": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "commandHint_hug": "ส่งกอดให้",
+ "@commandHint_hug": {},
+ "replace": "แทนที่",
+ "@replace": {},
+ "archive": "คลังเก็บ",
+ "@archive": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "accept": "ยอมรับ",
+ "@accept": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "commandHint_googly": "ส่งสายตากวนๆ มาให้หน่อย",
+ "@commandHint_googly": {},
+ "pin": "ปักหมุด",
+ "@pin": {
+ "type": "text",
+ "placeholders": {}
+ },
+ "importNow": "นำเข้าเลย",
+ "@importNow": {},
+ "anyoneCanJoin": "ใครๆ ก็สามารถเข้าร่วมได้",
+ "@anyoneCanJoin": {
+ "type": "text",
+ "placeholders": {}
+ }
+}
diff --git a/assets/l10n/intl_uk.arb b/assets/l10n/intl_uk.arb
index dfac06603..b9f379f9d 100644
--- a/assets/l10n/intl_uk.arb
+++ b/assets/l10n/intl_uk.arb
@@ -736,7 +736,7 @@
"type": "text",
"placeholders": {}
},
- "noGoogleServicesWarning": "Схоже, на вашому телефоні немає служб Google. Це гарне рішення для вашої приватності! Щоб отримувати push-сповіщення у FluffyChat, ми радимо використовувати https://microg.org/ або https://unifiedpush.org/.",
+ "noGoogleServicesWarning": "Схоже, Firebase Cloud Messaging недоступна на вашому пристрої. Щоб отримувати push-сповіщення, радимо встановити ntfy. За допомогою ntfy або іншого постачальника Unified Push ви можете отримувати push-сповіщення у безпечний спосіб. Ви можете завантажити ntfy з PlayStore або з F-Droid.",
"@noGoogleServicesWarning": {
"type": "text",
"placeholders": {}
@@ -2619,5 +2619,45 @@
"placeholders": {
"seconds": {}
}
- }
+ },
+ "banUserDescription": "Користувача буде заблоковано в бесіді, і він не зможе знову увійти в неї, поки його не буде розблоковано.",
+ "@banUserDescription": {},
+ "removeDevicesDescription": "Ви вийдете з цього пристрою і більше не зможете отримувати повідомлення.",
+ "@removeDevicesDescription": {},
+ "unbanUserDescription": "Користувач зможе знову увійти в бесіду, якщо спробує.",
+ "@unbanUserDescription": {},
+ "todoLists": "(Бета) Завдання",
+ "@todoLists": {},
+ "editTodo": "Змінити завдання",
+ "@editTodo": {},
+ "pushNotificationsNotAvailable": "Push-сповіщення недоступні",
+ "@pushNotificationsNotAvailable": {},
+ "pleaseAddATitle": "Додайте заголовок",
+ "@pleaseAddATitle": {},
+ "makeAdminDescription": "Після того, як ви зробите цього користувача адміністратором, ви, можливо, не зможете це скасувати, оскільки він матиме ті самі права, що й ви.",
+ "@makeAdminDescription": {},
+ "noTodosYet": "До цієї бесіди ще не додано жодного завдання. Створіть своє перше завдання і почніть співпрацювати з іншими. 📝",
+ "@noTodosYet": {},
+ "archiveRoomDescription": "Бесіду буде переміщено до архіву. Інші користувачі зможуть побачити, що ви вийшли з неї.",
+ "@archiveRoomDescription": {},
+ "todosUnencrypted": "Зауважте, що завдання бачать усі учасники бесіди, і вони не захищені наскрізним шифруванням.",
+ "@todosUnencrypted": {},
+ "hasKnocked": "{user} стукає до вас",
+ "@hasKnocked": {
+ "placeholders": {
+ "user": {}
+ }
+ },
+ "newTodo": "Нове завдання",
+ "@newTodo": {},
+ "learnMore": "Докладніше",
+ "@learnMore": {},
+ "todoListChangedError": "Отакої... Список завдань було змінено, поки ви його редагували.",
+ "@todoListChangedError": {},
+ "roomUpgradeDescription": "Після цього бесіду буде відтворено з новою версією кімнати. Усі учасники отримають сповіщення, що їм потрібно перейти до нової бесіди. Ви можете дізнатися більше про версії кімнат на https://spec.matrix.org/latest/rooms/",
+ "@roomUpgradeDescription": {},
+ "pleaseEnterANumber": "Введіть число більше ніж 0",
+ "@pleaseEnterANumber": {},
+ "kickUserDescription": "Користувача вигнали з бесіди, але не заблокували. До загальнодоступних бесід користувач може приєднатися будь-коли.",
+ "@kickUserDescription": {}
}
diff --git a/assets/l10n/intl_zh.arb b/assets/l10n/intl_zh.arb
index 586a5c9ff..38f47563b 100644
--- a/assets/l10n/intl_zh.arb
+++ b/assets/l10n/intl_zh.arb
@@ -1123,7 +1123,7 @@
"type": "text",
"placeholders": {}
},
- "noGoogleServicesWarning": "看起来你手机上没有谷歌服务框架。这对保护你的隐私而言是个好决定!要接收 FluffyChat 的推送通知,推荐你使用 https://microg.org/ 或 https://unifiedpush.org/。",
+ "noGoogleServicesWarning": "看起来你手机上没有 Firebase Cloud Messaging。如果仍希望接收 FluffyChat 的推送通知,推荐安装 ntfy。借助 ntfy 或另一个 Unified Push 程序,你可以以一种数据安全的方式接收推送通知。你可以从 PlayStore 或 F-Droid 商店下载 ntfy。",
"@noGoogleServicesWarning": {
"type": "text",
"placeholders": {}
@@ -2617,5 +2617,13 @@
"placeholders": {
"seconds": {}
}
- }
+ },
+ "hasKnocked": "{user} 请求了加入聊天室的邀请",
+ "@hasKnocked": {
+ "placeholders": {
+ "user": {}
+ }
+ },
+ "pleaseEnterANumber": "请输入大于 0 的数",
+ "@pleaseEnterANumber": {}
}
diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart
index 3edcc3fef..5fe126858 100644
--- a/lib/config/app_config.dart
+++ b/lib/config/app_config.dart
@@ -68,7 +68,6 @@ abstract class AppConfig {
static bool hideRedactedEvents = false;
static bool hideUnknownEvents = true;
static bool hideUnimportantStateEvents = true;
- static bool showDirectChatsInSpaces = true;
static bool separateChatTypes = false;
static bool autoplayImages = true;
static bool sendTypingNotifications = true;
diff --git a/lib/config/routes.dart b/lib/config/routes.dart
index 7fca80f91..be82c09b2 100644
--- a/lib/config/routes.dart
+++ b/lib/config/routes.dart
@@ -534,6 +534,7 @@ abstract class AppRoutes {
],
redirect: loggedOutRedirect,
),
+ // #Pangea
// GoRoute(
// path: 'tasks',
// pageBuilder: (context, state) => defaultPageBuilder(
@@ -545,6 +546,7 @@ abstract class AppRoutes {
// ),
// ),
// ),
+ // Pangea#
],
),
],
diff --git a/lib/config/setting_keys.dart b/lib/config/setting_keys.dart
index 14bc6da3c..e7ef76596 100644
--- a/lib/config/setting_keys.dart
+++ b/lib/config/setting_keys.dart
@@ -5,8 +5,6 @@ abstract class SettingKeys {
static const String hideUnknownEvents = 'chat.fluffy.hideUnknownEvents';
static const String hideUnimportantStateEvents =
'chat.fluffy.hideUnimportantStateEvents';
- static const String showDirectChatsInSpaces =
- 'chat.fluffy.showDirectChatsInSpaces';
static const String separateChatTypes = 'chat.fluffy.separateChatTypes';
static const String sentry = 'sentry';
static const String theme = 'theme';
diff --git a/lib/main.dart b/lib/main.dart
index b5ea0cf2a..fa2c1a53f 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -6,11 +6,13 @@ import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
import 'package:fluffychat/utils/client_manager.dart';
import 'package:fluffychat/utils/platform_infos.dart';
+import 'package:fluffychat/widgets/error_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:get_storage/get_storage.dart';
import 'package:matrix/matrix.dart';
+import 'package:shared_preferences/shared_preferences.dart';
import 'config/setting_keys.dart';
import 'utils/background_push.dart';
@@ -42,7 +44,8 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized();
Logs().nativeColors = !PlatformInfos.isIOS;
- final clients = await ClientManager.getClients();
+ final store = await SharedPreferences.getInstance();
+ final clients = await ClientManager.getClients(store: store);
// If the app starts in detached mode, we assume that it is in
// background fetch mode for processing push notifications. This is
@@ -53,7 +56,7 @@ void main() async {
// starting the Flutter engine but process incoming push notifications.
BackgroundPush.clientOnly(clients.first);
// To start the flutter engine afterwards we add an custom observer.
- WidgetsBinding.instance.addObserver(AppStarter(clients));
+ WidgetsBinding.instance.addObserver(AppStarter(clients, store));
Logs().i(
'${AppConfig.applicationName} started in background-fetch mode. No GUI will be created unless the app is no longer detached.',
);
@@ -64,11 +67,11 @@ void main() async {
Logs().i(
'${AppConfig.applicationName} started in foreground mode. Rendering GUI...',
);
- await startGui(clients);
+ await startGui(clients, store);
}
/// Fetch the pincode for the applock and start the flutter engine.
-Future startGui(List clients) async {
+Future startGui(List clients, SharedPreferences store) async {
// Fetch the pin for the applock if existing for mobile applications.
String? pin;
if (PlatformInfos.isMobile) {
@@ -85,16 +88,18 @@ Future startGui(List clients) async {
await firstClient?.roomsLoading;
await firstClient?.accountDataLoading;
- runApp(FluffyChatApp(clients: clients, pincode: pin));
+ ErrorWidget.builder = (details) => FluffyChatErrorWidget(details);
+ runApp(FluffyChatApp(clients: clients, pincode: pin, store: store));
}
/// Watches the lifecycle changes to start the application when it
/// is no longer detached.
class AppStarter with WidgetsBindingObserver {
final List clients;
+ final SharedPreferences store;
bool guiStarted = false;
- AppStarter(this.clients);
+ AppStarter(this.clients, this.store);
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
@@ -104,7 +109,7 @@ class AppStarter with WidgetsBindingObserver {
Logs().i(
'${AppConfig.applicationName} switches from the detached background-fetch mode to ${state.name} mode. Rendering GUI...',
);
- startGui(clients);
+ startGui(clients, store);
// We must make sure that the GUI is only started once.
guiStarted = true;
}
diff --git a/lib/pages/add_story/add_story.dart b/lib/pages/add_story/add_story.dart
index 7a7421ad9..a27193593 100644
--- a/lib/pages/add_story/add_story.dart
+++ b/lib/pages/add_story/add_story.dart
@@ -21,7 +21,7 @@ import 'package:video_player/video_player.dart';
import '../../utils/matrix_sdk_extensions/client_stories_extension.dart';
class AddStoryPage extends StatefulWidget {
- const AddStoryPage({Key? key}) : super(key: key);
+ const AddStoryPage({super.key});
@override
AddStoryController createState() => AddStoryController();
diff --git a/lib/pages/add_story/add_story_view.dart b/lib/pages/add_story/add_story_view.dart
index 12f7dad26..ea828aadf 100644
--- a/lib/pages/add_story/add_story_view.dart
+++ b/lib/pages/add_story/add_story_view.dart
@@ -8,7 +8,7 @@ import 'add_story.dart';
class AddStoryView extends StatelessWidget {
final AddStoryController controller;
- const AddStoryView(this.controller, {Key? key}) : super(key: key);
+ const AddStoryView(this.controller, {super.key});
@override
Widget build(BuildContext context) {
diff --git a/lib/pages/add_story/invite_story_page.dart b/lib/pages/add_story/invite_story_page.dart
index c28af4416..c62a0b815 100644
--- a/lib/pages/add_story/invite_story_page.dart
+++ b/lib/pages/add_story/invite_story_page.dart
@@ -14,8 +14,8 @@ class InviteStoryPage extends StatefulWidget {
final Room? storiesRoom;
const InviteStoryPage({
required this.storiesRoom,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
InviteStoryPageState createState() => InviteStoryPageState();
diff --git a/lib/pages/archive/archive.dart b/lib/pages/archive/archive.dart
index 823fe8362..a3c35c347 100644
--- a/lib/pages/archive/archive.dart
+++ b/lib/pages/archive/archive.dart
@@ -9,24 +9,36 @@ import 'package:fluffychat/pages/archive/archive_view.dart';
import 'package:fluffychat/widgets/matrix.dart';
class Archive extends StatefulWidget {
- const Archive({Key? key}) : super(key: key);
+ const Archive({super.key});
@override
ArchiveController createState() => ArchiveController();
}
class ArchiveController extends State {
- List? archive;
+ List archive = [];
Future> getArchive(BuildContext context) async {
- final archive = this.archive;
- if (archive != null) return archive;
- return this.archive = await Matrix.of(context).client.loadArchive();
+ if (archive.isNotEmpty) return archive;
+ return archive = await Matrix.of(context).client.loadArchive();
+ }
+
+ void forgetRoomAction(int i) async {
+ await showFutureLoadingDialog(
+ context: context,
+ future: () async {
+ Logs().v('Forget room ${archive.last.getLocalizedDisplayname()}');
+ await archive[i].forget();
+ archive.removeAt(i);
+ },
+ );
+ setState(() {});
}
void forgetAllAction() async {
final archive = this.archive;
- if (archive == null) return;
+ final client = Matrix.of(context).client;
+ if (archive.isEmpty) return;
if (await showOkCancelAlertDialog(
useRootNavigator: false,
context: context,
@@ -48,6 +60,7 @@ class ArchiveController extends State {
}
},
);
+ client.clearArchivesFromCache();
setState(() {});
}
diff --git a/lib/pages/archive/archive_view.dart b/lib/pages/archive/archive_view.dart
index e3d2c29cd..a60eb9ad7 100644
--- a/lib/pages/archive/archive_view.dart
+++ b/lib/pages/archive/archive_view.dart
@@ -8,11 +8,10 @@ import 'package:matrix/matrix.dart';
class ArchiveView extends StatelessWidget {
final ArchiveController controller;
- const ArchiveView(this.controller, {Key? key}) : super(key: key);
+ const ArchiveView(this.controller, {super.key});
@override
Widget build(BuildContext context) {
- var archive = controller.archive;
return FutureBuilder>(
future: controller.getArchive(context),
builder: (BuildContext context, snapshot) => Scaffold(
@@ -48,16 +47,16 @@ class ArchiveView extends StatelessWidget {
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
);
} else {
- archive = snapshot.data;
- if (archive == null || archive!.isEmpty) {
+ if (controller.archive.isEmpty) {
return const Center(
child: Icon(Icons.archive_outlined, size: 80),
);
}
return ListView.builder(
- itemCount: archive!.length,
+ itemCount: controller.archive.length,
itemBuilder: (BuildContext context, int i) => ChatListItem(
- archive![i],
+ controller.archive[i],
+ onForget: () => controller.forgetRoomAction(i),
),
);
}
diff --git a/lib/pages/bootstrap/bootstrap_dialog.dart b/lib/pages/bootstrap/bootstrap_dialog.dart
index 39caa4fe8..b9b944835 100644
--- a/lib/pages/bootstrap/bootstrap_dialog.dart
+++ b/lib/pages/bootstrap/bootstrap_dialog.dart
@@ -17,10 +17,10 @@ class BootstrapDialog extends StatefulWidget {
final bool wipe;
final Client client;
const BootstrapDialog({
- Key? key,
+ super.key,
this.wipe = false,
required this.client,
- }) : super(key: key);
+ });
Future show(BuildContext context) => showAdaptiveBottomSheet(
context: context,
diff --git a/lib/pages/chat/add_widget_tile.dart b/lib/pages/chat/add_widget_tile.dart
index 89ad8976d..11e3cce5d 100644
--- a/lib/pages/chat/add_widget_tile.dart
+++ b/lib/pages/chat/add_widget_tile.dart
@@ -8,7 +8,7 @@ import 'package:fluffychat/pages/chat/add_widget_tile_view.dart';
class AddWidgetTile extends StatefulWidget {
final Room room;
- const AddWidgetTile({Key? key, required this.room}) : super(key: key);
+ const AddWidgetTile({super.key, required this.room});
@override
State createState() => AddWidgetTileState();
diff --git a/lib/pages/chat/add_widget_tile_view.dart b/lib/pages/chat/add_widget_tile_view.dart
index 7cb448858..973e2a1d1 100644
--- a/lib/pages/chat/add_widget_tile_view.dart
+++ b/lib/pages/chat/add_widget_tile_view.dart
@@ -6,8 +6,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
class AddWidgetTileView extends StatelessWidget {
final AddWidgetTileState controller;
- const AddWidgetTileView({Key? key, required this.controller})
- : super(key: key);
+ const AddWidgetTileView({super.key, required this.controller});
@override
Widget build(BuildContext context) {
diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart
index 77fe8a923..6e01a693e 100644
--- a/lib/pages/chat/chat.dart
+++ b/lib/pages/chat/chat.dart
@@ -57,9 +57,9 @@ class ChatPage extends StatelessWidget {
final String roomId;
const ChatPage({
- Key? key,
+ super.key,
required this.roomId,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
@@ -109,9 +109,9 @@ class ChatPageWithRoom extends StatefulWidget {
final Room room;
const ChatPageWithRoom({
- Key? key,
+ super.key,
required this.room,
- }) : super(key: key);
+ });
@override
ChatController createState() => ChatController();
@@ -170,9 +170,8 @@ class ChatController extends State {
if (matrixFiles.isEmpty) return;
// Pangea#
- await showDialog(
+ await showAdaptiveDialog(
context: context,
- useRootNavigator: false,
builder: (c) => SendFileDialog(
files: matrixFiles,
room: room,
@@ -303,9 +302,19 @@ class ChatController extends State {
if (timeline?.allowNewEvent == false ||
scrollController.position.pixels > 0 && _scrolledUp == false) {
setState(() => _scrolledUp = true);
- } else if (scrollController.position.pixels == 0 && _scrolledUp == true) {
+ } else if (scrollController.position.pixels <= 0 && _scrolledUp == true) {
setState(() => _scrolledUp = false);
}
+
+ if (scrollController.position.pixels == 0 ||
+ scrollController.position.pixels == 64) {
+ requestFuture();
+ } else if (scrollController.position.pixels ==
+ scrollController.position.maxScrollExtent ||
+ scrollController.position.pixels + 64 ==
+ scrollController.position.maxScrollExtent) {
+ requestHistory();
+ }
}
void _loadDraft() async {
@@ -565,7 +574,6 @@ class ChatController extends State {
final l10n = L10n.of(context)!;
final dialogResult = await showOkCancelAlertDialog(
context: context,
- useRootNavigator: false,
title: l10n.commandInvalid,
message: l10n.commandMissing(commandMatch[0]!),
okLabel: l10n.sendAsText,
@@ -656,14 +664,13 @@ class ChatController extends State {
void sendFileAction() async {
final result = await AppLock.of(context).pauseWhile(
FilePicker.platform.pickFiles(
- allowMultiple: true,
+ allowMultiple: false,
withData: true,
),
);
if (result == null || result.files.isEmpty) return;
- await showDialog(
+ await showAdaptiveDialog(
context: context,
- useRootNavigator: false,
builder: (c) => SendFileDialog(
files: result.files
.map(
@@ -679,9 +686,8 @@ class ChatController extends State {
}
void sendImageFromClipBoard(Uint8List? image) async {
- await showDialog(
+ await showAdaptiveDialog(
context: context,
- useRootNavigator: false,
builder: (c) => SendFileDialog(
files: [
MatrixFile(
@@ -695,19 +701,17 @@ class ChatController extends State {
}
void sendImageAction() async {
- //AppLock.of(context).pauseWhile();
final result = await AppLock.of(context).pauseWhile(
FilePicker.platform.pickFiles(
type: FileType.image,
withData: true,
- allowMultiple: true,
+ allowMultiple: false,
),
);
if (result == null || result.files.isEmpty) return;
- await showDialog(
+ await showAdaptiveDialog(
context: context,
- useRootNavigator: false,
builder: (c) => SendFileDialog(
files: result.files
.map(
@@ -728,9 +732,8 @@ class ChatController extends State {
final file = await ImagePicker().pickImage(source: ImageSource.camera);
if (file == null) return;
final bytes = await file.readAsBytes();
- await showDialog(
+ await showAdaptiveDialog(
context: context,
- useRootNavigator: false,
builder: (c) => SendFileDialog(
files: [
MatrixImageFile(
@@ -746,12 +749,14 @@ class ChatController extends State {
void openVideoCameraAction() async {
// Make sure the textfield is unfocused before opening the camera
FocusScope.of(context).requestFocus(FocusNode());
- final file = await ImagePicker().pickVideo(source: ImageSource.camera);
+ final file = await ImagePicker().pickVideo(
+ source: ImageSource.camera,
+ maxDuration: const Duration(minutes: 1),
+ );
if (file == null) return;
final bytes = await file.readAsBytes();
- await showDialog(
+ await showAdaptiveDialog(
context: context,
- useRootNavigator: false,
builder: (c) => SendFileDialog(
files: [
MatrixVideoFile(
@@ -800,7 +805,6 @@ class ChatController extends State {
if (await Record().hasPermission() == false) return;
final result = await showDialog(
context: context,
- useRootNavigator: false,
barrierDismissible: false,
builder: (c) => const RecordingDialog(),
);
@@ -862,9 +866,8 @@ class ChatController extends State {
}
void sendLocationAction() async {
- await showDialog(
+ await showAdaptiveDialog(
context: context,
- useRootNavigator: false,
builder: (c) => SendLocationDialog(room: room),
);
}
@@ -919,7 +922,6 @@ class ChatController extends State {
);
if (score == null) return;
final reason = await showTextInputDialog(
- useRootNavigator: false,
context: context,
title: L10n.of(context)!.whyDoYouWantToReportThis,
okLabel: L10n.of(context)!.ok,
@@ -966,6 +968,25 @@ class ChatController extends State {
);
}
+ void deleteErrorEventsAction() async {
+ try {
+ if (selectedEvents.any((event) => event.status != EventStatus.error)) {
+ throw Exception(
+ 'Tried to delete failed to send events but one event is not failed to sent',
+ );
+ }
+ for (final event in selectedEvents) {
+ await event.remove();
+ }
+ setState(selectedEvents.clear);
+ } catch (e, s) {
+ ErrorReporter(
+ context,
+ 'Error while delete error events action',
+ ).onErrorCallback(e, s);
+ }
+ }
+
void redactEventsAction() async {
final reasonInput = selectedEvents.any((event) => event.status.isSent)
? await showTextInputDialog(
@@ -1026,6 +1047,7 @@ class ChatController extends State {
if (isArchived) return false;
final clients = Matrix.of(context).currentBundle;
for (final event in selectedEvents) {
+ if (!event.status.isSent) return false;
if (event.canRedact == false &&
!(clients!.any((cl) => event.senderId == cl!.userID))) return false;
}
@@ -1039,8 +1061,7 @@ class ChatController extends State {
!selectedEvents.single.status.isSent) {
return false;
}
- return currentRoomBundle
- .any((cl) => selectedEvents.first.senderId == cl!.userID);
+ return true;
}
bool get canEditSelectedEvents {
@@ -1108,7 +1129,7 @@ class ChatController extends State {
}
await scrollController.scrollToIndex(
eventIndex,
- preferPosition: AutoScrollPosition.end,
+ preferPosition: AutoScrollPosition.middle,
);
_updateScrollController();
}
@@ -1153,15 +1174,6 @@ class ChatController extends State {
return sendEmojiAction(emoji.emoji);
}
- void forgetRoom() async {
- final result = await showFutureLoadingDialog(
- context: context,
- future: room.forget,
- );
- if (result.error != null) return;
- context.go('/rooms/archive');
- }
-
void typeEmoji(Emoji? emoji) {
if (emoji == null) return;
final text = sendController.text;
@@ -1252,7 +1264,6 @@ class ChatController extends State {
void goToNewRoomAction() async {
if (OkCancelResult.ok !=
await showOkCancelAlertDialog(
- useRootNavigator: false,
context: context,
title: L10n.of(context)!.goToTheNewRoom,
message: room
@@ -1504,7 +1515,6 @@ class ChatController extends State {
context: context,
title: L10n.of(context)!.unavailable,
okLabel: L10n.of(context)!.next,
- useRootNavigator: false,
);
}
}
diff --git a/lib/pages/chat/chat_app_bar_title.dart b/lib/pages/chat/chat_app_bar_title.dart
index b8ad6fbda..b43adef05 100644
--- a/lib/pages/chat/chat_app_bar_title.dart
+++ b/lib/pages/chat/chat_app_bar_title.dart
@@ -7,7 +7,7 @@ import 'package:go_router/go_router.dart';
class ChatAppBarTitle extends StatelessWidget {
final ChatController controller;
- const ChatAppBarTitle(this.controller, {Key? key}) : super(key: key);
+ const ChatAppBarTitle(this.controller, {super.key});
@override
Widget build(BuildContext context) {
diff --git a/lib/pages/chat/chat_emoji_picker.dart b/lib/pages/chat/chat_emoji_picker.dart
index 863f547ff..86bf94682 100644
--- a/lib/pages/chat/chat_emoji_picker.dart
+++ b/lib/pages/chat/chat_emoji_picker.dart
@@ -7,7 +7,7 @@ import 'chat.dart';
class ChatEmojiPicker extends StatelessWidget {
final ChatController controller;
- const ChatEmojiPicker(this.controller, {Key? key}) : super(key: key);
+ const ChatEmojiPicker(this.controller, {super.key});
@override
Widget build(BuildContext context) {
@@ -32,10 +32,7 @@ class ChatEmojiPicker extends StatelessWidget {
iconColor: theme.colorScheme.primary.withOpacity(0.5),
iconColorSelected: theme.colorScheme.primary,
indicatorColor: theme.colorScheme.primary,
- noRecents: Text(
- L10n.of(context)!.emoteKeyboardNoRecents,
- style: theme.textTheme.bodyLarge,
- ),
+ noRecents: const NoRecent(),
skinToneDialogBgColor: Color.lerp(
theme.colorScheme.background,
theme.colorScheme.primaryContainer,
@@ -48,3 +45,15 @@ class ChatEmojiPicker extends StatelessWidget {
);
}
}
+
+class NoRecent extends StatelessWidget {
+ const NoRecent({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Text(
+ L10n.of(context)!.emoteKeyboardNoRecents,
+ style: Theme.of(context).textTheme.bodyLarge,
+ );
+ }
+}
diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart
index 40d17a6d0..fe5e84499 100644
--- a/lib/pages/chat/chat_event_list.dart
+++ b/lib/pages/chat/chat_event_list.dart
@@ -16,9 +16,9 @@ import 'package:scroll_to_index/scroll_to_index.dart';
class ChatEventList extends StatelessWidget {
final ChatController controller;
const ChatEventList({
- Key? key,
+ super.key,
required this.controller,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
@@ -31,138 +31,121 @@ class ChatEventList extends StatelessWidget {
thisEventsKeyMap[controller.timeline!.events[i].eventId] = i;
}
- return ListView.custom(
- padding: EdgeInsets.only(
- top: 16,
- bottom: 4,
- left: horizontalPadding,
- right: horizontalPadding,
- ),
- reverse: true,
- controller: controller.scrollController,
- keyboardDismissBehavior: PlatformInfos.isIOS
- ? ScrollViewKeyboardDismissBehavior.onDrag
- : ScrollViewKeyboardDismissBehavior.manual,
- childrenDelegate: SliverChildBuilderDelegate(
- (BuildContext context, int i) {
- // Footer to display typing indicator and read receipts:
- if (i == 0) {
- if (controller.timeline!.isRequestingFuture) {
- return const Center(
- child: CircularProgressIndicator.adaptive(strokeWidth: 2),
+ return SelectionArea(
+ child: ListView.custom(
+ padding: EdgeInsets.only(
+ top: 16,
+ bottom: 4,
+ left: horizontalPadding,
+ right: horizontalPadding,
+ ),
+ reverse: true,
+ controller: controller.scrollController,
+ keyboardDismissBehavior: PlatformInfos.isIOS
+ ? ScrollViewKeyboardDismissBehavior.onDrag
+ : ScrollViewKeyboardDismissBehavior.manual,
+ childrenDelegate: SliverChildBuilderDelegate(
+ (BuildContext context, int i) {
+ // Footer to display typing indicator and read receipts:
+ if (i == 0) {
+ if (controller.timeline!.isRequestingFuture) {
+ return const Center(
+ child: CircularProgressIndicator.adaptive(strokeWidth: 2),
+ );
+ }
+ if (controller.timeline!.canRequestFuture) {
+ return Center(
+ child: IconButton(
+ onPressed: controller.requestFuture,
+ icon: const Icon(Icons.refresh_outlined),
+ ),
+ );
+ }
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ SeenByRow(controller),
+ TypingIndicators(controller),
+ ],
);
}
- if (controller.timeline!.canRequestFuture) {
- return Builder(
- builder: (context) {
- WidgetsBinding.instance.addPostFrameCallback(
- (_) => controller.requestFuture(),
- );
- return Center(
- child: IconButton(
- onPressed: controller.requestFuture,
- icon: const Icon(Icons.refresh_outlined),
- ),
- );
- },
- );
- }
- return Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- SeenByRow(controller),
- TypingIndicators(controller),
- ],
- );
- }
- // #Pangea
- if (i == 1) {
- return controller.room.locked && !controller.room.isRoomAdmin
- ? const LockedChatMessage()
- : const SizedBox.shrink();
- }
- // Pangea#
-
- // Request history button or progress indicator:
- if (i == controller.timeline!.events.length + 1) {
- if (controller.timeline!.isRequestingHistory) {
- return const Center(
- child: CircularProgressIndicator.adaptive(strokeWidth: 2),
- );
- }
- if (controller.timeline!.canRequestHistory) {
- return Builder(
- builder: (context) {
- WidgetsBinding.instance.addPostFrameCallback(
- (_) => controller.requestHistory(),
- );
- return Center(
- child: IconButton(
- onPressed: controller.requestHistory,
- icon: const Icon(Icons.refresh_outlined),
- ),
- );
- },
- );
- }
- return const SizedBox.shrink();
- }
-
- // The message at this index:
- // #Pangea
- // final event = controller.timeline!.events[i - 1];
- final event = controller.timeline!.events[i - 2];
- // Pangea#
-
- return AutoScrollTag(
- key: ValueKey(event.eventId),
// #Pangea
- // index: i - 1,
- index: i - 2,
+ if (i == 1) {
+ return controller.room.locked && !controller.room.isRoomAdmin
+ ? const LockedChatMessage()
+ : const SizedBox.shrink();
+ }
// Pangea#
- controller: controller.scrollController,
- child: event.isVisibleInGui
- ? Message(
- event,
- onSwipe: (direction) =>
- controller.replyAction(replyTo: event),
- onInfoTab: controller.showEventInfo,
- onAvatarTab: (Event event) => showAdaptiveBottomSheet(
- context: context,
- builder: (c) => UserBottomSheet(
- user: event.senderFromMemoryOrFallback,
- outerContext: context,
- onMention: () => controller.sendController.text +=
- '${event.senderFromMemoryOrFallback.mention} ',
+
+ // Request history button or progress indicator:
+ if (i == controller.timeline!.events.length + 1) {
+ if (controller.timeline!.isRequestingHistory) {
+ return const Center(
+ child: CircularProgressIndicator.adaptive(strokeWidth: 2),
+ );
+ }
+ if (controller.timeline!.canRequestHistory) {
+ return Center(
+ child: IconButton(
+ onPressed: controller.requestHistory,
+ icon: const Icon(Icons.refresh_outlined),
+ ),
+ );
+ }
+ return const SizedBox.shrink();
+ }
+ // The message at this index:
+ // #Pangea
+ // final event = controller.timeline!.events[i - 1];
+ final event = controller.timeline!.events[i - 2];
+ // Pangea#
+
+ return AutoScrollTag(
+ key: ValueKey(event.eventId),
+ index: i - 1,
+ controller: controller.scrollController,
+ child: event.isVisibleInGui
+ ? Message(
+ event,
+ onSwipe: () => controller.replyAction(replyTo: event),
+ onInfoTab: controller.showEventInfo,
+ onAvatarTab: (Event event) => showAdaptiveBottomSheet(
+ context: context,
+ builder: (c) => UserBottomSheet(
+ user: event.senderFromMemoryOrFallback,
+ outerContext: context,
+ onMention: () => controller.sendController.text +=
+ '${event.senderFromMemoryOrFallback.mention} ',
+ ),
),
- ),
- onSelect: controller.onSelectMessage,
- scrollToEventId: (String eventId) =>
- controller.scrollToEventId(eventId),
- // #Pangea
- // longPressSelect: controller.selectedEvents.isEmpty,
- selectedDisplayLang: controller
- .choreographer.messageOptions.selectedDisplayLang,
- immersionMode: controller.choreographer.immersionMode,
- definitions: controller.choreographer.definitionsEnabled,
- // Pangea#
- selected: controller.selectedEvents
- .any((e) => e.eventId == event.eventId),
- timeline: controller.timeline!,
- displayReadMarker:
- controller.readMarkerEventId == event.eventId &&
- controller.timeline?.allowNewEvent == false,
- nextEvent: i < controller.timeline!.events.length
- ? controller.timeline!.events[i]
- : null,
- )
- : const SizedBox.shrink(),
- );
- },
- childCount: controller.timeline!.events.length + 2,
- findChildIndexCallback: (key) =>
- controller.findChildIndexCallback(key, thisEventsKeyMap),
+ onSelect: controller.onSelectMessage,
+ scrollToEventId: (String eventId) =>
+ controller.scrollToEventId(eventId),
+ // #Pangea
+ // longPressSelect: controller.selectedEvents.isEmpty,
+ selectedDisplayLang: controller
+ .choreographer.messageOptions.selectedDisplayLang,
+ immersionMode: controller.choreographer.immersionMode,
+ definitions: controller.choreographer.definitionsEnabled,
+ // Pangea#
+ selected: controller.selectedEvents
+ .any((e) => e.eventId == event.eventId),
+ timeline: controller.timeline!,
+ displayReadMarker:
+ controller.readMarkerEventId == event.eventId &&
+ controller.timeline?.allowNewEvent == false,
+ nextEvent: i < controller.timeline!.events.length
+ ? controller.timeline!.events[i]
+ : null,
+ )
+ : const SizedBox.shrink(),
+ );
+ },
+ childCount: controller.timeline!.events.length + 2,
+ findChildIndexCallback: (key) =>
+ controller.findChildIndexCallback(key, thisEventsKeyMap),
+ ),
),
);
}
diff --git a/lib/pages/chat/chat_input_row.dart b/lib/pages/chat/chat_input_row.dart
index 659201814..ddd01249e 100644
--- a/lib/pages/chat/chat_input_row.dart
+++ b/lib/pages/chat/chat_input_row.dart
@@ -18,7 +18,7 @@ import 'input_bar.dart';
class ChatInputRow extends StatelessWidget {
final ChatController controller;
- const ChatInputRow(this.controller, {Key? key}) : super(key: key);
+ const ChatInputRow(this.controller, {super.key});
@override
Widget build(BuildContext context) {
@@ -39,18 +39,36 @@ class ChatInputRow extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: controller.selectMode
? [
- SizedBox(
- height: 56,
- child: TextButton(
- onPressed: controller.forwardEventsAction,
- child: Row(
- children: [
- const Icon(Icons.keyboard_arrow_left_outlined),
- Text(L10n.of(context)!.forward),
- ],
+ if (controller.selectedEvents
+ .every((event) => event.status == EventStatus.error))
+ SizedBox(
+ height: 56,
+ child: TextButton(
+ style: TextButton.styleFrom(
+ foregroundColor: Theme.of(context).colorScheme.error,
+ ),
+ onPressed: controller.deleteErrorEventsAction,
+ child: Row(
+ children: [
+ const Icon(Icons.delete),
+ Text(L10n.of(context)!.delete),
+ ],
+ ),
+ ),
+ )
+ else
+ SizedBox(
+ height: 56,
+ child: TextButton(
+ onPressed: controller.forwardEventsAction,
+ child: Row(
+ children: [
+ const Icon(Icons.keyboard_arrow_left_outlined),
+ Text(L10n.of(context)!.forward),
+ ],
+ ),
),
),
- ),
controller.selectedEvents.length == 1
? controller.selectedEvents.first
.getDisplayEvent(controller.timeline!)
@@ -271,7 +289,10 @@ class ChatInputRow extends StatelessWidget {
room: controller.room,
minLines: 1,
maxLines: 8,
- autofocus: !PlatformInfos.isMobile,
+ // #Pangea
+ // autofocus: !PlatformInfos.isMobile,
+ autofocus: false,
+ // Pangea#
keyboardType: TextInputType.multiline,
textInputAction:
AppConfig.sendOnEnter ? TextInputAction.send : null,
@@ -279,7 +300,7 @@ class ChatInputRow extends StatelessWidget {
// onSubmitted: controller.onInputBarSubmitted,
onSubmitted: (String value) =>
controller.onInputBarSubmitted(value, context),
- // Pangea#
+ // #Pangea
onSubmitImage: controller.sendImageFromClipBoard,
focusNode: controller.inputFocus,
controller: controller.sendController,
@@ -307,7 +328,6 @@ class ChatInputRow extends StatelessWidget {
),
if (!PlatformInfos.isMobile ||
controller.inputText.isNotEmpty)
- // #Pangea
ChoreographerSendButton(controller: controller),
// Container(
// height: 56,
@@ -318,7 +338,6 @@ class ChatInputRow extends StatelessWidget {
// tooltip: L10n.of(context)!.send,
// ),
// ),
- // Pangea#
],
),
],
@@ -329,7 +348,7 @@ class ChatInputRow extends StatelessWidget {
class _ChatAccountPicker extends StatelessWidget {
final ChatController controller;
- const _ChatAccountPicker(this.controller, {Key? key}) : super(key: key);
+ const _ChatAccountPicker(this.controller);
void _popupMenuButtonSelected(String mxid, BuildContext context) {
final client = Matrix.of(context)
diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart
index d5567022a..e9ff07063 100644
--- a/lib/pages/chat/chat_view.dart
+++ b/lib/pages/chat/chat_view.dart
@@ -32,7 +32,7 @@ enum _EventContextAction { info, report }
class ChatView extends StatelessWidget {
final ChatController controller;
- const ChatView(this.controller, {Key? key}) : super(key: key);
+ const ChatView(this.controller, {super.key});
List _appBarActions(BuildContext context) {
if (controller.selectMode) {
@@ -97,41 +97,31 @@ class ChatView extends StatelessWidget {
],
),
),
- PopupMenuItem(
- value: _EventContextAction.report,
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- const Icon(
- Icons.shield_outlined,
- color: Colors.red,
- ),
- const SizedBox(width: 12),
- Text(L10n.of(context)!.reportMessage),
- ],
+ if (controller.selectedEvents.single.status.isSent)
+ PopupMenuItem(
+ value: _EventContextAction.report,
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const Icon(
+ Icons.shield_outlined,
+ color: Colors.red,
+ ),
+ const SizedBox(width: 12),
+ Text(L10n.of(context)!.reportMessage),
+ ],
+ ),
),
- ),
],
),
];
+ // #Pangea
+ } else {
+ return [
+ ChatSettingsPopupMenu(controller.room, !controller.room.isDirectChat),
+ ];
}
- // #Pangea
- // else if (controller.isArchived) {
- // return [
- // Padding(
- // padding: const EdgeInsets.all(8.0),
- // child: TextButton.icon(
- // onPressed: controller.forgetRoom,
- // style: TextButton.styleFrom(
- // foregroundColor: Theme.of(context).colorScheme.error,
- // ),
- // icon: const Icon(Icons.delete_forever_outlined),
- // label: Text(L10n.of(context)!.delete),
- // ),
- // ),
- // ];
- // }
- //else {
+ // } else if (!controller.room.isArchived) {
// return [
// if (Matrix.of(context).voipPlugin != null &&
// controller.room.isDirectChat)
@@ -144,9 +134,7 @@ class ChatView extends StatelessWidget {
// ChatSettingsPopupMenu(controller.room, true),
// ];
// }
- return [
- ChatSettingsPopupMenu(controller.room, !controller.room.isDirectChat),
- ];
+ // return [];
// Pangea#
}
@@ -236,7 +224,7 @@ class ChatView extends StatelessWidget {
roomID: controller.roomId,
)
: null)
- // Pangea#
+ // #Pangea
: null,
body: DropTarget(
onDragDone: controller.onDragDone,
@@ -320,6 +308,7 @@ class ChatView extends StatelessWidget {
if (controller.room.canSendDefaultMessages &&
controller.room.membership == Membership.join)
// #Pangea
+ // Container(
ConditionalFlexible(
isScroll: controller.isRowScrollable,
child: ConditionalScroll(
@@ -328,9 +317,8 @@ class ChatView extends StatelessWidget {
onChange: (size, position) {
controller.inputRowSize = size!.height;
},
- child:
- // Pangea#
- Container(
+ child: Container(
+ // Pangea#
margin: EdgeInsets.only(
bottom: bottomSheetPadding,
left: bottomSheetPadding,
@@ -419,21 +407,19 @@ class ChatView extends StatelessWidget {
),
),
),
+ if (controller.dragging)
+ Container(
+ color: Theme.of(context)
+ .scaffoldBackgroundColor
+ .withOpacity(0.9),
+ alignment: Alignment.center,
+ child: const Icon(
+ Icons.upload_outlined,
+ size: 100,
+ ),
+ ),
],
),
- // #Pangea
- // if (controller.dragging)
- // Container(
- // color: Theme.of(context)
- // .scaffoldBackgroundColor
- // .withOpacity(0.9),
- // alignment: Alignment.center,
- // child: const Icon(
- // Icons.upload_outlined,
- // size: 100,
- // ),
- // ),
- // Pangea#
),
],
),
@@ -478,3 +464,4 @@ class ConditionalScroll extends StatelessWidget {
}
}
// #Pangea
+
diff --git a/lib/pages/chat/edit_widgets_dialog.dart b/lib/pages/chat/edit_widgets_dialog.dart
index 7d9579a10..2ba163feb 100644
--- a/lib/pages/chat/edit_widgets_dialog.dart
+++ b/lib/pages/chat/edit_widgets_dialog.dart
@@ -8,7 +8,7 @@ import 'add_widget_tile.dart';
class EditWidgetsDialog extends StatelessWidget {
final Room room;
- const EditWidgetsDialog({Key? key, required this.room}) : super(key: key);
+ const EditWidgetsDialog({super.key, required this.room});
@override
Widget build(BuildContext context) {
diff --git a/lib/pages/chat/encryption_button.dart b/lib/pages/chat/encryption_button.dart
index 092ad24c7..91fb262aa 100644
--- a/lib/pages/chat/encryption_button.dart
+++ b/lib/pages/chat/encryption_button.dart
@@ -7,7 +7,7 @@ import '../../widgets/matrix.dart';
class EncryptionButton extends StatelessWidget {
final Room room;
- const EncryptionButton(this.room, {Key? key}) : super(key: key);
+ const EncryptionButton(this.room, {super.key});
@override
Widget build(BuildContext context) {
diff --git a/lib/pages/chat/event_info_dialog.dart b/lib/pages/chat/event_info_dialog.dart
index 5ac29c80c..0846e137e 100644
--- a/lib/pages/chat/event_info_dialog.dart
+++ b/lib/pages/chat/event_info_dialog.dart
@@ -24,8 +24,8 @@ class EventInfoDialog extends StatelessWidget {
const EventInfoDialog({
required this.event,
required this.l10n,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
String get prettyJson {
const JsonDecoder decoder = JsonDecoder();
diff --git a/lib/pages/chat/events/audio_player.dart b/lib/pages/chat/events/audio_player.dart
index 4cced05b0..bbd0b4844 100644
--- a/lib/pages/chat/events/audio_player.dart
+++ b/lib/pages/chat/events/audio_player.dart
@@ -19,8 +19,7 @@ class AudioPlayerWidget extends StatefulWidget {
static const int wavesCount = 40;
- const AudioPlayerWidget(this.event, {this.color = Colors.black, Key? key})
- : super(key: key);
+ const AudioPlayerWidget(this.event, {this.color = Colors.black, super.key});
@override
AudioPlayerState createState() => AudioPlayerState();
diff --git a/lib/pages/chat/events/cute_events.dart b/lib/pages/chat/events/cute_events.dart
index 49024750b..4369e5254 100644
--- a/lib/pages/chat/events/cute_events.dart
+++ b/lib/pages/chat/events/cute_events.dart
@@ -8,7 +8,7 @@ import 'package:matrix/matrix.dart';
class CuteContent extends StatefulWidget {
final Event event;
- const CuteContent(this.event, {Key? key}) : super(key: key);
+ const CuteContent(this.event, {super.key});
@override
State createState() => _CuteContentState();
@@ -96,10 +96,10 @@ class CuteEventOverlay extends StatefulWidget {
final VoidCallback onAnimationEnd;
const CuteEventOverlay({
- Key? key,
+ super.key,
required this.emoji,
required this.onAnimationEnd,
- }) : super(key: key);
+ });
@override
State createState() => _CuteEventOverlayState();
@@ -177,7 +177,7 @@ class _CuteOverlayContent extends StatelessWidget {
static const double size = 64.0;
final String emoji;
- const _CuteOverlayContent({Key? key, required this.emoji}) : super(key: key);
+ const _CuteOverlayContent({required this.emoji});
@override
Widget build(BuildContext context) {
diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart
index 9ac0f96c1..cc5641189 100644
--- a/lib/pages/chat/events/html_message.dart
+++ b/lib/pages/chat/events/html_message.dart
@@ -19,11 +19,11 @@ class HtmlMessage extends StatelessWidget {
final Color textColor;
const HtmlMessage({
- Key? key,
+ super.key,
required this.html,
required this.room,
this.textColor = Colors.black,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
@@ -62,87 +62,93 @@ class HtmlMessage extends StatelessWidget {
final linkColor = textColor.withAlpha(150);
- // there is no need to pre-validate the html, as we validate it while rendering
- return Html(
- data: linkifiedRenderHtml,
- style: {
- '*': Style(
+ final blockquoteStyle = Style(
+ border: Border(
+ left: BorderSide(
+ width: 3,
color: textColor,
- margin: Margins.all(0),
- fontSize: FontSize(fontSize),
),
- 'a': Style(color: linkColor, textDecorationColor: linkColor),
- 'h1': Style(
- fontSize: FontSize(fontSize * 2),
- lineHeight: LineHeight.number(1.5),
- fontWeight: FontWeight.w600,
- ),
- 'h2': Style(
- fontSize: FontSize(fontSize * 1.75),
- lineHeight: LineHeight.number(1.5),
- fontWeight: FontWeight.w500,
- ),
- 'h3': Style(
- fontSize: FontSize(fontSize * 1.5),
- lineHeight: LineHeight.number(1.5),
- ),
- 'h4': Style(
- fontSize: FontSize(fontSize * 1.25),
- lineHeight: LineHeight.number(1.5),
- ),
- 'h5': Style(
- fontSize: FontSize(fontSize * 1.25),
- lineHeight: LineHeight.number(1.5),
- ),
- 'h6': Style(
- fontSize: FontSize(fontSize),
- lineHeight: LineHeight.number(1.5),
- ),
- 'blockquote': Style(
- border: Border(
- left: BorderSide(
- width: 3,
- color: textColor,
- ),
+ ),
+ padding: HtmlPaddings.only(left: 6, bottom: 0),
+ );
+
+ // there is no need to pre-validate the html, as we validate it while rendering
+ return MouseRegion(
+ cursor: SystemMouseCursors.text,
+ child: Html(
+ data: linkifiedRenderHtml,
+ style: {
+ '*': Style(
+ color: textColor,
+ margin: Margins.all(0),
+ fontSize: FontSize(fontSize),
),
- padding: HtmlPaddings.only(left: 6, bottom: 0),
- ),
- 'hr': Style(
- border: Border.all(color: textColor, width: 0.5),
- ),
- 'table': Style(
- border: Border.all(color: textColor, width: 0.5),
- ),
- 'tr': Style(
- border: Border.all(color: textColor, width: 0.5),
- ),
- 'td': Style(
- border: Border.all(color: textColor, width: 0.5),
- padding: HtmlPaddings.all(2),
- ),
- 'th': Style(
- border: Border.all(color: textColor, width: 0.5),
- ),
- },
- extensions: [
- RoomPillExtension(context, room),
- CodeExtension(fontSize: fontSize),
- MatrixMathExtension(
- style: TextStyle(fontSize: fontSize, color: textColor),
- ),
- const TableHtmlExtension(),
- SpoilerExtension(textColor: textColor),
- const ImageExtension(),
- FontColorExtension(),
- ],
- onLinkTap: (url, _, __) => UrlLauncher(context, url).launchUrl(),
- onlyRenderTheseTags: const {
- ...allowedHtmlTags,
- // Needed to make it work properly
- 'body',
- 'html',
- },
- shrinkWrap: true,
+ 'a': Style(color: linkColor, textDecorationColor: linkColor),
+ 'h1': Style(
+ fontSize: FontSize(fontSize * 2),
+ lineHeight: LineHeight.number(1.5),
+ fontWeight: FontWeight.w600,
+ ),
+ 'h2': Style(
+ fontSize: FontSize(fontSize * 1.75),
+ lineHeight: LineHeight.number(1.5),
+ fontWeight: FontWeight.w500,
+ ),
+ 'h3': Style(
+ fontSize: FontSize(fontSize * 1.5),
+ lineHeight: LineHeight.number(1.5),
+ ),
+ 'h4': Style(
+ fontSize: FontSize(fontSize * 1.25),
+ lineHeight: LineHeight.number(1.5),
+ ),
+ 'h5': Style(
+ fontSize: FontSize(fontSize * 1.25),
+ lineHeight: LineHeight.number(1.5),
+ ),
+ 'h6': Style(
+ fontSize: FontSize(fontSize),
+ lineHeight: LineHeight.number(1.5),
+ ),
+ 'blockquote': blockquoteStyle,
+ 'tg-forward': blockquoteStyle,
+ 'hr': Style(
+ border: Border.all(color: textColor, width: 0.5),
+ ),
+ 'table': Style(
+ border: Border.all(color: textColor, width: 0.5),
+ ),
+ 'tr': Style(
+ border: Border.all(color: textColor, width: 0.5),
+ ),
+ 'td': Style(
+ border: Border.all(color: textColor, width: 0.5),
+ padding: HtmlPaddings.all(2),
+ ),
+ 'th': Style(
+ border: Border.all(color: textColor, width: 0.5),
+ ),
+ },
+ extensions: [
+ RoomPillExtension(context, room),
+ CodeExtension(fontSize: fontSize),
+ MatrixMathExtension(
+ style: TextStyle(fontSize: fontSize, color: textColor),
+ ),
+ const TableHtmlExtension(),
+ SpoilerExtension(textColor: textColor),
+ const ImageExtension(),
+ FontColorExtension(),
+ ],
+ onLinkTap: (url, _, __) => UrlLauncher(context, url).launchUrl(),
+ onlyRenderTheseTags: const {
+ ...allowedHtmlTags,
+ // Needed to make it work properly
+ 'body',
+ 'html',
+ },
+ shrinkWrap: true,
+ ),
);
}
@@ -190,6 +196,8 @@ class HtmlMessage extends StatelessWidget {
'ruby',
'rp',
'rt',
+ // Workaround for https://github.com/krille-chan/fluffychat/issues/507
+ 'tg-forward',
};
}
diff --git a/lib/pages/chat/events/image_bubble.dart b/lib/pages/chat/events/image_bubble.dart
index c5ca54afd..2225b0a77 100644
--- a/lib/pages/chat/events/image_bubble.dart
+++ b/lib/pages/chat/events/image_bubble.dart
@@ -27,8 +27,8 @@ class ImageBubble extends StatelessWidget {
this.height = 300,
this.animated = false,
this.onTap,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
Widget _buildPlaceholder(BuildContext context) {
if (event.messageType == MessageTypes.Sticker) {
diff --git a/lib/pages/chat/events/map_bubble.dart b/lib/pages/chat/events/map_bubble.dart
index b003bc6ed..c441c7d64 100644
--- a/lib/pages/chat/events/map_bubble.dart
+++ b/lib/pages/chat/events/map_bubble.dart
@@ -17,8 +17,8 @@ class MapBubble extends StatelessWidget {
this.width = 400,
this.height = 400,
this.radius = 10.0,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart
index 3a173925d..cc4b092f3 100644
--- a/lib/pages/chat/events/message.dart
+++ b/lib/pages/chat/events/message.dart
@@ -12,6 +12,7 @@ import 'package:matrix/matrix.dart';
import 'package:swipe_to_action/swipe_to_action.dart';
import '../../../config/app_config.dart';
+import '../../../widgets/hover_builder.dart';
import 'message_content.dart';
import 'message_reactions.dart';
import 'reply_content.dart';
@@ -26,7 +27,7 @@ class Message extends StatelessWidget {
final void Function(Event)? onAvatarTab;
final void Function(Event)? onInfoTab;
final void Function(String)? scrollToEventId;
- final void Function(SwipeDirection) onSwipe;
+ final void Function() onSwipe;
final bool longPressSelect;
final bool selected;
final Timeline timeline;
@@ -34,7 +35,7 @@ class Message extends StatelessWidget {
final LanguageModel? selectedDisplayLang;
final bool immersionMode;
final bool definitions;
- // Pangea#
+ // #Pangea
const Message(
this.event, {
@@ -52,13 +53,9 @@ class Message extends StatelessWidget {
required this.selectedDisplayLang,
required this.immersionMode,
required this.definitions,
- // Pangea#
- Key? key,
- }) : super(key: key);
-
- /// Indicates wheither the user may use a mouse instead
- /// of touchscreen.
- static bool useMouse = false;
+ // #Pangea
+ super.key,
+ });
@override
Widget build(BuildContext context) {
@@ -85,7 +82,7 @@ class Message extends StatelessWidget {
final client = Matrix.of(context).client;
final ownMessage = event.senderId == client.userID;
final alignment = ownMessage ? Alignment.topRight : Alignment.topLeft;
- var color = Theme.of(context).colorScheme.surfaceVariant;
+ var color = Theme.of(context).colorScheme.onInverseSurface;
final displayTime = event.type == EventTypes.RoomCreate ||
nextEvent == null ||
!event.originServerTs.sameEnvironment(nextEvent!.originServerTs);
@@ -99,7 +96,7 @@ class Message extends StatelessWidget {
nextEvent!.senderId == event.senderId &&
!displayTime;
final textColor = ownMessage
- ? Theme.of(context).colorScheme.onPrimary
+ ? Theme.of(context).colorScheme.onPrimaryContainer
: Theme.of(context).colorScheme.onBackground;
final rowMainAxisAlignment =
ownMessage ? MainAxisAlignment.end : MainAxisAlignment.start;
@@ -129,230 +126,234 @@ class Message extends StatelessWidget {
if (ownMessage) {
color = displayEvent.status.isError
? Colors.redAccent
- : Theme.of(context).colorScheme.primary;
+ : Theme.of(context).colorScheme.primaryContainer;
}
- // #Pangea
+ //#Pangea
final pangeaMessageEvent = PangeaMessageEvent(
event: event,
timeline: timeline,
ownMessage: ownMessage,
selected: selected,
);
- // Pangea#
+ //#Pangea
- final row = Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisAlignment: rowMainAxisAlignment,
- children: [
- sameSender || ownMessage
- ? SizedBox(
- width: Avatar.defaultSize,
- child: Padding(
- padding: const EdgeInsets.only(top: 8.0),
- child: Center(
- child: SizedBox(
- width: 16,
- height: 16,
- child: event.status == EventStatus.sending
- ? const CircularProgressIndicator.adaptive(
- strokeWidth: 2,
- )
- : event.status == EventStatus.error
- ? const Icon(Icons.error, color: Colors.red)
- : null,
- ),
- ),
- ),
- )
- : FutureBuilder(
- future: event.fetchSenderUser(),
- builder: (context, snapshot) {
- final user =
- snapshot.data ?? event.senderFromMemoryOrFallback;
- return Avatar(
- mxContent: user.avatarUrl,
- name: user.calcDisplayname(),
- onTap: () => onAvatarTab!(event),
- );
- },
+ final row = HoverBuilder(
+ builder: (context, hovered) => Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: rowMainAxisAlignment,
+ children: [
+ if (hovered || selected)
+ SizedBox(
+ width: Avatar.defaultSize,
+ height: Avatar.defaultSize - 8,
+ child: Checkbox.adaptive(
+ value: selected,
+ onChanged: (_) => onSelect?.call(event),
),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisSize: MainAxisSize.min,
- children: [
- if (!sameSender)
- Padding(
- padding: const EdgeInsets.only(left: 8.0, bottom: 4),
- child: ownMessage || event.room.isDirectChat
- ? const SizedBox(height: 12)
- : FutureBuilder(
- future: event.fetchSenderUser(),
- builder: (context, snapshot) {
- final displayname =
- snapshot.data?.calcDisplayname() ??
- event.senderFromMemoryOrFallback
- .calcDisplayname();
- return Text(
- displayname,
- style: TextStyle(
- fontSize: 12,
- fontWeight: FontWeight.bold,
- color: (Theme.of(context).brightness ==
- Brightness.light
- ? displayname.color
- : displayname.lightColorText),
- ),
- );
- },
- ),
+ )
+ else if (sameSender || ownMessage)
+ SizedBox(
+ width: Avatar.defaultSize,
+ child: Center(
+ child: SizedBox(
+ width: 16,
+ height: 16,
+ child: event.status == EventStatus.sending
+ ? const CircularProgressIndicator.adaptive(
+ strokeWidth: 2,
+ )
+ : event.status == EventStatus.error
+ ? const Icon(Icons.error, color: Colors.red)
+ : null,
),
- Container(
- alignment: alignment,
- padding: const EdgeInsets.only(left: 8),
- child: Material(
- color: noBubble ? Colors.transparent : color,
- borderRadius: borderRadius,
- clipBehavior: Clip.antiAlias,
- // #Pangea
- child: CompositedTransformTarget(
- link: MatrixState.pAnyState
- .layerLinkAndKey(event.eventId)
- .link,
- child: Container(
- key: MatrixState.pAnyState
+ ),
+ )
+ else
+ FutureBuilder(
+ future: event.fetchSenderUser(),
+ builder: (context, snapshot) {
+ final user = snapshot.data ?? event.senderFromMemoryOrFallback;
+ return Avatar(
+ mxContent: user.avatarUrl,
+ name: user.calcDisplayname(),
+ onTap: () => onAvatarTab!(event),
+ );
+ },
+ ),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (!sameSender)
+ Padding(
+ padding: const EdgeInsets.only(left: 8.0, bottom: 4),
+ child: ownMessage || event.room.isDirectChat
+ ? const SizedBox(height: 12)
+ : FutureBuilder(
+ future: event.fetchSenderUser(),
+ builder: (context, snapshot) {
+ final displayname =
+ snapshot.data?.calcDisplayname() ??
+ event.senderFromMemoryOrFallback
+ .calcDisplayname();
+ return Text(
+ displayname,
+ style: TextStyle(
+ fontSize: 12,
+ fontWeight: FontWeight.bold,
+ color: (Theme.of(context).brightness ==
+ Brightness.light
+ ? displayname.color
+ : displayname.lightColorText),
+ ),
+ );
+ },
+ ),
+ ),
+ Container(
+ alignment: alignment,
+ padding: const EdgeInsets.only(left: 8),
+ child: Material(
+ color: noBubble ? Colors.transparent : color,
+ borderRadius: borderRadius,
+ clipBehavior: Clip.antiAlias,
+ // #Pangea
+ child: CompositedTransformTarget(
+ link: MatrixState.pAnyState
.layerLinkAndKey(event.eventId)
- .key,
- // Pangea#
- decoration: BoxDecoration(
- borderRadius:
- BorderRadius.circular(AppConfig.borderRadius),
- ),
- padding: noBubble || noPadding
- ? EdgeInsets.zero
- : const EdgeInsets.symmetric(
- horizontal: 16,
- vertical: 8,
- ),
- constraints: const BoxConstraints(
- maxWidth: FluffyThemes.columnWidth * 1.5,
- ),
- child: Stack(
- children: [
- Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- if (event.relationshipType ==
- RelationshipTypes.reply)
- FutureBuilder(
- future: event.getReplyEvent(timeline),
- builder: (BuildContext context, snapshot) {
- final replyEvent = snapshot.hasData
- ? snapshot.data!
- : Event(
- eventId: event.relationshipEventId!,
- content: {
- 'msgtype': 'm.text',
- 'body': '...',
- },
- senderId: event.senderId,
- type: 'm.room.message',
- room: event.room,
- status: EventStatus.sent,
- originServerTs: DateTime.now(),
- );
- return InkWell(
- onTap: () {
- if (scrollToEventId != null) {
- scrollToEventId!(replyEvent.eventId);
- }
- },
- child: AbsorbPointer(
- child: Container(
- margin: const EdgeInsets.symmetric(
- vertical: 4.0,
- ),
- child: ReplyContent(
- replyEvent,
- ownMessage: ownMessage,
- timeline: timeline,
- ),
+ .link,
+ child: Container(
+ key: MatrixState.pAnyState
+ .layerLinkAndKey(event.eventId)
+ .key,
+ // #Pangea
+ decoration: BoxDecoration(
+ borderRadius:
+ BorderRadius.circular(AppConfig.borderRadius),
+ ),
+ padding: noBubble || noPadding
+ ? EdgeInsets.zero
+ : const EdgeInsets.symmetric(
+ horizontal: 16,
+ vertical: 8,
+ ),
+ constraints: const BoxConstraints(
+ maxWidth: FluffyThemes.columnWidth * 1.5,
+ ),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ if (event.relationshipType ==
+ RelationshipTypes.reply)
+ FutureBuilder(
+ future: event.getReplyEvent(timeline),
+ builder: (BuildContext context, snapshot) {
+ final replyEvent = snapshot.hasData
+ ? snapshot.data!
+ : Event(
+ eventId: event.relationshipEventId!,
+ content: {
+ 'msgtype': 'm.text',
+ 'body': '...',
+ },
+ senderId: event.senderId,
+ type: 'm.room.message',
+ room: event.room,
+ status: EventStatus.sent,
+ originServerTs: DateTime.now(),
+ );
+ return InkWell(
+ onTap: () {
+ if (scrollToEventId != null) {
+ scrollToEventId!(replyEvent.eventId);
+ }
+ },
+ child: AbsorbPointer(
+ child: Container(
+ margin: const EdgeInsets.symmetric(
+ vertical: 4.0,
+ ),
+ child: ReplyContent(
+ replyEvent,
+ ownMessage: ownMessage,
+ timeline: timeline,
),
),
- );
- },
- ),
- MessageContent(
- displayEvent,
- textColor: textColor,
- onInfoTab: onInfoTab,
- // #Pangea
- selected: selected,
- pangeaMessageEvent: pangeaMessageEvent,
- selectedDisplayLang: selectedDisplayLang,
- immersionMode: immersionMode,
- definitions: definitions,
- // Pangea#
+ ),
+ );
+ },
),
- if (event.hasAggregatedEvents(
- timeline,
- RelationshipTypes.edit,
- )
- // #Pangea
- ||
- (pangeaMessageEvent.showUseType)
- // Pangea#
- )
- Padding(
- padding: const EdgeInsets.only(
- top: 4.0,
- ),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- // #Pangea
- if (pangeaMessageEvent.showUseType) ...[
- pangeaMessageEvent.useType.iconView(
- context,
- textColor.withAlpha(164),
- ),
- const SizedBox(width: 4)
- ],
- if (event.hasAggregatedEvents(
- timeline,
- RelationshipTypes.edit,
- )) ...[
- // Pangea#
- Icon(
- Icons.edit_outlined,
- color: textColor.withAlpha(164),
- size: 14,
- ),
- Text(
- ' - ${displayEvent.originServerTs.localizedTimeShort(context)}',
- style: TextStyle(
- color: textColor.withAlpha(164),
- fontSize: 12,
- ),
- ),
- ],
- ],
- ),
+ MessageContent(
+ displayEvent,
+ textColor: textColor,
+ onInfoTab: onInfoTab,
+ // #Pangea
+ selected: selected,
+ pangeaMessageEvent: pangeaMessageEvent,
+ selectedDisplayLang: selectedDisplayLang,
+ immersionMode: immersionMode,
+ definitions: definitions,
+ // Pangea#
+ ),
+ if (event.hasAggregatedEvents(
+ timeline,
+ RelationshipTypes.edit,
+ )
+ // #Pangea
+ ||
+ (pangeaMessageEvent.showUseType)
+ // #Pangea
+ )
+ Padding(
+ padding: const EdgeInsets.only(
+ top: 4.0,
),
- ],
- ),
- ],
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ // #Pangea
+ if (pangeaMessageEvent.showUseType) ...[
+ pangeaMessageEvent.useType.iconView(
+ context,
+ textColor.withAlpha(164),
+ ),
+ const SizedBox(width: 4),
+ ],
+ if (event.hasAggregatedEvents(
+ timeline,
+ RelationshipTypes.edit,
+ )) ...[
+ // #Pangea
+ Icon(
+ Icons.edit_outlined,
+ color: textColor.withAlpha(164),
+ size: 14,
+ ),
+ Text(
+ ' - ${displayEvent.originServerTs.localizedTimeShort(context)}',
+ style: TextStyle(
+ color: textColor.withAlpha(164),
+ fontSize: 12,
+ ),
+ ),
+ ],
+ ],
+ ),
+ ),
+ ],
+ ),
),
),
),
),
- ),
- ],
+ ],
+ ),
),
- ),
- ],
+ ],
+ ),
);
Widget container;
if (event.hasAggregatedEvents(timeline, RelationshipTypes.reaction) ||
@@ -446,31 +447,27 @@ class Message extends StatelessWidget {
background: const Padding(
padding: EdgeInsets.symmetric(horizontal: 12.0),
child: Center(
- child: Icon(Icons.reply_outlined),
+ child: Icon(Icons.check_outlined),
),
),
direction: SwipeDirection.endToStart,
- onSwipe: onSwipe,
+ onSwipe: (_) => onSwipe(),
child: Center(
- child: MouseRegion(
- onEnter: (_) => useMouse = true,
- onExit: (_) => useMouse = false,
- child: InkWell(
- onTap: longPressSelect || useMouse ? () => onSelect!(event) : null,
- onLongPress: () => onSelect!(event),
- child: Container(
- color: selected
- ? Theme.of(context).primaryColor.withAlpha(100)
- : Theme.of(context).primaryColor.withAlpha(0),
- constraints: const BoxConstraints(
- maxWidth: FluffyThemes.columnWidth * 2.5,
- ),
- padding: const EdgeInsets.symmetric(
- horizontal: 8.0,
- vertical: 4.0,
- ),
- child: container,
+ child: InkWell(
+ onTap: longPressSelect ? () => onSelect!(event) : null,
+ onLongPress: () => onSelect!(event),
+ child: Container(
+ color: selected
+ ? Theme.of(context).primaryColor.withAlpha(100)
+ : Theme.of(context).primaryColor.withAlpha(0),
+ constraints: const BoxConstraints(
+ maxWidth: FluffyThemes.columnWidth * 2.5,
),
+ padding: const EdgeInsets.symmetric(
+ horizontal: 8.0,
+ vertical: 4.0,
+ ),
+ child: container,
),
),
),
diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart
index f79031663..6e3115ab5 100644
--- a/lib/pages/chat/events/message_content.dart
+++ b/lib/pages/chat/events/message_content.dart
@@ -40,7 +40,7 @@ class MessageContent extends StatelessWidget {
const MessageContent(
this.event, {
this.onInfoTab,
- Key? key,
+ super.key,
required this.textColor,
// #Pangea
required this.selected,
@@ -49,7 +49,7 @@ class MessageContent extends StatelessWidget {
required this.immersionMode,
required this.definitions,
// Pangea#
- }) : super(key: key);
+ });
void _verifyOrRequestKey(BuildContext context) async {
final l10n = L10n.of(context)!;
@@ -359,8 +359,7 @@ class _ButtonContent extends StatelessWidget {
required this.textColor,
required this.onPressed,
required this.fontSize,
- Key? key,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
diff --git a/lib/pages/chat/events/message_download_content.dart b/lib/pages/chat/events/message_download_content.dart
index 77e72183c..f19888a0b 100644
--- a/lib/pages/chat/events/message_download_content.dart
+++ b/lib/pages/chat/events/message_download_content.dart
@@ -6,8 +6,7 @@ class MessageDownloadContent extends StatelessWidget {
final Event event;
final Color textColor;
- const MessageDownloadContent(this.event, this.textColor, {Key? key})
- : super(key: key);
+ const MessageDownloadContent(this.event, this.textColor, {super.key});
@override
Widget build(BuildContext context) {
diff --git a/lib/pages/chat/events/message_reactions.dart b/lib/pages/chat/events/message_reactions.dart
index 71b30554e..ac9722ed5 100644
--- a/lib/pages/chat/events/message_reactions.dart
+++ b/lib/pages/chat/events/message_reactions.dart
@@ -1,10 +1,8 @@
import 'package:collection/collection.dart' show IterableExtension;
import 'package:fluffychat/config/app_config.dart';
-import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:fluffychat/widgets/mxc_image.dart';
-import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart';
@@ -13,8 +11,7 @@ class MessageReactions extends StatelessWidget {
final Event event;
final Timeline timeline;
- const MessageReactions(this.event, this.timeline, {Key? key})
- : super(key: key);
+ const MessageReactions(this.event, this.timeline, {super.key});
@override
Widget build(BuildContext context) {
@@ -48,36 +45,34 @@ class MessageReactions extends StatelessWidget {
spacing: 4.0,
runSpacing: 4.0,
children: [
- ...reactionList
- .map(
- (r) => _Reaction(
- reactionKey: r.key,
- count: r.count,
- reacted: r.reacted,
- onTap: () {
- if (r.reacted) {
- final evt = allReactionEvents.firstWhereOrNull(
- (e) =>
- e.senderId == e.room.client.userID &&
- e.content.tryGetMap('m.relates_to')?['key'] == r.key,
- );
- if (evt != null) {
- showFutureLoadingDialog(
- context: context,
- future: () => evt.redactEvent(),
- );
- }
- } else {
- event.room.sendReaction(event.eventId, r.key!);
- }
- },
- onLongPress: () async => await _AdaptableReactorsDialog(
- client: client,
- reactionEntry: r,
- ).show(context),
- ),
- )
- .toList(),
+ ...reactionList.map(
+ (r) => _Reaction(
+ reactionKey: r.key,
+ count: r.count,
+ reacted: r.reacted,
+ onTap: () {
+ if (r.reacted) {
+ final evt = allReactionEvents.firstWhereOrNull(
+ (e) =>
+ e.senderId == e.room.client.userID &&
+ e.content.tryGetMap('m.relates_to')?['key'] == r.key,
+ );
+ if (evt != null) {
+ showFutureLoadingDialog(
+ context: context,
+ future: () => evt.redactEvent(),
+ );
+ }
+ } else {
+ event.room.sendReaction(event.eventId, r.key!);
+ }
+ },
+ onLongPress: () async => await _AdaptableReactorsDialog(
+ client: client,
+ reactionEntry: r,
+ ).show(context),
+ ),
+ ),
if (allReactionEvents.any((e) => e.status.isSending))
const SizedBox(
width: 28,
@@ -188,24 +183,16 @@ class _AdaptableReactorsDialog extends StatelessWidget {
final _ReactionEntry? reactionEntry;
const _AdaptableReactorsDialog({
- Key? key,
this.client,
this.reactionEntry,
- }) : super(key: key);
+ });
- Future show(BuildContext context) => PlatformInfos.isCupertinoStyle
- ? showCupertinoDialog(
- context: context,
- builder: (context) => this,
- barrierDismissible: true,
- useRootNavigator: false,
- )
- : showDialog(
- context: context,
- builder: (context) => this,
- barrierDismissible: true,
- useRootNavigator: false,
- );
+ Future show(BuildContext context) => showAdaptiveDialog(
+ context: context,
+ builder: (context) => this,
+ barrierDismissible: true,
+ useRootNavigator: false,
+ );
@override
Widget build(BuildContext context) {
@@ -230,14 +217,9 @@ class _AdaptableReactorsDialog extends StatelessWidget {
final title = Center(child: Text(reactionEntry!.key!));
- return PlatformInfos.isCupertinoStyle
- ? CupertinoAlertDialog(
- title: title,
- content: body,
- )
- : AlertDialog(
- title: title,
- content: body,
- );
+ return AlertDialog.adaptive(
+ title: title,
+ content: body,
+ );
}
}
diff --git a/lib/pages/chat/events/reply_content.dart b/lib/pages/chat/events/reply_content.dart
index 6de2e5924..4c70ae004 100644
--- a/lib/pages/chat/events/reply_content.dart
+++ b/lib/pages/chat/events/reply_content.dart
@@ -14,9 +14,9 @@ class ReplyContent extends StatelessWidget {
const ReplyContent(
this.replyEvent, {
this.ownMessage = false,
- Key? key,
+ super.key,
this.timeline,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
@@ -36,7 +36,7 @@ class ReplyContent extends StatelessWidget {
maxLines: 1,
style: TextStyle(
color: ownMessage
- ? Theme.of(context).colorScheme.onPrimary
+ ? Theme.of(context).colorScheme.onPrimaryContainer
: Theme.of(context).colorScheme.onBackground,
fontSize: fontSize,
),
@@ -49,7 +49,7 @@ class ReplyContent extends StatelessWidget {
width: 3,
height: fontSize * 2 + 6,
color: ownMessage
- ? Theme.of(context).colorScheme.onPrimary
+ ? Theme.of(context).colorScheme.onPrimaryContainer
: Theme.of(context).colorScheme.onBackground,
),
const SizedBox(width: 6),
@@ -68,7 +68,7 @@ class ReplyContent extends StatelessWidget {
style: TextStyle(
fontWeight: FontWeight.bold,
color: ownMessage
- ? Theme.of(context).colorScheme.onPrimary
+ ? Theme.of(context).colorScheme.onPrimaryContainer
: Theme.of(context).colorScheme.onBackground,
fontSize: fontSize,
),
diff --git a/lib/pages/chat/events/state_message.dart b/lib/pages/chat/events/state_message.dart
index d60aa8148..b5e54eba3 100644
--- a/lib/pages/chat/events/state_message.dart
+++ b/lib/pages/chat/events/state_message.dart
@@ -8,7 +8,7 @@ import '../../../config/app_config.dart';
class StateMessage extends StatelessWidget {
final Event event;
- const StateMessage(this.event, {Key? key}) : super(key: key);
+ const StateMessage(this.event, {super.key});
@override
Widget build(BuildContext context) {
diff --git a/lib/pages/chat/events/sticker.dart b/lib/pages/chat/events/sticker.dart
index 041eae7ae..8e52bfe39 100644
--- a/lib/pages/chat/events/sticker.dart
+++ b/lib/pages/chat/events/sticker.dart
@@ -10,7 +10,7 @@ import 'image_bubble.dart';
class Sticker extends StatefulWidget {
final Event event;
- const Sticker(this.event, {Key? key}) : super(key: key);
+ const Sticker(this.event, {super.key});
@override
StickerState createState() => StickerState();
diff --git a/lib/pages/chat/events/verification_request_content.dart b/lib/pages/chat/events/verification_request_content.dart
index 13274678c..eacd3e259 100644
--- a/lib/pages/chat/events/verification_request_content.dart
+++ b/lib/pages/chat/events/verification_request_content.dart
@@ -11,8 +11,8 @@ class VerificationRequestContent extends StatelessWidget {
const VerificationRequestContent({
required this.event,
required this.timeline,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
diff --git a/lib/pages/chat/events/video_player.dart b/lib/pages/chat/events/video_player.dart
index b6a28591d..eee2516cf 100644
--- a/lib/pages/chat/events/video_player.dart
+++ b/lib/pages/chat/events/video_player.dart
@@ -17,7 +17,7 @@ import '../../../utils/error_reporter.dart';
class EventVideoPlayer extends StatefulWidget {
final Event event;
- const EventVideoPlayer(this.event, {Key? key}) : super(key: key);
+ const EventVideoPlayer(this.event, {super.key});
@override
EventVideoPlayerState createState() => EventVideoPlayerState();
diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart
index 83ab417b8..5c45734da 100644
--- a/lib/pages/chat/input_bar.dart
+++ b/lib/pages/chat/input_bar.dart
@@ -43,8 +43,8 @@ class InputBar extends StatelessWidget {
this.autofocus,
this.textInputAction,
this.readOnly = false,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
List