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> getSuggestions(String text) { // #Pangea diff --git a/lib/pages/chat/pinned_events.dart b/lib/pages/chat/pinned_events.dart index eec216b59..e5fd60b75 100644 --- a/lib/pages/chat/pinned_events.dart +++ b/lib/pages/chat/pinned_events.dart @@ -13,7 +13,7 @@ import 'package:matrix/matrix.dart'; class PinnedEvents extends StatelessWidget { final ChatController controller; - const PinnedEvents(this.controller, {Key? key}) : super(key: key); + const PinnedEvents(this.controller, {super.key}); Future _displayPinnedEventsDialog( BuildContext context, diff --git a/lib/pages/chat/reactions_picker.dart b/lib/pages/chat/reactions_picker.dart index c94a22357..ad8b63ef8 100644 --- a/lib/pages/chat/reactions_picker.dart +++ b/lib/pages/chat/reactions_picker.dart @@ -10,7 +10,7 @@ import '../../config/themes.dart'; class ReactionsPicker extends StatelessWidget { final ChatController controller; - const ReactionsPicker(this.controller, {Key? key}) : super(key: key); + const ReactionsPicker(this.controller, {super.key}); @override Widget build(BuildContext context) { diff --git a/lib/pages/chat/recording_dialog.dart b/lib/pages/chat/recording_dialog.dart index e84ab8202..267f82c62 100644 --- a/lib/pages/chat/recording_dialog.dart +++ b/lib/pages/chat/recording_dialog.dart @@ -14,8 +14,8 @@ import 'events/audio_player.dart'; class RecordingDialog extends StatefulWidget { static const String recordingFileType = 'm4a'; const RecordingDialog({ - Key? key, - }) : super(key: key); + super.key, + }); @override RecordingDialogState createState() => RecordingDialogState(); diff --git a/lib/pages/chat/reply_display.dart b/lib/pages/chat/reply_display.dart index 77edee1dc..32bec7c25 100644 --- a/lib/pages/chat/reply_display.dart +++ b/lib/pages/chat/reply_display.dart @@ -10,7 +10,7 @@ import 'events/reply_content.dart'; class ReplyDisplay extends StatelessWidget { final ChatController controller; - const ReplyDisplay(this.controller, {Key? key}) : super(key: key); + const ReplyDisplay(this.controller, {super.key}); @override Widget build(BuildContext context) { diff --git a/lib/pages/chat/seen_by_row.dart b/lib/pages/chat/seen_by_row.dart index 47bc4f9ce..faac0db4d 100644 --- a/lib/pages/chat/seen_by_row.dart +++ b/lib/pages/chat/seen_by_row.dart @@ -8,7 +8,7 @@ import 'package:fluffychat/widgets/matrix.dart'; class SeenByRow extends StatelessWidget { final ChatController controller; - const SeenByRow(this.controller, {Key? key}) : super(key: key); + const SeenByRow(this.controller, {super.key}); @override Widget build(BuildContext context) { @@ -38,14 +38,13 @@ class SeenByRow extends StatelessWidget { ? seenByUsers.sublist(0, maxAvatars) : seenByUsers) .map( - (user) => Avatar( - mxContent: user.avatarUrl, - name: user.calcDisplayname(), - size: 16, - fontSize: 9, - ), - ) - .toList(), + (user) => Avatar( + mxContent: user.avatarUrl, + name: user.calcDisplayname(), + size: 16, + fontSize: 9, + ), + ), if (seenByUsers.length > maxAvatars) SizedBox( width: 16, diff --git a/lib/pages/chat/send_file_dialog.dart b/lib/pages/chat/send_file_dialog.dart index e019cef1c..958f430c7 100644 --- a/lib/pages/chat/send_file_dialog.dart +++ b/lib/pages/chat/send_file_dialog.dart @@ -1,5 +1,7 @@ +import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/size_string.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; @@ -14,8 +16,8 @@ class SendFileDialog extends StatefulWidget { const SendFileDialog({ required this.room, required this.files, - Key? key, - }) : super(key: key); + super.key, + }); @override SendFileDialogState createState() => SendFileDialogState(); @@ -83,20 +85,41 @@ class SendFileDialogState extends State { mainAxisSize: MainAxisSize.min, children: [ Flexible( - child: Image.memory( - widget.files.first.bytes, - fit: BoxFit.contain, + child: Material( + borderRadius: BorderRadius.circular(AppConfig.borderRadius), + elevation: + Theme.of(context).appBarTheme.scrolledUnderElevation ?? 4, + shadowColor: Theme.of(context).appBarTheme.shadowColor, + clipBehavior: Clip.hardEdge, + child: Image.memory( + widget.files.first.bytes, + fit: BoxFit.contain, + height: 256, + ), ), ), + const SizedBox(height: 16), + // Workaround for SwitchListTile.adaptive crashes in CupertinoDialog Row( - children: [ - Checkbox( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CupertinoSwitch( value: origImage, - onChanged: (v) => setState(() => origImage = v ?? false), + onChanged: (v) => setState(() => origImage = v), ), - InkWell( - onTap: () => setState(() => origImage = !origImage), - child: Text('${L10n.of(context)!.sendOriginal} ($sizeString)'), + const SizedBox(width: 16), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + L10n.of(context)!.sendOriginal, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + Text(sizeString), + ], + ), ), ], ), @@ -105,7 +128,7 @@ class SendFileDialogState extends State { } else { contentWidget = Text('$fileName ($sizeString)'); } - return AlertDialog( + return AlertDialog.adaptive( title: Text(sendStr), content: contentWidget, actions: [ diff --git a/lib/pages/chat/send_location_dialog.dart b/lib/pages/chat/send_location_dialog.dart index d29626f56..b2a99004e 100644 --- a/lib/pages/chat/send_location_dialog.dart +++ b/lib/pages/chat/send_location_dialog.dart @@ -9,15 +9,14 @@ import 'package:geolocator/geolocator.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/pages/chat/events/map_bubble.dart'; -import 'package:fluffychat/utils/platform_infos.dart'; class SendLocationDialog extends StatefulWidget { final Room room; const SendLocationDialog({ required this.room, - Key? key, - }) : super(key: key); + super.key, + }); @override SendLocationDialogState createState() => SendLocationDialogState(); @@ -111,23 +110,7 @@ class SendLocationDialogState extends State { ], ); } - if (PlatformInfos.isCupertinoStyle) { - return CupertinoAlertDialog( - title: Text(L10n.of(context)!.shareLocation), - content: contentWidget, - actions: [ - CupertinoDialogAction( - onPressed: Navigator.of(context, rootNavigator: false).pop, - child: Text(L10n.of(context)!.cancel), - ), - CupertinoDialogAction( - onPressed: isSending ? null : sendAction, - child: Text(L10n.of(context)!.send), - ), - ], - ); - } - return AlertDialog( + return AlertDialog.adaptive( title: Text(L10n.of(context)!.shareLocation), content: contentWidget, actions: [ diff --git a/lib/pages/chat/sticker_picker_dialog.dart b/lib/pages/chat/sticker_picker_dialog.dart index 80cae6b54..16065c916 100644 --- a/lib/pages/chat/sticker_picker_dialog.dart +++ b/lib/pages/chat/sticker_picker_dialog.dart @@ -9,7 +9,7 @@ import 'events/image_bubble.dart'; class StickerPickerDialog extends StatefulWidget { final Room room; - const StickerPickerDialog({required this.room, Key? key}) : super(key: key); + const StickerPickerDialog({required this.room, super.key}); @override StickerPickerDialogState createState() => StickerPickerDialogState(); diff --git a/lib/pages/chat/tombstone_display.dart b/lib/pages/chat/tombstone_display.dart index 50609cfd4..e080a0009 100644 --- a/lib/pages/chat/tombstone_display.dart +++ b/lib/pages/chat/tombstone_display.dart @@ -7,7 +7,7 @@ import 'chat.dart'; class TombstoneDisplay extends StatelessWidget { final ChatController controller; - const TombstoneDisplay(this.controller, {Key? key}) : super(key: key); + const TombstoneDisplay(this.controller, {super.key}); @override Widget build(BuildContext context) { diff --git a/lib/pages/chat/typing_indicators.dart b/lib/pages/chat/typing_indicators.dart index 3bb3f907e..8101fd76f 100644 --- a/lib/pages/chat/typing_indicators.dart +++ b/lib/pages/chat/typing_indicators.dart @@ -8,7 +8,7 @@ import 'package:fluffychat/widgets/matrix.dart'; class TypingIndicators extends StatelessWidget { final ChatController controller; - const TypingIndicators(this.controller, {Key? key}) : super(key: key); + const TypingIndicators(this.controller, {super.key}); @override Widget build(BuildContext context) { diff --git a/lib/pages/chat_details/chat_details.dart b/lib/pages/chat_details/chat_details.dart index aa658d992..cbdca647a 100644 --- a/lib/pages/chat_details/chat_details.dart +++ b/lib/pages/chat_details/chat_details.dart @@ -25,9 +25,9 @@ class ChatDetails extends StatefulWidget { final String roomId; const ChatDetails({ - Key? key, + super.key, required this.roomId, - }) : super(key: key); + }); @override ChatDetailsController createState() => ChatDetailsController(); @@ -121,8 +121,7 @@ class ChatDetailsController extends State { if (adminMode) AlertDialogAction(label: L10n.of(context)!.create, key: 'new'), ...aliases.result! - .map((alias) => AlertDialogAction(key: alias, label: alias)) - .toList(), + .map((alias) => AlertDialogAction(key: alias, label: alias)), ], ); if (select == null) return; diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index 7a8beaf2f..dc4823aaa 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -27,7 +27,7 @@ import 'package:matrix/matrix.dart'; class ChatDetailsView extends StatelessWidget { final ChatDetailsController controller; - const ChatDetailsView(this.controller, {Key? key}) : super(key: key); + const ChatDetailsView(this.controller, {super.key}); @override Widget build(BuildContext context) { diff --git a/lib/pages/chat_encryption_settings/chat_encryption_settings.dart b/lib/pages/chat_encryption_settings/chat_encryption_settings.dart index a23376c75..c0c4a4cb4 100644 --- a/lib/pages/chat_encryption_settings/chat_encryption_settings.dart +++ b/lib/pages/chat_encryption_settings/chat_encryption_settings.dart @@ -11,7 +11,7 @@ import 'package:matrix/matrix.dart'; import '../key_verification/key_verification_dialog.dart'; class ChatEncryptionSettings extends StatefulWidget { - const ChatEncryptionSettings({Key? key}) : super(key: key); + const ChatEncryptionSettings({super.key}); @override ChatEncryptionSettingsController createState() => diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 50ddf987a..b3507f0a4 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -12,7 +12,6 @@ import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/utils/chat_list_handle_space_tap.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/firebase_analytics.dart'; -import 'package:fluffychat/utils/famedlysdk_store.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; @@ -241,7 +240,7 @@ class ChatListController extends State ], ); if (newServer == null) return; - Store().setItem(_serverStoreNamespace, newServer.single); + Matrix.of(context).store.setString(_serverStoreNamespace, newServer.single); setState(() { searchServer = newServer.single; }); @@ -439,7 +438,8 @@ class ChatListController extends State CallKeepManager().initialize(); WidgetsBinding.instance.addPostFrameCallback((_) async { if (mounted) { - searchServer = await Store().getItem(_serverStoreNamespace); + searchServer = + Matrix.of(context).store.getString(_serverStoreNamespace); Matrix.of(context).backgroundPush?.setupPush(); } diff --git a/lib/pages/chat_list/search_title.dart b/lib/pages/chat_list/search_title.dart index 3ed0e64d4..62bcfb684 100644 --- a/lib/pages/chat_list/search_title.dart +++ b/lib/pages/chat_list/search_title.dart @@ -13,8 +13,8 @@ class SearchTitle extends StatelessWidget { this.trailing, this.onTap, this.color, - Key? key, - }) : super(key: key); + super.key, + }); @override Widget build(BuildContext context) => Material( diff --git a/lib/pages/chat_permissions_settings/chat_permissions_settings.dart b/lib/pages/chat_permissions_settings/chat_permissions_settings.dart index c5ba36493..1186efe53 100644 --- a/lib/pages/chat_permissions_settings/chat_permissions_settings.dart +++ b/lib/pages/chat_permissions_settings/chat_permissions_settings.dart @@ -11,7 +11,7 @@ import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; class ChatPermissionsSettings extends StatefulWidget { - const ChatPermissionsSettings({Key? key}) : super(key: key); + const ChatPermissionsSettings({super.key}); @override ChatPermissionsSettingsController createState() => @@ -97,6 +97,7 @@ class ChatPermissionsSettingsController extends State { okLabel: L10n.of(context)!.yes, cancelLabel: L10n.of(context)!.cancel, title: L10n.of(context)!.areYouSure, + message: L10n.of(context)!.roomUpgradeDescription, )) { return; } diff --git a/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart index 700152d93..c3ef20f6d 100644 --- a/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart +++ b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart @@ -9,8 +9,7 @@ import 'package:matrix/matrix.dart'; class ChatPermissionsSettingsView extends StatelessWidget { final ChatPermissionsSettingsController controller; - const ChatPermissionsSettingsView(this.controller, {Key? key}) - : super(key: key); + const ChatPermissionsSettingsView(this.controller, {super.key}); @override Widget build(BuildContext context) { diff --git a/lib/pages/chat_permissions_settings/permission_list_tile.dart b/lib/pages/chat_permissions_settings/permission_list_tile.dart index 3b577ac36..cf8d795ab 100644 --- a/lib/pages/chat_permissions_settings/permission_list_tile.dart +++ b/lib/pages/chat_permissions_settings/permission_list_tile.dart @@ -9,12 +9,12 @@ class PermissionsListTile extends StatelessWidget { final void Function()? onTap; const PermissionsListTile({ - Key? key, + super.key, required this.permissionKey, required this.permission, this.category, this.onTap, - }) : super(key: key); + }); String getLocalizedPowerLevelString(BuildContext context) { if (category == null) { diff --git a/lib/pages/device_settings/device_settings.dart b/lib/pages/device_settings/device_settings.dart index 0153fc35c..c0b8526d8 100644 --- a/lib/pages/device_settings/device_settings.dart +++ b/lib/pages/device_settings/device_settings.dart @@ -12,7 +12,7 @@ import 'package:matrix/matrix.dart'; import '../../widgets/matrix.dart'; class DevicesSettings extends StatefulWidget { - const DevicesSettings({Key? key}) : super(key: key); + const DevicesSettings({super.key}); @override DevicesSettingsController createState() => DevicesSettingsController(); @@ -40,6 +40,7 @@ class DevicesSettingsController extends State { title: L10n.of(context)!.areYouSure, okLabel: L10n.of(context)!.yes, cancelLabel: L10n.of(context)!.cancel, + message: L10n.of(context)!.removeDevicesDescription, ) == OkCancelResult.cancel) return; final matrix = Matrix.of(context); diff --git a/lib/pages/device_settings/device_settings_view.dart b/lib/pages/device_settings/device_settings_view.dart index b87217b4f..d644089dd 100644 --- a/lib/pages/device_settings/device_settings_view.dart +++ b/lib/pages/device_settings/device_settings_view.dart @@ -8,7 +8,7 @@ import 'user_device_list_item.dart'; class DevicesSettingsView extends StatelessWidget { final DevicesSettingsController controller; - const DevicesSettingsView(this.controller, {Key? key}) : super(key: key); + const DevicesSettingsView(this.controller, {super.key}); @override Widget build(BuildContext context) { diff --git a/lib/pages/device_settings/user_device_list_item.dart b/lib/pages/device_settings/user_device_list_item.dart index 887b3b445..db793ac35 100644 --- a/lib/pages/device_settings/user_device_list_item.dart +++ b/lib/pages/device_settings/user_device_list_item.dart @@ -31,8 +31,8 @@ class UserDeviceListItem extends StatelessWidget { required this.verify, required this.block, required this.unblock, - Key? key, - }) : super(key: key); + super.key, + }); @override Widget build(BuildContext context) { diff --git a/lib/pages/dialer/pip/pip_view.dart b/lib/pages/dialer/pip/pip_view.dart index 74008aaf1..5396c9136 100644 --- a/lib/pages/dialer/pip/pip_view.dart +++ b/lib/pages/dialer/pip/pip_view.dart @@ -15,13 +15,13 @@ class PIPView extends StatefulWidget { ) builder; const PIPView({ - Key? key, + super.key, required this.builder, this.initialCorner = PIPViewCorner.topRight, this.floatingWidth, this.floatingHeight, this.avoidKeyboard = true, - }) : super(key: key); + }); @override PIPViewState createState() => PIPViewState(); diff --git a/lib/pages/homeserver_picker/homeserver_app_bar.dart b/lib/pages/homeserver_picker/homeserver_app_bar.dart index 88797ca68..ca3d874b1 100644 --- a/lib/pages/homeserver_picker/homeserver_app_bar.dart +++ b/lib/pages/homeserver_picker/homeserver_app_bar.dart @@ -49,6 +49,7 @@ class HomeserverAppBar extends StatelessWidget { controller.checkHomeserverAction(); }, textFieldConfiguration: TextFieldConfiguration( + enabled: !controller.isLoggingIn, controller: controller.homeserverController, decoration: InputDecoration( prefixIcon: Navigator.of(context).canPop() diff --git a/lib/pages/homeserver_picker/homeserver_bottom_sheet.dart b/lib/pages/homeserver_picker/homeserver_bottom_sheet.dart index d7cd534bf..0fc6d0112 100644 --- a/lib/pages/homeserver_picker/homeserver_bottom_sheet.dart +++ b/lib/pages/homeserver_picker/homeserver_bottom_sheet.dart @@ -5,8 +5,7 @@ import 'package:url_launcher/url_launcher_string.dart'; class HomeserverBottomSheet extends StatelessWidget { final HomeserverBenchmarkResult homeserver; - const HomeserverBottomSheet({required this.homeserver, Key? key}) - : super(key: key); + const HomeserverBottomSheet({required this.homeserver, super.key}); @override Widget build(BuildContext context) { diff --git a/lib/pages/homeserver_picker/homeserver_picker.dart b/lib/pages/homeserver_picker/homeserver_picker.dart index 5a2ac62d8..9eb945f17 100644 --- a/lib/pages/homeserver_picker/homeserver_picker.dart +++ b/lib/pages/homeserver_picker/homeserver_picker.dart @@ -33,6 +33,8 @@ class HomeserverPicker extends StatefulWidget { class HomeserverPickerController extends State { bool isLoading = false; + bool isLoggingIn = false; + final TextEditingController homeserverController = TextEditingController( text: AppConfig.defaultHomeserver, ); @@ -133,7 +135,7 @@ class HomeserverPickerController extends State { ? Uri.parse(redirectUrl).scheme : "http://localhost:3001"; final result = await FlutterWebAuth2.authenticate( - url: url, + url: url.toString(), callbackUrlScheme: urlScheme, ); final token = Uri.parse(result).queryParameters['loginToken']; @@ -160,9 +162,12 @@ class HomeserverPickerController extends State { List? get identityProviders { final loginTypes = _rawLoginTypes; if (loginTypes == null) return null; - final rawProviders = loginTypes.tryGetList('flows')!.singleWhere( - (flow) => flow['type'] == AuthenticationTypes.sso, - )['identity_providers']; + final List? rawProviders = loginTypes.tryGetList('flows')!.singleWhere( + (flow) => flow['type'] == AuthenticationTypes.sso, + )['identity_providers'] ?? + [ + {'id': null}, + ]; final list = (rawProviders as List) .map((json) => IdentityProvider.fromJson(json)) .toList(); diff --git a/lib/pages/image_viewer/image_viewer.dart b/lib/pages/image_viewer/image_viewer.dart index 73a52c4c7..f7d3ea544 100644 --- a/lib/pages/image_viewer/image_viewer.dart +++ b/lib/pages/image_viewer/image_viewer.dart @@ -10,7 +10,7 @@ import '../../utils/matrix_sdk_extensions/event_extension.dart'; class ImageViewer extends StatefulWidget { final Event event; - const ImageViewer(this.event, {Key? key}) : super(key: key); + const ImageViewer(this.event, {super.key}); @override ImageViewerController createState() => ImageViewerController(); diff --git a/lib/pages/image_viewer/image_viewer_view.dart b/lib/pages/image_viewer/image_viewer_view.dart index 9d9c74295..c53c2b57a 100644 --- a/lib/pages/image_viewer/image_viewer_view.dart +++ b/lib/pages/image_viewer/image_viewer_view.dart @@ -8,7 +8,7 @@ import 'image_viewer.dart'; class ImageViewerView extends StatelessWidget { final ImageViewerController controller; - const ImageViewerView(this.controller, {Key? key}) : super(key: key); + const ImageViewerView(this.controller, {super.key}); @override Widget build(BuildContext context) { diff --git a/lib/pages/key_verification/key_verification_dialog.dart b/lib/pages/key_verification/key_verification_dialog.dart index 4bb8c1572..d86ff3e00 100644 --- a/lib/pages/key_verification/key_verification_dialog.dart +++ b/lib/pages/key_verification/key_verification_dialog.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'dart:ui'; import 'package:adaptive_dialog/adaptive_dialog.dart'; -import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -12,18 +11,18 @@ import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; class KeyVerificationDialog extends StatefulWidget { - Future show(BuildContext context) => showAdaptiveBottomSheet( + Future show(BuildContext context) => showAdaptiveDialog( context: context, builder: (context) => this, - isDismissible: false, + barrierDismissible: false, ); final KeyVerification request; const KeyVerificationDialog({ - Key? key, + super.key, required this.request, - }) : super(key: key); + }); @override KeyVerificationPageState createState() => KeyVerificationPageState(); @@ -340,24 +339,17 @@ class KeyVerificationPageState extends State { ); break; } - return Scaffold( - appBar: AppBar( - leading: const Center(child: CloseButton()), - title: title, - ), - body: ListView( - padding: const EdgeInsets.all(12.0), - children: [body], - ), - bottomNavigationBar: SafeArea( - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: buttons, - ), + + return AlertDialog.adaptive( + title: title, + content: SizedBox( + height: 256, + width: 256, + child: ListView( + children: [body], ), ), + actions: buttons, ); } } diff --git a/lib/pages/new_private_chat/new_private_chat.dart b/lib/pages/new_private_chat/new_private_chat.dart index 5b5fca28a..5d68c815f 100644 --- a/lib/pages/new_private_chat/new_private_chat.dart +++ b/lib/pages/new_private_chat/new_private_chat.dart @@ -12,7 +12,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; class NewPrivateChat extends StatefulWidget { - const NewPrivateChat({Key? key}) : super(key: key); + const NewPrivateChat({super.key}); @override NewPrivateChatController createState() => NewPrivateChatController(); @@ -73,7 +73,9 @@ class NewPrivateChatController extends State { } await showAdaptiveBottomSheet( context: context, - builder: (_) => const QrScannerModal(), + builder: (_) => QrScannerModal( + onScan: (link) => UrlLauncher(context, link).openMatrixToUrl(), + ), ); } diff --git a/lib/pages/new_private_chat/new_private_chat_view.dart b/lib/pages/new_private_chat/new_private_chat_view.dart index 529987c48..da3a4e2a9 100644 --- a/lib/pages/new_private_chat/new_private_chat_view.dart +++ b/lib/pages/new_private_chat/new_private_chat_view.dart @@ -10,7 +10,7 @@ import 'package:go_router/go_router.dart'; class NewPrivateChatView extends StatelessWidget { final NewPrivateChatController controller; - const NewPrivateChatView(this.controller, {Key? key}) : super(key: key); + const NewPrivateChatView(this.controller, {super.key}); static const double _qrCodePadding = 8; diff --git a/lib/pages/new_private_chat/qr_scanner_modal.dart b/lib/pages/new_private_chat/qr_scanner_modal.dart index 8c125e460..b509941e5 100644 --- a/lib/pages/new_private_chat/qr_scanner_modal.dart +++ b/lib/pages/new_private_chat/qr_scanner_modal.dart @@ -6,10 +6,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:qr_code_scanner/qr_code_scanner.dart'; -import 'package:fluffychat/utils/url_launcher.dart'; - class QrScannerModal extends StatefulWidget { - const QrScannerModal({Key? key}) : super(key: key); + final void Function(String) onScan; + const QrScannerModal({required this.onScan, super.key}); @override QrScannerModalState createState() => QrScannerModalState(); @@ -69,7 +68,8 @@ class QrScannerModalState extends State { sub = controller.scannedDataStream.listen((scanData) { sub.cancel(); Navigator.of(context).pop(); - UrlLauncher(context, scanData.code).openMatrixToUrl(); + final data = scanData.code; + if (data != null) widget.onScan(data); }); } diff --git a/lib/pages/settings_3pid/settings_3pid.dart b/lib/pages/settings_3pid/settings_3pid.dart index ff3256934..d46cac2a9 100644 --- a/lib/pages/settings_3pid/settings_3pid.dart +++ b/lib/pages/settings_3pid/settings_3pid.dart @@ -11,7 +11,7 @@ import 'settings_3pid_view.dart'; class Settings3Pid extends StatefulWidget { static int sendAttempt = 0; - const Settings3Pid({Key? key}) : super(key: key); + const Settings3Pid({super.key}); @override Settings3PidController createState() => Settings3PidController(); diff --git a/lib/pages/settings_3pid/settings_3pid_view.dart b/lib/pages/settings_3pid/settings_3pid_view.dart index 2a88f7711..d868a623a 100644 --- a/lib/pages/settings_3pid/settings_3pid_view.dart +++ b/lib/pages/settings_3pid/settings_3pid_view.dart @@ -7,7 +7,7 @@ import 'package:matrix/matrix.dart'; class Settings3PidView extends StatelessWidget { final Settings3PidController controller; - const Settings3PidView(this.controller, {Key? key}) : super(key: key); + const Settings3PidView(this.controller, {super.key}); @override Widget build(BuildContext context) { diff --git a/lib/pages/settings_chat/settings_chat.dart b/lib/pages/settings_chat/settings_chat.dart index b25941669..1c1035559 100644 --- a/lib/pages/settings_chat/settings_chat.dart +++ b/lib/pages/settings_chat/settings_chat.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'settings_chat_view.dart'; class SettingsChat extends StatefulWidget { - const SettingsChat({Key? key}) : super(key: key); + const SettingsChat({super.key}); @override SettingsChatController createState() => SettingsChatController(); diff --git a/lib/pages/settings_chat/settings_chat_view.dart b/lib/pages/settings_chat/settings_chat_view.dart index ca3477980..4034ea49c 100644 --- a/lib/pages/settings_chat/settings_chat_view.dart +++ b/lib/pages/settings_chat/settings_chat_view.dart @@ -11,7 +11,7 @@ import 'settings_chat.dart'; class SettingsChatView extends StatelessWidget { final SettingsChatController controller; - const SettingsChatView(this.controller, {Key? key}) : super(key: key); + const SettingsChatView(this.controller, {super.key}); @override Widget build(BuildContext context) { @@ -100,14 +100,6 @@ class SettingsChatView extends StatelessWidget { ), ), const Divider(height: 1), - // #Pangea - SettingsSwitchListTile.adaptive( - title: L10n.of(context)!.showDirectChatsInSpaces, - onChanged: (b) => AppConfig.showDirectChatsInSpaces = b, - storeKey: SettingKeys.showDirectChatsInSpaces, - defaultValue: AppConfig.showDirectChatsInSpaces, - ), - // Pangea# SettingsSwitchListTile.adaptive( title: L10n.of(context)!.separateChatTypes, onChanged: (b) => AppConfig.separateChatTypes = b, diff --git a/lib/pages/settings_emotes/import_archive_dialog.dart b/lib/pages/settings_emotes/import_archive_dialog.dart index faf069120..fd25fc9c6 100644 --- a/lib/pages/settings_emotes/import_archive_dialog.dart +++ b/lib/pages/settings_emotes/import_archive_dialog.dart @@ -204,11 +204,11 @@ class _EmojiImportPreview extends StatefulWidget { final VoidCallback onRemove; const _EmojiImportPreview({ - Key? key, + super.key, required this.entry, required this.onNameChanged, required this.onRemove, - }) : super(key: key); + }); @override State<_EmojiImportPreview> createState() => _EmojiImportPreviewState(); diff --git a/lib/pages/settings_ignore_list/settings_ignore_list.dart b/lib/pages/settings_ignore_list/settings_ignore_list.dart index cf0713aa9..0b27cee3b 100644 --- a/lib/pages/settings_ignore_list/settings_ignore_list.dart +++ b/lib/pages/settings_ignore_list/settings_ignore_list.dart @@ -8,7 +8,7 @@ import 'settings_ignore_list_view.dart'; class SettingsIgnoreList extends StatefulWidget { final String? initialUserId; - const SettingsIgnoreList({Key? key, this.initialUserId}) : super(key: key); + const SettingsIgnoreList({super.key, this.initialUserId}); @override SettingsIgnoreListController createState() => SettingsIgnoreListController(); diff --git a/lib/pages/settings_ignore_list/settings_ignore_list_view.dart b/lib/pages/settings_ignore_list/settings_ignore_list_view.dart index 092db16ab..e1620ecb3 100644 --- a/lib/pages/settings_ignore_list/settings_ignore_list_view.dart +++ b/lib/pages/settings_ignore_list/settings_ignore_list_view.dart @@ -10,7 +10,7 @@ import 'settings_ignore_list.dart'; class SettingsIgnoreListView extends StatelessWidget { final SettingsIgnoreListController controller; - const SettingsIgnoreListView(this.controller, {Key? key}) : super(key: key); + const SettingsIgnoreListView(this.controller, {super.key}); @override Widget build(BuildContext context) { diff --git a/lib/pages/settings_multiple_emotes/settings_multiple_emotes.dart b/lib/pages/settings_multiple_emotes/settings_multiple_emotes.dart index 34306c41a..4d2e983ed 100644 --- a/lib/pages/settings_multiple_emotes/settings_multiple_emotes.dart +++ b/lib/pages/settings_multiple_emotes/settings_multiple_emotes.dart @@ -4,7 +4,7 @@ import 'package:go_router/go_router.dart'; import 'settings_multiple_emotes_view.dart'; class MultipleEmotesSettings extends StatefulWidget { - const MultipleEmotesSettings({Key? key}) : super(key: key); + const MultipleEmotesSettings({super.key}); @override MultipleEmotesSettingsController createState() => diff --git a/lib/pages/settings_multiple_emotes/settings_multiple_emotes_view.dart b/lib/pages/settings_multiple_emotes/settings_multiple_emotes_view.dart index 80f81fea4..e6eedeffe 100644 --- a/lib/pages/settings_multiple_emotes/settings_multiple_emotes_view.dart +++ b/lib/pages/settings_multiple_emotes/settings_multiple_emotes_view.dart @@ -8,8 +8,7 @@ import 'package:matrix/matrix.dart'; class MultipleEmotesSettingsView extends StatelessWidget { final MultipleEmotesSettingsController controller; - const MultipleEmotesSettingsView(this.controller, {Key? key}) - : super(key: key); + const MultipleEmotesSettingsView(this.controller, {super.key}); @override Widget build(BuildContext context) { diff --git a/lib/pages/settings_notifications/settings_notifications.dart b/lib/pages/settings_notifications/settings_notifications.dart index 453634344..5721386db 100644 --- a/lib/pages/settings_notifications/settings_notifications.dart +++ b/lib/pages/settings_notifications/settings_notifications.dart @@ -53,7 +53,7 @@ class NotificationSettingsItem { } class SettingsNotifications extends StatefulWidget { - const SettingsNotifications({Key? key}) : super(key: key); + const SettingsNotifications({super.key}); @override SettingsNotificationsController createState() => diff --git a/lib/pages/settings_notifications/settings_notifications_view.dart b/lib/pages/settings_notifications/settings_notifications_view.dart index 91fb9a318..37846d8bb 100644 --- a/lib/pages/settings_notifications/settings_notifications_view.dart +++ b/lib/pages/settings_notifications/settings_notifications_view.dart @@ -11,8 +11,7 @@ import 'settings_notifications.dart'; class SettingsNotificationsView extends StatelessWidget { final SettingsNotificationsController controller; - const SettingsNotificationsView(this.controller, {Key? key}) - : super(key: key); + const SettingsNotificationsView(this.controller, {super.key}); @override Widget build(BuildContext context) { diff --git a/lib/pages/settings_security/settings_security.dart b/lib/pages/settings_security/settings_security.dart index f8b2c9c13..fe927fc5b 100644 --- a/lib/pages/settings_security/settings_security.dart +++ b/lib/pages/settings_security/settings_security.dart @@ -15,7 +15,7 @@ import '../bootstrap/bootstrap_dialog.dart'; import 'settings_security_view.dart'; class SettingsSecurity extends StatefulWidget { - const SettingsSecurity({Key? key}) : super(key: key); + const SettingsSecurity({super.key}); @override SettingsSecurityController createState() => SettingsSecurityController(); diff --git a/lib/pages/settings_security/settings_security_view.dart b/lib/pages/settings_security/settings_security_view.dart index da8b683f1..9119fe277 100644 --- a/lib/pages/settings_security/settings_security_view.dart +++ b/lib/pages/settings_security/settings_security_view.dart @@ -10,7 +10,7 @@ import 'settings_security.dart'; class SettingsSecurityView extends StatelessWidget { final SettingsSecurityController controller; - const SettingsSecurityView(this.controller, {Key? key}) : super(key: key); + const SettingsSecurityView(this.controller, {super.key}); @override Widget build(BuildContext context) { diff --git a/lib/pages/settings_stories/settings_stories.dart b/lib/pages/settings_stories/settings_stories.dart index 802c07d0c..16e9208bb 100644 --- a/lib/pages/settings_stories/settings_stories.dart +++ b/lib/pages/settings_stories/settings_stories.dart @@ -8,7 +8,7 @@ import 'package:fluffychat/widgets/matrix.dart'; import '../../utils/matrix_sdk_extensions/client_stories_extension.dart'; class SettingsStories extends StatefulWidget { - const SettingsStories({Key? key}) : super(key: key); + const SettingsStories({super.key}); @override SettingsStoriesController createState() => SettingsStoriesController(); diff --git a/lib/pages/settings_stories/settings_stories_view.dart b/lib/pages/settings_stories/settings_stories_view.dart index 31524eef4..f5d71fab3 100644 --- a/lib/pages/settings_stories/settings_stories_view.dart +++ b/lib/pages/settings_stories/settings_stories_view.dart @@ -8,7 +8,7 @@ import 'package:fluffychat/widgets/avatar.dart'; class SettingsStoriesView extends StatelessWidget { final SettingsStoriesController controller; - const SettingsStoriesView(this.controller, {Key? key}) : super(key: key); + const SettingsStoriesView(this.controller, {super.key}); @override Widget build(BuildContext context) { diff --git a/lib/pages/settings_style/settings_style.dart b/lib/pages/settings_style/settings_style.dart index 619dc2c6d..67c6d9738 100644 --- a/lib/pages/settings_style/settings_style.dart +++ b/lib/pages/settings_style/settings_style.dart @@ -11,7 +11,7 @@ import '../../widgets/matrix.dart'; import 'settings_style_view.dart'; class SettingsStyle extends StatefulWidget { - const SettingsStyle({Key? key}) : super(key: key); + const SettingsStyle({super.key}); @override SettingsStyleController createState() => SettingsStyleController(); @@ -30,13 +30,13 @@ class SettingsStyleController extends State { if (pickedFile == null) return; await Matrix.of(context) .store - .setItem(SettingKeys.wallpaper, pickedFile.path); + .setString(SettingKeys.wallpaper, pickedFile.path!); setState(() {}); } void deleteWallpaperAction() async { Matrix.of(context).wallpaper = null; - await Matrix.of(context).store.deleteItem(SettingKeys.wallpaper); + await Matrix.of(context).store.remove(SettingKeys.wallpaper); setState(() {}); } @@ -76,7 +76,7 @@ class SettingsStyleController extends State { void changeFontSizeFactor(double d) { setState(() => AppConfig.fontSizeFactor = d); - Matrix.of(context).store.setItem( + Matrix.of(context).store.setString( SettingKeys.fontSizeFactor, AppConfig.fontSizeFactor.toString(), ); diff --git a/lib/pages/settings_style/settings_style_view.dart b/lib/pages/settings_style/settings_style_view.dart index c4864d8fb..7e08df92a 100644 --- a/lib/pages/settings_style/settings_style_view.dart +++ b/lib/pages/settings_style/settings_style_view.dart @@ -10,7 +10,7 @@ import 'settings_style.dart'; class SettingsStyleView extends StatelessWidget { final SettingsStyleController controller; - const SettingsStyleView(this.controller, {Key? key}) : super(key: key); + const SettingsStyleView(this.controller, {super.key}); @override Widget build(BuildContext context) { diff --git a/lib/pages/story/story_page.dart b/lib/pages/story/story_page.dart index 5a3d314b4..d92f7a53b 100644 --- a/lib/pages/story/story_page.dart +++ b/lib/pages/story/story_page.dart @@ -22,7 +22,7 @@ import 'package:path_provider/path_provider.dart'; import 'package:video_player/video_player.dart'; class StoryPage extends StatefulWidget { - const StoryPage({Key? key}) : super(key: key); + const StoryPage({super.key}); @override StoryPageController createState() => StoryPageController(); diff --git a/lib/pages/story/story_view.dart b/lib/pages/story/story_view.dart index ed8325604..58c433bb2 100644 --- a/lib/pages/story/story_view.dart +++ b/lib/pages/story/story_view.dart @@ -18,7 +18,7 @@ import '../../config/themes.dart'; class StoryView extends StatelessWidget { final StoryPageController controller; - const StoryView(this.controller, {Key? key}) : super(key: key); + const StoryView(this.controller, {super.key}); static const List textShadows = [ Shadow( diff --git a/lib/pages/tasks/model/matrix_todo_list.dart b/lib/pages/tasks/model/matrix_todo_list.dart new file mode 100644 index 000000000..fdcb88970 --- /dev/null +++ b/lib/pages/tasks/model/matrix_todo_list.dart @@ -0,0 +1,59 @@ +import 'package:matrix/matrix.dart'; + +extension MatrixTodoExtension on Room { + static const String stateKey = 'im.fluffychat.matrix_todos'; + static const String contentKey = 'todos'; + + List? get matrixTodos => getState(stateKey) + ?.content + .tryGetList(contentKey) + ?.map((json) => MatrixTodo.fromJson(json)) + .toList(); + + Future updateMatrixTodos(List matrixTodos) => + client.setRoomStateWithKey( + id, + stateKey, + '', + {contentKey: matrixTodos.map((todo) => todo.toJson()).toList()}, + ); +} + +class MatrixTodo { + String title; + String? description; + DateTime? dueDate; + bool done; + List? subTasks; + + MatrixTodo({ + required this.title, + this.description, + this.dueDate, + this.done = false, + this.subTasks, + }); + + factory MatrixTodo.fromJson(Map json) => MatrixTodo( + title: json['title'] as String, + description: json['description'] as String?, + dueDate: json['due_date'] == null + ? null + : DateTime.fromMillisecondsSinceEpoch(json['due_date'] as int), + done: json['done'] as bool, + subTasks: json['sub_tasks'] == null + ? null + : (json['sub_tasks'] as List) + .map((json) => MatrixTodo.fromJson(json)) + .toList(), + ); + + Map toJson() => { + 'title': title, + if (description != null) 'description': description, + if (dueDate != null) 'due_date': dueDate?.millisecondsSinceEpoch, + 'done': done, + if (subTasks != null) + 'sub_tasks': subTasks?.map((t) => t.toJson()).toList(), + }; +} diff --git a/lib/pages/tasks/tasks.dart b/lib/pages/tasks/tasks.dart new file mode 100644 index 000000000..7743b99e5 --- /dev/null +++ b/lib/pages/tasks/tasks.dart @@ -0,0 +1,255 @@ +import 'package:flutter/material.dart'; + +import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/pages/tasks/tasks_view.dart'; +import 'package:fluffychat/utils/localized_exception_extension.dart'; +import 'model/matrix_todo_list.dart'; + +class TasksPage extends StatefulWidget { + final Room room; + const TasksPage({required this.room, super.key}); + + @override + State createState() => TasksController(); +} + +class TasksController extends State { + bool isLoading = false; + DateTime? newTaskDateTime; + String? newTaskDescription; + + final FocusNode focusNode = FocusNode(); + final TextEditingController textEditingController = TextEditingController(); + + List? _tmpTodos; + + List get todos => _tmpTodos ?? widget.room.matrixTodos ?? []; + + Stream get onUpdate => widget.room.client.onSync.stream.where( + (syncUpdate) => + syncUpdate.rooms?.join?[widget.room.id]?.state + ?.any((event) => event.type == MatrixTodoExtension.stateKey) ?? + false, + ); + + void setNewTaskDateTime() async { + final now = DateTime.now(); + final date = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: now.subtract(const Duration(days: 365 * 100)), + lastDate: now.add(const Duration(days: 365 * 100)), + ); + if (date == null) return; + setState(() { + newTaskDateTime = date; + }); + } + + void setNewTaskDescription() async { + final text = await showTextInputDialog( + context: context, + title: L10n.of(context)!.addDescription, + textFields: [ + DialogTextField( + hintText: L10n.of(context)!.addDescription, + maxLength: 512, + minLines: 4, + maxLines: 8, + ), + ], + ); + if (text == null || text.single.isEmpty) return; + setState(() { + newTaskDescription = text.single; + }); + } + + void addTodo([_]) { + if (textEditingController.text.isEmpty) return; + updateTodos( + update: (todos) => [ + ...todos, + MatrixTodo( + title: textEditingController.text, + dueDate: newTaskDateTime, + description: newTaskDescription, + ), + ], + onSuccess: () { + newTaskDateTime = null; + newTaskDescription = null; + textEditingController.clear(); + focusNode.requestFocus(); + }, + ); + } + + void toggleDone(int i) => updateTodos( + update: (todos) { + todos[i].done = !todos[i].done; + return todos; + }, + ); + + void cleanUp() => updateTodos( + update: (todos) => todos..removeWhere((t) => t.done), + ); + + void onReorder(int oldindex, int newindex) { + if (newindex > oldindex) { + newindex -= 1; + } + updateTodos( + update: (todos) { + final todo = todos.removeAt(oldindex); + todos.insert(newindex, todo); + return todos; + }, + tmpTodo: true, + ); + } + + void updateTodos({ + required List Function(List) update, + void Function()? onSuccess, + bool tmpTodo = false, + }) async { + setState(() { + isLoading = true; + }); + try { + final newTodos = update(todos); + assert(todos != newTodos); + if (tmpTodo) { + setState(() { + _tmpTodos = newTodos; + }); + onUpdate.first.then((_) { + _tmpTodos = null; + }); + } + await widget.room + .updateMatrixTodos(newTodos) + .timeout(const Duration(seconds: 30)); + onSuccess?.call(); + } on MatrixException catch (e) { + final retryAfterMs = e.retryAfterMs; + if (retryAfterMs == null) rethrow; + Logs().w('Rate limit! Try again in $retryAfterMs ms'); + await Future.delayed(Duration(milliseconds: retryAfterMs)); + updateTodos(update: update, onSuccess: onSuccess); + } catch (e, s) { + Logs().w('Unable to update todo list', e, s); + if (_tmpTodos != null) { + setState(() { + _tmpTodos = null; + }); + } + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: const Duration(seconds: 20), + content: Row( + children: [ + Icon( + Icons.signal_wifi_connected_no_internet_4_outlined, + color: Theme.of(context).colorScheme.background, + ), + const SizedBox(width: 16), + Expanded( + child: Text( + e.toLocalizedString(context), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + action: e is TodoListChangedException + ? null + : SnackBarAction( + label: 'Try again', + onPressed: () { + updateTodos(update: update, onSuccess: onSuccess); + }, + ), + ), + ); + } finally { + setState(() { + isLoading = false; + }); + } + } + + void editTodo(int i, MatrixTodo todo) async { + final texts = await showTextInputDialog( + context: context, + title: L10n.of(context)!.editTodo, + textFields: [ + DialogTextField( + hintText: L10n.of(context)!.newTodo, + initialText: todo.title, + maxLength: 64, + validator: (text) { + if (text == null) return L10n.of(context)!.pleaseAddATitle; + return null; + }, + ), + DialogTextField( + hintText: L10n.of(context)!.addDescription, + maxLength: 512, + minLines: 4, + maxLines: 8, + initialText: todo.description, + ), + ], + ); + if (texts == null) return; + updateTodos( + update: (todos) { + if (todos[i].toJson().toString() != todo.toJson().toString()) { + throw TodoListChangedException(); + } + todos[i].title = texts[0]; + todos[i].description = texts[1].isEmpty ? null : texts[1]; + return todos; + }, + ); + } + + void deleteTodo(int i) => updateTodos( + update: (list) { + list.removeAt(i); + return list; + }, + ); + + void editTodoDueDate(int i, MatrixTodo todo) async { + final now = DateTime.now(); + final date = await showDatePicker( + context: context, + initialDate: todo.dueDate ?? DateTime.now(), + firstDate: now.subtract(const Duration(days: 365 * 100)), + lastDate: now.add(const Duration(days: 365 * 100)), + ); + if (date == null) return; + updateTodos( + update: (todos) { + if (todos[i].toJson().toString() != todo.toJson().toString()) { + throw TodoListChangedException(); + } + todos[i].dueDate = date; + return todos; + }, + ); + } + + @override + Widget build(BuildContext context) => TasksView(this); +} + +class TodoListChangedException implements Exception {} diff --git a/lib/pages/tasks/tasks_view.dart b/lib/pages/tasks/tasks_view.dart new file mode 100644 index 000000000..8046b44d1 --- /dev/null +++ b/lib/pages/tasks/tasks_view.dart @@ -0,0 +1,234 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:intl/intl.dart'; + +import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pages/tasks/tasks.dart'; + +class TasksView extends StatelessWidget { + final TasksController controller; + const TasksView(this.controller, {super.key}); + + @override + Widget build(BuildContext context) { + final tag = Localizations.maybeLocaleOf(context)?.toLanguageTag(); + return StreamBuilder( + stream: controller.widget.room.onUpdate.stream, + builder: (context, snapshot) { + final list = controller.todos; + return Scaffold( + appBar: AppBar( + title: Text(L10n.of(context)!.todoLists), + actions: [ + AnimatedCrossFade( + duration: FluffyThemes.animationDuration, + firstChild: const SizedBox( + width: 32, + height: 32, + ), + secondChild: const Padding( + padding: EdgeInsets.all(8.0), + child: SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator.adaptive(strokeWidth: 2), + ), + ), + crossFadeState: controller.isLoading + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, + ), + if (list.any((todo) => todo.done)) + IconButton( + icon: const Icon(Icons.cleaning_services_outlined), + onPressed: controller.cleanUp, + ), + ], + ), + body: Column( + children: [ + Expanded( + child: Opacity( + opacity: controller.isLoading ? 0.66 : 1, + child: list.isEmpty + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.task_alt, + size: 80, + color: Theme.of(context).colorScheme.secondary, + ), + const SizedBox(height: 16), + SizedBox( + width: 256, + child: Text( + L10n.of(context)!.noTodosYet, + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 16), + SizedBox( + width: 256, + child: Text( + L10n.of(context)!.todosUnencrypted, + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.orange), + ), + ), + ], + ) + : ReorderableListView.builder( + onReorder: controller.onReorder, + itemCount: list.length, + buildDefaultDragHandles: false, + itemBuilder: (context, i) { + final todo = list[i]; + final description = todo.description; + final dueDate = todo.dueDate; + return ListTile( + key: Key(todo.toJson().toString()), + leading: Icon( + todo.done + ? Icons.check_circle + : Icons.circle_outlined, + ), + title: Text( + todo.title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + decoration: todo.done + ? TextDecoration.lineThrough + : null, + ), + ), + subtitle: description == null && dueDate == null + ? null + : Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + if (description != null) + Text( + description, + maxLines: 2, + ), + if (dueDate != null) + SizedBox( + height: 24, + child: OutlinedButton.icon( + style: OutlinedButton.styleFrom( + padding: + const EdgeInsets.symmetric( + horizontal: 6, + ), + ), + icon: const Icon( + Icons.calendar_month, + size: 16, + ), + label: Text( + DateFormat.yMMMd(tag) + .format(dueDate), + style: const TextStyle( + fontSize: 12, + ), + ), + onPressed: () => + controller.editTodoDueDate( + i, + todo, + ), + ), + ), + ], + ), + onTap: controller.isLoading + ? null + : () => controller.toggleDone(i), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon( + Icons.edit_outlined, + size: 16, + ), + onPressed: () => + controller.editTodo(i, todo), + ), + IconButton( + icon: const Icon( + Icons.delete_outlined, + size: 16, + ), + onPressed: () => controller.deleteTodo(i), + ), + ReorderableDragStartListener( + index: i, + child: + const Icon(Icons.drag_handle_outlined), + ), + ], + ), + ); + }, + ), + ), + ), + Padding( + padding: const EdgeInsets.all(12.0), + child: TextField( + focusNode: controller.focusNode, + readOnly: controller.isLoading, + controller: controller.textEditingController, + onSubmitted: controller.addTodo, + maxLength: 64, + decoration: InputDecoration( + counterStyle: const TextStyle(height: double.minPositive), + counterText: '', + hintText: L10n.of(context)!.newTodo, + prefixIcon: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon( + controller.newTaskDateTime == null + ? Icons.calendar_month_outlined + : Icons.calendar_month, + color: controller.newTaskDateTime == null + ? null + : Theme.of(context).primaryColor, + ), + onPressed: controller.setNewTaskDateTime, + ), + IconButton( + icon: Icon( + Icons.text_fields, + color: controller.newTaskDescription == null + ? null + : Theme.of(context).primaryColor, + ), + onPressed: controller.setNewTaskDescription, + ), + ], + ), + suffixIcon: IconButton( + icon: const Icon(Icons.add_outlined), + onPressed: + controller.isLoading ? null : controller.addTodo, + ), + ), + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet.dart index e83e4e25c..1d190acdf 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet.dart @@ -232,7 +232,10 @@ class UserBottomSheetController extends State { ); if (roomIdResult.error != null) return; widget.outerContext.go('/rooms/${roomIdResult.result!}'); - Navigator.of(context, rootNavigator: false).pop(); + Navigator.of(context, rootNavigator: false) + ..pop() + ..pop(); + widget.outerContext.go('/rooms/${roomIdResult.result!}'); break; case UserBottomSheetAction.ignore: context.go('/rooms/settings/security/ignorelist'); diff --git a/lib/pangea/pages/class_settings/p_class_widgets/delete_class_tile.dart b/lib/pangea/pages/class_settings/p_class_widgets/delete_class_tile.dart index 44e23c1e9..cc7b447e3 100644 --- a/lib/pangea/pages/class_settings/p_class_widgets/delete_class_tile.dart +++ b/lib/pangea/pages/class_settings/p_class_widgets/delete_class_tile.dart @@ -3,16 +3,16 @@ import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; +import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; -import 'package:vrouter/vrouter.dart'; class DeleteSpaceTile extends StatelessWidget { final Room room; const DeleteSpaceTile({ - Key? key, + super.key, required this.room, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -36,12 +36,12 @@ class DeleteSpaceTile extends StatelessWidget { ); } deleteRoom(room.id, client); - VRouter.of(context).to('/rooms'); + context.go('/rooms'); return; } Future deleteChat() { - VRouter.of(context).to('/rooms'); + context.go('/rooms'); return deleteRoom(room.id, Matrix.of(context).client); } @@ -81,7 +81,7 @@ class DeleteSpaceTile extends StatelessWidget { fontSize: 14, ), textAlign: TextAlign.center, - ) + ), ], ), content: room.isSpace @@ -124,7 +124,7 @@ class DeleteSpaceTile extends StatelessWidget { onPressed: () { Navigator.of(context).pop(); }, - ) + ), ], ); }, diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart index 41b225473..6ee4c8c07 100644 --- a/lib/utils/background_push.dart +++ b/lib/utils/background_push.dart @@ -41,7 +41,6 @@ import 'package:unifiedpush/unifiedpush.dart'; import '../config/app_config.dart'; import '../config/setting_keys.dart'; import '../widgets/matrix.dart'; -import 'famedlysdk_store.dart'; import 'platform_infos.dart'; //import 'package:fcm_shared_isolate/fcm_shared_isolate.dart'; @@ -59,8 +58,7 @@ class BackgroundPush { String? _fcmToken; void Function(String errorMsg, {Uri? link})? onFcmError; L10n? l10n; - Store? _store; - Store get store => _store ??= Store(); + Future loadLocale() async { final context = matrix?.context; // inspired by _lookupL10n in .dart_tool/flutter_gen/gen_l10n/l10n.dart @@ -311,14 +309,9 @@ class BackgroundPush { if (matrix == null) { return; } - // #Pangea - if (await store.getItemBool(SettingKeys.showNoGoogle, true) == true) { + if ((matrix?.store.getBool(SettingKeys.showNoGoogle) ?? false) == true) { return; } - // if (await store.getItemBool(SettingKeys.showNoGoogle, false) == true) { - // return; - // } - // Pangea# await loadLocale(); WidgetsBinding.instance.addPostFrameCallback((_) { if (PlatformInfos.isAndroid) { @@ -428,16 +421,17 @@ class BackgroundPush { oldTokens: oldTokens, useDeviceSpecificAppId: true, ); - await store.setItem(SettingKeys.unifiedPushEndpoint, newEndpoint); - await store.setItemBool(SettingKeys.unifiedPushRegistered, true); + await matrix?.store.setString(SettingKeys.unifiedPushEndpoint, newEndpoint); + await matrix?.store.setBool(SettingKeys.unifiedPushRegistered, true); } Future _upUnregistered(String i) async { upAction = true; Logs().i('[Push] Removing UnifiedPush endpoint...'); - final oldEndpoint = await store.getItem(SettingKeys.unifiedPushEndpoint); - await store.setItemBool(SettingKeys.unifiedPushRegistered, false); - await store.deleteItem(SettingKeys.unifiedPushEndpoint); + final oldEndpoint = + matrix?.store.getString(SettingKeys.unifiedPushEndpoint); + await matrix?.store.setBool(SettingKeys.unifiedPushRegistered, false); + await matrix?.store.remove(SettingKeys.unifiedPushEndpoint); if (oldEndpoint?.isNotEmpty ?? false) { // remove the old pusher await setupPusher( @@ -463,12 +457,12 @@ class BackgroundPush { /// Workaround for the problem that local notification IDs must be int but we /// sort by [roomId] which is a String. To make sure that we don't have duplicated - /// IDs we map the [roomId] to a number and store this number. + /// IDs we map the [roomId] to a number and matrix?.store this number. late Map idMap; Future _loadIdMap() async { idMap = Map.from( json.decode( - (await store.getItem(SettingKeys.notificationCurrentIds)) ?? '{}', + (matrix?.store.getString(SettingKeys.notificationCurrentIds)) ?? '{}', ), ); } @@ -542,7 +536,7 @@ class BackgroundPush { } } if (changed) { - await store.setItem( + await matrix?.store.setString( SettingKeys.notificationCurrentIds, json.encode(idMap), ); diff --git a/lib/utils/client_manager.dart b/lib/utils/client_manager.dart index 0c983f99b..449c2ca17 100644 --- a/lib/utils/client_manager.dart +++ b/lib/utils/client_manager.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; import 'package:fluffychat/utils/custom_http_client.dart'; import 'package:fluffychat/utils/custom_image_resizer.dart'; @@ -10,12 +8,14 @@ import 'package:hive_flutter/hive_flutter.dart'; import 'package:matrix/encryption/utils/key_verification.dart'; import 'package:matrix/matrix.dart'; import 'package:path_provider/path_provider.dart'; - -import 'famedlysdk_store.dart'; +import 'package:shared_preferences/shared_preferences.dart'; abstract class ClientManager { static const String clientNamespace = 'im.fluffychat.store.clients'; - static Future> getClients({bool initialize = true}) async { + static Future> getClients({ + bool initialize = true, + required SharedPreferences store, + }) async { if (PlatformInfos.isLinux) { Hive.init((await getApplicationSupportDirectory()).path); } else { @@ -23,19 +23,15 @@ abstract class ClientManager { } final clientNames = {}; try { - final rawClientNames = await Store().getItem(clientNamespace); - if (rawClientNames != null) { - final clientNamesList = - (jsonDecode(rawClientNames) as List).cast(); - clientNames.addAll(clientNamesList); - } + final clientNamesList = store.getStringList(clientNamespace) ?? []; + clientNames.addAll(clientNamesList); } catch (e, s) { Logs().w('Client names in store are corrupted', e, s); - await Store().deleteItem(clientNamespace); + await store.remove(clientNamespace); } if (clientNames.isEmpty) { clientNames.add(PlatformInfos.clientName); - await Store().setItem(clientNamespace, jsonEncode(clientNames.toList())); + await store.setStringList(clientNamespace, clientNames.toList()); } final clients = clientNames.map(createClient).toList(); if (initialize) { @@ -61,31 +57,27 @@ abstract class ClientManager { clientNames.remove(client.clientName); clients.remove(client); } - await Store().setItem(clientNamespace, jsonEncode(clientNames.toList())); + await store.setStringList(clientNamespace, clientNames.toList()); } return clients; } - static Future addClientNameToStore(String clientName) async { - final clientNamesList = []; - final rawClientNames = await Store().getItem(clientNamespace); - if (rawClientNames != null) { - final stored = (jsonDecode(rawClientNames) as List).cast(); - clientNamesList.addAll(stored); - } + static Future addClientNameToStore( + String clientName, + SharedPreferences store, + ) async { + final clientNamesList = store.getStringList(clientNamespace) ?? []; clientNamesList.add(clientName); - await Store().setItem(clientNamespace, jsonEncode(clientNamesList)); + await store.setStringList(clientNamespace, clientNamesList); } - static Future removeClientNameFromStore(String clientName) async { - final clientNamesList = []; - final rawClientNames = await Store().getItem(clientNamespace); - if (rawClientNames != null) { - final stored = (jsonDecode(rawClientNames) as List).cast(); - clientNamesList.addAll(stored); - } + static Future removeClientNameFromStore( + String clientName, + SharedPreferences store, + ) async { + final clientNamesList = store.getStringList(clientNamespace) ?? []; clientNamesList.remove(clientName); - await Store().setItem(clientNamespace, jsonEncode(clientNamesList)); + await store.setStringList(clientNamespace, clientNamesList); } static NativeImplementations get nativeImplementations => kIsWeb diff --git a/lib/utils/custom_scroll_behaviour.dart b/lib/utils/custom_scroll_behaviour.dart index 719b75473..ba25e9564 100644 --- a/lib/utils/custom_scroll_behaviour.dart +++ b/lib/utils/custom_scroll_behaviour.dart @@ -5,7 +5,6 @@ class CustomScrollBehavior extends MaterialScrollBehavior { @override Set get dragDevices => { PointerDeviceKind.touch, - PointerDeviceKind.mouse, PointerDeviceKind.trackpad, }; } diff --git a/lib/utils/famedlysdk_store.dart b/lib/utils/famedlysdk_store.dart deleted file mode 100644 index 2f16b5963..000000000 --- a/lib/utils/famedlysdk_store.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'dart:core'; - -import 'package:shared_preferences/shared_preferences.dart'; - -class Store { - SharedPreferences? _prefs; - - Future _setupLocalStorage() async { - _prefs ??= await SharedPreferences.getInstance(); - } - - Future getItem(String key) async { - await _setupLocalStorage(); - return _prefs!.getString(key); - } - - Future getItemBool(String key, [bool? defaultValue]) async { - await _setupLocalStorage(); - return _prefs!.getBool(key) ?? defaultValue ?? true; - } - - Future setItem(String key, String? value) async { - await _setupLocalStorage(); - if (value == null) { - await _prefs!.remove(key); - return; - } - await _prefs!.setString(key, value); - return; - } - - Future setItemBool(String key, bool value) async { - await _setupLocalStorage(); - await _prefs!.setBool(key, value); - return; - } - - Future deleteItem(String key) async { - await _setupLocalStorage(); - await _prefs!.remove(key); - return; - } -} diff --git a/lib/utils/localized_exception_extension.dart b/lib/utils/localized_exception_extension.dart index b3d6093c3..d665c7f10 100644 --- a/lib/utils/localized_exception_extension.dart +++ b/lib/utils/localized_exception_extension.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; +import 'package:fluffychat/pages/tasks/tasks.dart'; import 'uia_request_manager.dart'; extension LocalizedExceptionExtension on Object { @@ -19,6 +20,9 @@ extension LocalizedExceptionExtension on Object { return (this as MatrixException).errorMessage; } } + if (this is TodoListChangedException) { + return L10n.of(context)!.todoListChangedError; + } if (this is FileTooBigMatrixException) { return L10n.of(context)!.fileIsTooBigForServer; } diff --git a/lib/utils/matrix_sdk_extensions/event_extension.dart b/lib/utils/matrix_sdk_extensions/event_extension.dart index dbd4a4aec..ddd5abe80 100644 --- a/lib/utils/matrix_sdk_extensions/event_extension.dart +++ b/lib/utils/matrix_sdk_extensions/event_extension.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:async/async.dart' as async; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:matrix/matrix.dart'; @@ -10,7 +11,7 @@ import 'package:fluffychat/utils/size_string.dart'; import 'matrix_file_extension.dart'; extension LocalizedBody on Event { - Future> _getFile(BuildContext context) => + Future> _getFile(BuildContext context) => showFutureLoadingDialog( context: context, future: downloadAndDecryptAttachment, diff --git a/lib/utils/matrix_sdk_extensions/flutter_hive_collections_database.dart b/lib/utils/matrix_sdk_extensions/flutter_hive_collections_database.dart index 989d0d25b..3fe18942c 100644 --- a/lib/utils/matrix_sdk_extensions/flutter_hive_collections_database.dart +++ b/lib/utils/matrix_sdk_extensions/flutter_hive_collections_database.dart @@ -11,14 +11,10 @@ import 'package:universal_html/html.dart' as html; class FlutterHiveCollectionsDatabase extends HiveCollectionsDatabase { FlutterHiveCollectionsDatabase( - String name, - String path, { - HiveCipher? key, - }) : super( - name, - path, - key: key, - ); + super.name, + String super.path, { + super.key, + }); static const String _cipherStorageKey = 'hive_encryption_key'; diff --git a/lib/utils/push_helper.dart b/lib/utils/push_helper.dart index 6c361de32..0b2c94330 100644 --- a/lib/utils/push_helper.dart +++ b/lib/utils/push_helper.dart @@ -102,7 +102,11 @@ Future _tryPushHelper( //onDidReceiveBackgroundNotificationResponse: onSelectNotification, ); - client ??= (await ClientManager.getClients(initialize: false)).first; + client ??= (await ClientManager.getClients( + initialize: false, + store: await SharedPreferences.getInstance(), + )) + .first; final event = await client.getEventByPushNotification( notification, storeInDatabase: isBackgroundMessage, diff --git a/lib/utils/voip_plugin.dart b/lib/utils/voip_plugin.dart index 8a3a0f2ee..186b4da3d 100644 --- a/lib/utils/voip_plugin.dart +++ b/lib/utils/voip_plugin.dart @@ -10,7 +10,6 @@ import 'package:flutter_webrtc/flutter_webrtc.dart' as webrtc_impl; import 'package:matrix/matrix.dart'; import 'package:webrtc_interface/webrtc_interface.dart' hide Navigator; -import '../../utils/famedlysdk_store.dart'; import '../../utils/voip/callkeep_manager.dart'; import '../../utils/voip/user_media_manager.dart'; import '../widgets/matrix.dart'; @@ -132,7 +131,8 @@ class VoipPlugin with WidgetsBindingObserver implements WebRTCDelegate { } else { try { final wasForeground = await FlutterForegroundTask.isAppOnForeground; - await Store().setItem( + + await matrix.store.setString( 'wasForeground', wasForeground == true ? 'true' : 'false', ); @@ -171,7 +171,7 @@ class VoipPlugin with WidgetsBindingObserver implements WebRTCDelegate { if (PlatformInfos.isAndroid) { FlutterForegroundTask.setOnLockScreenVisibility(false); FlutterForegroundTask.stopService(); - final wasForeground = await Store().getItem('wasForeground'); + final wasForeground = matrix.store.getString('wasForeground'); wasForeground == 'false' ? FlutterForegroundTask.minimizeApp() : null; } } diff --git a/lib/widgets/adaptive_flat_button.dart b/lib/widgets/adaptive_flat_button.dart index 7faa7eed4..faed7e951 100644 --- a/lib/widgets/adaptive_flat_button.dart +++ b/lib/widgets/adaptive_flat_button.dart @@ -9,11 +9,11 @@ class AdaptiveFlatButton extends StatelessWidget { final void Function()? onPressed; const AdaptiveFlatButton({ - Key? key, + super.key, required this.label, this.textColor, this.onPressed, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/lib/widgets/avatar.dart b/lib/widgets/avatar.dart index 76a3a49dd..433d60f60 100644 --- a/lib/widgets/avatar.dart +++ b/lib/widgets/avatar.dart @@ -25,8 +25,8 @@ class Avatar extends StatelessWidget { //#Pangea this.littleIcon, // Pangea# - Key? key, - }) : super(key: key); + super.key, + }); @override Widget build(BuildContext context) { diff --git a/lib/widgets/chat_settings_popup_menu.dart b/lib/widgets/chat_settings_popup_menu.dart index 4d6abdd89..9bcefcbb5 100644 --- a/lib/widgets/chat_settings_popup_menu.dart +++ b/lib/widgets/chat_settings_popup_menu.dart @@ -107,6 +107,16 @@ class ChatSettingsPopupMenuState extends State { // ), // ), // Pangea# + PopupMenuItem( + value: 'todos', + child: Row( + children: [ + const Icon(Icons.task_alt_outlined), + const SizedBox(width: 12), + Text(L10n.of(context)!.todoLists), + ], + ), + ), PopupMenuItem( value: 'leave', child: Row( diff --git a/lib/widgets/connection_status_header.dart b/lib/widgets/connection_status_header.dart index 7d58b000f..2283c41bf 100644 --- a/lib/widgets/connection_status_header.dart +++ b/lib/widgets/connection_status_header.dart @@ -10,7 +10,7 @@ import '../utils/localized_exception_extension.dart'; import 'matrix.dart'; class ConnectionStatusHeader extends StatefulWidget { - const ConnectionStatusHeader({Key? key}) : super(key: key); + const ConnectionStatusHeader({super.key}); @override ConnectionStatusHeaderState createState() => ConnectionStatusHeaderState(); diff --git a/lib/widgets/content_banner.dart b/lib/widgets/content_banner.dart index 72a89a5ad..f9e6a6167 100644 --- a/lib/widgets/content_banner.dart +++ b/lib/widgets/content_banner.dart @@ -21,8 +21,8 @@ class ContentBanner extends StatelessWidget { this.client, this.opacity = 0.75, this.placeholder, - Key? key, - }) : super(key: key); + super.key, + }); @override Widget build(BuildContext context) { diff --git a/lib/widgets/error_widget.dart b/lib/widgets/error_widget.dart new file mode 100644 index 000000000..8606c7608 --- /dev/null +++ b/lib/widgets/error_widget.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; + +import 'package:fluffychat/utils/error_reporter.dart'; + +class FluffyChatErrorWidget extends StatefulWidget { + final FlutterErrorDetails details; + const FluffyChatErrorWidget(this.details, {super.key}); + + @override + State createState() => _FluffyChatErrorWidgetState(); +} + +class _FluffyChatErrorWidgetState extends State { + @override + void initState() { + super.initState(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + ErrorReporter(context, 'Error Widget').onErrorCallback( + widget.details.exception, + widget.details.stack, + ); + }); + } + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.orange, + child: Placeholder( + child: Center( + child: Material( + color: Colors.white.withOpacity(0.9), + borderRadius: BorderRadius.circular(8), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + '😲 Oh no! Something is broken 😲\n${widget.details.exception}', + maxLines: 5, + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.black), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/widgets/fluffy_chat_app.dart b/lib/widgets/fluffy_chat_app.dart index e1237fa8b..ac202798b 100644 --- a/lib/widgets/fluffy_chat_app.dart +++ b/lib/widgets/fluffy_chat_app.dart @@ -9,6 +9,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import '../config/app_config.dart'; import '../utils/custom_scroll_behaviour.dart'; @@ -18,11 +19,13 @@ class FluffyChatApp extends StatelessWidget { final Widget? testWidget; final List clients; final String? pincode; + final SharedPreferences store; const FluffyChatApp({ super.key, this.testWidget, required this.clients, + required this.store, this.pincode, }); @@ -73,6 +76,7 @@ class FluffyChatApp extends StatelessWidget { onGenerateRoute: (_) => MaterialPageRoute( builder: (_) => Matrix( clients: clients, + store: store, child: testWidget ?? child, ), ), diff --git a/lib/widgets/hover_builder.dart b/lib/widgets/hover_builder.dart new file mode 100644 index 000000000..f895d8532 --- /dev/null +++ b/lib/widgets/hover_builder.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +class HoverBuilder extends StatefulWidget { + final Widget Function(BuildContext context, bool hovered) builder; + const HoverBuilder({required this.builder, super.key}); + + @override + State createState() => _HoverBuilderState(); +} + +class _HoverBuilderState extends State { + bool hovered = false; + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (_) => hovered + ? null + : setState(() { + hovered = true; + }), + onExit: (_) => !hovered + ? null + : setState(() { + hovered = false; + }), + child: widget.builder(context, hovered), + ); + } +} diff --git a/lib/widgets/layouts/empty_page.dart b/lib/widgets/layouts/empty_page.dart index 75550d942..d976d0e0b 100644 --- a/lib/widgets/layouts/empty_page.dart +++ b/lib/widgets/layouts/empty_page.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; class EmptyPage extends StatelessWidget { final bool loading; static const double _width = 300; - const EmptyPage({this.loading = false, Key? key}) : super(key: key); + const EmptyPage({this.loading = false, super.key}); @override Widget build(BuildContext context) { final width = min(MediaQuery.of(context).size.width, EmptyPage._width) / 2; diff --git a/lib/widgets/layouts/max_width_body.dart b/lib/widgets/layouts/max_width_body.dart index 36e21a368..f57214a3d 100644 --- a/lib/widgets/layouts/max_width_body.dart +++ b/lib/widgets/layouts/max_width_body.dart @@ -14,8 +14,8 @@ class MaxWidthBody extends StatelessWidget { this.maxWidth = 600, this.withFrame = true, this.withScrolling = true, - Key? key, - }) : super(key: key); + super.key, + }); @override Widget build(BuildContext context) { return SafeArea( diff --git a/lib/widgets/layouts/two_column_layout.dart b/lib/widgets/layouts/two_column_layout.dart index f0fdb9912..a6f4c8bdf 100644 --- a/lib/widgets/layouts/two_column_layout.dart +++ b/lib/widgets/layouts/two_column_layout.dart @@ -6,11 +6,11 @@ class TwoColumnLayout extends StatelessWidget { final bool displayNavigationRail; const TwoColumnLayout({ - Key? key, + super.key, required this.mainView, required this.sideView, required this.displayNavigationRail, - }) : super(key: key); + }); @override Widget build(BuildContext context) { return ScaffoldMessenger( diff --git a/lib/widgets/log_view.dart b/lib/widgets/log_view.dart index 43e5b6c38..e7e5f9238 100644 --- a/lib/widgets/log_view.dart +++ b/lib/widgets/log_view.dart @@ -3,7 +3,7 @@ import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; class LogViewer extends StatefulWidget { - const LogViewer({Key? key}) : super(key: key); + const LogViewer({super.key}); @override LogViewerState createState() => LogViewerState(); diff --git a/lib/widgets/matrix.dart b/lib/widgets/matrix.dart index 61d13d5ed..35c541dd4 100644 --- a/lib/widgets/matrix.dart +++ b/lib/widgets/matrix.dart @@ -22,6 +22,7 @@ import 'package:image_picker/image_picker.dart'; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:universal_html/html.dart' as html; import 'package:url_launcher/url_launcher_string.dart'; @@ -30,7 +31,6 @@ import '../config/setting_keys.dart'; import '../pages/key_verification/key_verification_dialog.dart'; import '../utils/account_bundles.dart'; import '../utils/background_push.dart'; -import '../utils/famedlysdk_store.dart'; import 'local_notifications_extension.dart'; // import 'package:flutter_secure_storage/flutter_secure_storage.dart'; @@ -42,9 +42,12 @@ class Matrix extends StatefulWidget { final Map? queryParameters; + final SharedPreferences store; + const Matrix({ this.child, required this.clients, + required this.store, this.queryParameters, super.key, }); @@ -60,11 +63,11 @@ class Matrix extends StatefulWidget { class MatrixState extends State with WidgetsBindingObserver { int _activeClient = -1; String? activeBundle; - Store store = Store(); // #Pangea static late PangeaController pangeaController; static PangeaAnyState pAnyState = PangeaAnyState(); // Pangea# + SharedPreferences get store => widget.store; HomeserverSummary? loginHomeserverSummary; XFile? loginAvatar; @@ -163,7 +166,10 @@ class MatrixState extends State with WidgetsBindingObserver { if (!widget.clients.contains(_loginClientCandidate)) { widget.clients.add(_loginClientCandidate!); } - ClientManager.addClientNameToStore(_loginClientCandidate!.clientName); + ClientManager.addClientNameToStore( + _loginClientCandidate!.clientName, + store, + ); _registerSubs(_loginClientCandidate!.clientName); _loginClientCandidate = null; FluffyChatApp.router.go('/rooms'); @@ -192,7 +198,7 @@ class MatrixState extends State with WidgetsBindingObserver { try { if (client.isLogged()) { // TODO: Figure out how this works in multi account - final statusMsg = await store.getItem(SettingKeys.ownStatusMessage); + final statusMsg = store.getString(SettingKeys.ownStatusMessage); if (statusMsg?.isNotEmpty ?? false) { Logs().v('Send cached status message: "$statusMsg"'); await client.setPresence( @@ -327,7 +333,7 @@ class MatrixState extends State with WidgetsBindingObserver { if (loggedInWithMultipleClients && state != LoginState.loggedIn) { _cancelSubs(c.clientName); widget.clients.remove(c); - ClientManager.removeClientNameFromStore(c.clientName); + ClientManager.removeClientNameFromStore(c.clientName, store); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(L10n.of(context)!.oneClientLoggedOut), @@ -419,7 +425,7 @@ class MatrixState extends State with WidgetsBindingObserver { ); } if (result == OkCancelResult.cancel) { - await store.setItemBool(SettingKeys.showNoGoogle, true); + await store.setBool(SettingKeys.showNoGoogle, true); } }, ); @@ -429,7 +435,7 @@ class MatrixState extends State with WidgetsBindingObserver { } void createVoipPlugin() async { - if (await store.getItemBool(SettingKeys.experimentalVoip) == false) { + if (store.getBool(SettingKeys.experimentalVoip) == false) { voipPlugin = null; return; } @@ -447,47 +453,40 @@ class MatrixState extends State with WidgetsBindingObserver { } void initSettings() { - store.getItem(SettingKeys.wallpaper).then((final path) async { - if (path == null) return; - final file = File(path); - if (await file.exists()) { - wallpaper = file; - } - }); - store.getItem(SettingKeys.fontSizeFactor).then( - (value) => AppConfig.fontSizeFactor = - double.tryParse(value ?? '') ?? AppConfig.fontSizeFactor, - ); - store - .getItemBool(SettingKeys.renderHtml, AppConfig.renderHtml) - .then((value) => AppConfig.renderHtml = value); - store - .getItemBool( - SettingKeys.hideRedactedEvents, - AppConfig.hideRedactedEvents, - ) - .then((value) => AppConfig.hideRedactedEvents = value); - store - .getItemBool(SettingKeys.hideUnknownEvents, AppConfig.hideUnknownEvents) - .then((value) => AppConfig.hideUnknownEvents = value); - store - .getItemBool(SettingKeys.separateChatTypes, AppConfig.separateChatTypes) - .then((value) => AppConfig.separateChatTypes = value); - store - .getItemBool(SettingKeys.autoplayImages, AppConfig.autoplayImages) - .then((value) => AppConfig.autoplayImages = value); - store - .getItemBool( - SettingKeys.sendTypingNotifications, - AppConfig.sendTypingNotifications, - ) - .then((value) => AppConfig.sendTypingNotifications = value); - store - .getItemBool(SettingKeys.sendOnEnter, AppConfig.sendOnEnter) - .then((value) => AppConfig.sendOnEnter = value); - store - .getItemBool(SettingKeys.experimentalVoip, AppConfig.experimentalVoip) - .then((value) => AppConfig.experimentalVoip = value); + final path = store.getString(SettingKeys.wallpaper); + if (path != null) wallpaper = File(path); + + AppConfig.fontSizeFactor = + double.tryParse(store.getString(SettingKeys.fontSizeFactor) ?? '') ?? + AppConfig.fontSizeFactor; + + AppConfig.renderHtml = + store.getBool(SettingKeys.renderHtml) ?? AppConfig.renderHtml; + + AppConfig.hideRedactedEvents = + store.getBool(SettingKeys.hideRedactedEvents) ?? + AppConfig.hideRedactedEvents; + + AppConfig.hideUnknownEvents = + store.getBool(SettingKeys.hideUnknownEvents) ?? + AppConfig.hideUnknownEvents; + + AppConfig.separateChatTypes = + store.getBool(SettingKeys.separateChatTypes) ?? + AppConfig.separateChatTypes; + + AppConfig.autoplayImages = + store.getBool(SettingKeys.autoplayImages) ?? AppConfig.autoplayImages; + + AppConfig.sendTypingNotifications = + store.getBool(SettingKeys.sendTypingNotifications) ?? + AppConfig.sendTypingNotifications; + + AppConfig.sendOnEnter = + store.getBool(SettingKeys.sendOnEnter) ?? AppConfig.sendOnEnter; + + AppConfig.experimentalVoip = store.getBool(SettingKeys.experimentalVoip) ?? + AppConfig.experimentalVoip; } @override diff --git a/lib/widgets/mxc_image.dart b/lib/widgets/mxc_image.dart index beb79425a..0aedd1e4f 100644 --- a/lib/widgets/mxc_image.dart +++ b/lib/widgets/mxc_image.dart @@ -38,8 +38,8 @@ class MxcImage extends StatefulWidget { this.animationCurve = FluffyThemes.animationCurve, this.thumbnailMethod = ThumbnailMethod.scale, this.cacheKey, - Key? key, - }) : super(key: key); + super.key, + }); @override State createState() => _MxcImageState(); diff --git a/lib/widgets/permission_slider_dialog.dart b/lib/widgets/permission_slider_dialog.dart index b67729978..712432cef 100644 --- a/lib/widgets/permission_slider_dialog.dart +++ b/lib/widgets/permission_slider_dialog.dart @@ -60,6 +60,16 @@ Future showPermissionChooser( initialText: currentLevel.toString(), keyboardType: TextInputType.number, autocorrect: false, + validator: (text) { + if (text == null) { + return L10n.of(context)!.pleaseEnterANumber; + } + final level = int.tryParse(text); + if (level == null || level < 0) { + return L10n.of(context)!.pleaseEnterANumber; + } + return null; + }, ), ], ); diff --git a/lib/widgets/public_room_bottom_sheet.dart b/lib/widgets/public_room_bottom_sheet.dart index b49b2e6d2..eb386c432 100644 --- a/lib/widgets/public_room_bottom_sheet.dart +++ b/lib/widgets/public_room_bottom_sheet.dart @@ -21,8 +21,8 @@ class PublicRoomBottomSheet extends StatelessWidget { required this.outerContext, this.chunk, this.onRoomJoined, - Key? key, - }) : super(key: key) { + super.key, + }) { assert(roomAlias != null || chunk != null); } diff --git a/lib/widgets/settings_switch_list_tile.dart b/lib/widgets/settings_switch_list_tile.dart index a3da2dbb6..8f4285a4b 100644 --- a/lib/widgets/settings_switch_list_tile.dart +++ b/lib/widgets/settings_switch_list_tile.dart @@ -9,12 +9,12 @@ class SettingsSwitchListTile extends StatefulWidget { final Function(bool)? onChanged; const SettingsSwitchListTile.adaptive({ - Key? key, + super.key, this.defaultValue = false, required this.storeKey, required this.title, this.onChanged, - }) : super(key: key); + }); @override SettingsSwitchListTileState createState() => SettingsSwitchListTileState(); @@ -23,19 +23,15 @@ class SettingsSwitchListTile extends StatefulWidget { class SettingsSwitchListTileState extends State { @override Widget build(BuildContext context) { - return FutureBuilder( - future: Matrix.of(context) - .store - .getItemBool(widget.storeKey, widget.defaultValue), - builder: (context, snapshot) => SwitchListTile.adaptive( - value: snapshot.data ?? widget.defaultValue, - title: Text(widget.title), - onChanged: (bool newValue) async { - widget.onChanged?.call(newValue); - await Matrix.of(context).store.setItemBool(widget.storeKey, newValue); - setState(() {}); - }, - ), + return SwitchListTile.adaptive( + value: Matrix.of(context).store.getBool(widget.storeKey) ?? + widget.defaultValue, + title: Text(widget.title), + onChanged: (bool newValue) async { + widget.onChanged?.call(newValue); + await Matrix.of(context).store.setBool(widget.storeKey, newValue); + setState(() {}); + }, ); } } diff --git a/lib/widgets/theme_builder.dart b/lib/widgets/theme_builder.dart index b1ca4aa42..b35a0b449 100644 --- a/lib/widgets/theme_builder.dart +++ b/lib/widgets/theme_builder.dart @@ -19,8 +19,8 @@ class ThemeBuilder extends StatefulWidget { required this.builder, this.themeModeSettingsKey = 'theme_mode', this.primaryColorSettingsKey = 'primary_color', - Key? key, - }) : super(key: key); + super.key, + }); @override State createState() => ThemeController(); diff --git a/linux/my_application.cc b/linux/my_application.cc index 269a85872..6dea055f2 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -53,11 +53,11 @@ static void my_application_activate(GApplication* application) { if (use_header_bar) { GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); - gtk_header_bar_set_title(header_bar, "fluffychat"); + gtk_header_bar_set_title(header_bar, "FluffyChat"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); } else { - gtk_window_set_title(window, "fluffychat"); + gtk_window_set_title(window, "FluffyChat"); } gtk_window_set_default_size(window, 864, 680); diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 16eea7f4d..80363dd23 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -62,7 +62,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin")) MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin")) - FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PurchasesFlutterPlugin.register(with: registry.registrar(forPlugin: "PurchasesFlutterPlugin")) diff --git a/needed-translations.txt b/needed-translations.txt index fd4622756..ea40fd5dc 100644 --- a/needed-translations.txt +++ b/needed-translations.txt @@ -4,10 +4,6 @@ "addNewFriend", "alreadyHaveAnAccount", "classes", - "hasKnocked", - "pleaseEnterANumber", - "archiveRoomDescription", - "roomUpgradeDescription", "allCorrect", "newWayAllGood", "othersAreBetter", @@ -751,13 +747,7 @@ "reportToTeacher", "reportMessageTitle", "reportMessageBody", - "noTeachersFound", - "pushNotificationsNotAvailable", - "learnMore", - "banUserDescription", - "unbanUserDescription", - "kickUserDescription", - "makeAdminDescription" + "noTeachersFound" ], "bn": [ @@ -2099,7 +2089,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "bo": [ @@ -3445,7 +3443,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "ca": [ @@ -4414,7 +4420,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "cs": [ @@ -5194,7 +5208,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "de": [ @@ -5202,12 +5224,6 @@ "addNewFriend", "alreadyHaveAnAccount", "classes", - "emoteKeyboardNoRecents", - "hasKnocked", - "wrongPinEntered", - "pleaseEnterANumber", - "archiveRoomDescription", - "roomUpgradeDescription", "allCorrect", "newWayAllGood", "othersAreBetter", @@ -5951,13 +5967,7 @@ "reportToTeacher", "reportMessageTitle", "reportMessageBody", - "noTeachersFound", - "pushNotificationsNotAvailable", - "learnMore", - "banUserDescription", - "unbanUserDescription", - "kickUserDescription", - "makeAdminDescription" + "noTeachersFound" ], "el": [ @@ -7303,7 +7313,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "eo": [ @@ -8273,7 +8291,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "es": [ @@ -8330,7 +8356,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "et": [ @@ -8338,10 +8372,6 @@ "addNewFriend", "alreadyHaveAnAccount", "classes", - "hasKnocked", - "pleaseEnterANumber", - "archiveRoomDescription", - "roomUpgradeDescription", "allCorrect", "newWayAllGood", "othersAreBetter", @@ -9085,13 +9115,7 @@ "reportToTeacher", "reportMessageTitle", "reportMessageBody", - "noTeachersFound", - "pushNotificationsNotAvailable", - "learnMore", - "banUserDescription", - "unbanUserDescription", - "kickUserDescription", - "makeAdminDescription" + "noTeachersFound" ], "eu": [ @@ -9099,10 +9123,6 @@ "addNewFriend", "alreadyHaveAnAccount", "classes", - "hasKnocked", - "pleaseEnterANumber", - "archiveRoomDescription", - "roomUpgradeDescription", "allCorrect", "newWayAllGood", "othersAreBetter", @@ -9846,13 +9866,7 @@ "reportToTeacher", "reportMessageTitle", "reportMessageBody", - "noTeachersFound", - "pushNotificationsNotAvailable", - "learnMore", - "banUserDescription", - "unbanUserDescription", - "kickUserDescription", - "makeAdminDescription" + "noTeachersFound" ], "fa": [ @@ -10651,56 +10665,22 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "fi": [ - "notAnImage", - "importNow", - "importEmojis", - "importFromZipFile", - "importZipFile", - "exportEmotePack", - "replace", - "savedEmotePack", "accountInformation", "addNewFriend", "alreadyHaveAnAccount", - "sendTypingNotifications", "classes", - "createGroup", - "chatPermissions", - "emoteKeyboardNoRecents", - "chatDescription", - "chatDescriptionHasBeenChanged", - "inviteContactToGroupQuestion", - "noChatDescriptionYet", - "anyoneCanKnock", - "noOneCanJoin", - "tryAgain", - "invalidServerName", - "redactMessageDescription", - "optionalRedactReason", - "messagesStyle", - "shareInviteLink", - "redactedBy", - "directChat", - "redactedByBecause", - "setChatDescription", - "hasKnocked", - "signInWith", - "profileNotFound", - "setTheme", - "setColorTheme", - "invite", - "requests", - "inviteGroupChat", - "invitePrivateChat", - "invalidInput", - "wrongPinEntered", - "pleaseEnterANumber", - "archiveRoomDescription", - "roomUpgradeDescription", "allCorrect", "newWayAllGood", "othersAreBetter", @@ -11450,7 +11430,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "fr": [ @@ -12247,7 +12235,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "ga": [ @@ -13198,7 +13194,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "gl": [ @@ -13206,12 +13210,6 @@ "addNewFriend", "alreadyHaveAnAccount", "classes", - "hasKnocked", - "invalidInput", - "wrongPinEntered", - "pleaseEnterANumber", - "archiveRoomDescription", - "roomUpgradeDescription", "allCorrect", "newWayAllGood", "othersAreBetter", @@ -13956,12 +13954,7 @@ "reportMessageTitle", "reportMessageBody", "noTeachersFound", - "pushNotificationsNotAvailable", - "learnMore", - "banUserDescription", - "unbanUserDescription", - "kickUserDescription", - "makeAdminDescription" + "todosUnencrypted" ], "he": [ @@ -15034,7 +15027,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "hi": [ @@ -16380,7 +16381,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "hr": [ @@ -16388,8 +16397,6 @@ "addNewFriend", "alreadyHaveAnAccount", "classes", - "hasKnocked", - "pleaseEnterANumber", "archiveRoomDescription", "roomUpgradeDescription", "allCorrect", @@ -17141,7 +17148,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "hu": [ @@ -18104,7 +18119,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "id": [ @@ -18865,7 +18888,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "ie": [ @@ -19946,7 +19977,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "it": [ @@ -20925,7 +20964,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "ja": [ @@ -21756,7 +21803,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "ko": [ @@ -22654,7 +22709,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "lt": [ @@ -23486,7 +23549,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "lv": [ @@ -24832,7 +24903,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "nb": [ @@ -25851,7 +25930,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "nl": [ @@ -26612,60 +26699,23 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "pl": [ - "notAnImage", - "importNow", - "importEmojis", - "importFromZipFile", - "importZipFile", - "exportEmotePack", - "replace", - "savedEmotePack", "accountInformation", "addNewFriend", "alreadyHaveAnAccount", - "sendTypingNotifications", "classes", - "createGroup", - "allRooms", - "discover", - "chatPermissions", - "emoteKeyboardNoRecents", - "chatDescription", - "chatDescriptionHasBeenChanged", - "inviteContactToGroupQuestion", - "noChatDescriptionYet", - "anyoneCanKnock", - "noOneCanJoin", - "tryAgain", - "invalidServerName", - "redactMessageDescription", - "optionalRedactReason", - "messagesStyle", - "shareInviteLink", - "redactedBy", - "directChat", - "redactedByBecause", - "setChatDescription", "hasKnocked", - "reportErrorDescription", - "report", - "signInWithPassword", - "continueWith", - "pleaseTryAgainLaterOrChooseDifferentServer", - "signInWith", - "profileNotFound", - "setTheme", - "setColorTheme", - "invite", - "requests", - "inviteGroupChat", - "invitePrivateChat", - "invalidInput", - "wrongPinEntered", "pleaseEnterANumber", "archiveRoomDescription", "roomUpgradeDescription", @@ -27418,7 +27468,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "pt": [ @@ -28742,7 +28800,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "pt_BR": [ @@ -29555,7 +29621,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "pt_PT": [ @@ -30573,7 +30647,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "ro": [ @@ -31372,7 +31454,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "ru": [ @@ -32133,7 +32223,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "sk": [ @@ -33220,7 +33318,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "sl": [ @@ -34455,7 +34561,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "sr": [ @@ -35447,7 +35561,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "sv": [ @@ -36255,7 +36377,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "ta": [ @@ -37598,61 +37728,22 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "th": [ - "passwordsDoNotMatch", - "pleaseEnterValidEmail", - "repeatPassword", - "pleaseChooseAtLeastChars", - "notAnImage", - "remove", - "importNow", - "importEmojis", - "importFromZipFile", - "importZipFile", - "exportEmotePack", - "replace", - "savedEmotePack", - "about", - "updateAvailable", - "updateNow", - "accept", - "acceptedTheInvitation", - "account", "accountInformation", - "activatedEndToEndEncryption", - "addEmail", - "confirmMatrixId", - "supposedMxid", "addGroupDescription", "addNewFriend", - "addToSpace", - "admin", - "alias", - "all", - "allChats", "alreadyHaveAnAccount", - "commandHint_googly", - "commandHint_cuddle", - "commandHint_hug", - "googlyEyesContent", - "cuddleContent", - "hugContent", - "answeredTheCall", - "anyoneCanJoin", - "appLock", - "archive", - "areGuestsAllowedToJoin", - "areYouSure", - "areYouSureYouWantToLogout", - "askSSSSSign", - "askVerificationRequest", - "autoplayImages", - "badServerLoginTypesException", - "sendTypingNotifications", - "sendOnEnter", "badServerVersionsException", "banFromChat", "banned", @@ -37685,11 +37776,9 @@ "changeWallpaper", "changeYourAvatar", "channelCorruptedDecryptError", - "chat", "yourChatBackupHasBeenSetUp", "chatBackup", "chatBackupDescription", - "chatDetails", "chatHasBeenAddedToThisSpace", "chats", "classes", @@ -37729,7 +37818,6 @@ "containsUserName", "contentHasBeenReported", "copiedToClipboard", - "copy", "copyToClipboard", "couldNotDecryptMessage", "countParticipants", @@ -37745,7 +37833,6 @@ "dateWithYear", "deactivateAccountWarning", "defaultPermissionLevel", - "delete", "deleteAccount", "deleteMessage", "deny", @@ -37757,7 +37844,6 @@ "discover", "displaynameHasBeenChanged", "downloadFile", - "edit", "editBlockedServers", "chatPermissions", "editChatPermissions", @@ -37788,7 +37874,6 @@ "everythingReady", "extremeOffensive", "fileName", - "fluffychat", "fontSize", "forward", "fromJoining", @@ -37805,7 +37890,6 @@ "guestsAreForbidden", "guestsCanJoin", "hasWithdrawnTheInvitationFor", - "help", "hideRedactedEvents", "hideUnknownEvents", "howOffensiveIsThisContent", @@ -37920,7 +38004,6 @@ "passwordRecovery", "people", "pickImage", - "pin", "play", "pleaseChoose", "pleaseChooseAPasscode", @@ -37967,7 +38050,6 @@ "seenByUser", "seenByUserAndCountOthers", "seenByUserAndUser", - "send", "sendAMessage", "sendAsText", "sendAudio", @@ -38944,7 +39026,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "tr": [ @@ -39705,7 +39795,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "uk": [ @@ -39713,10 +39811,6 @@ "addNewFriend", "alreadyHaveAnAccount", "classes", - "hasKnocked", - "pleaseEnterANumber", - "archiveRoomDescription", - "roomUpgradeDescription", "allCorrect", "newWayAllGood", "othersAreBetter", @@ -40460,13 +40554,7 @@ "reportToTeacher", "reportMessageTitle", "reportMessageBody", - "noTeachersFound", - "pushNotificationsNotAvailable", - "learnMore", - "banUserDescription", - "unbanUserDescription", - "kickUserDescription", - "makeAdminDescription" + "noTeachersFound" ], "vi": [ @@ -41733,7 +41821,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "zh": [ @@ -41741,8 +41837,6 @@ "addNewFriend", "alreadyHaveAnAccount", "classes", - "hasKnocked", - "pleaseEnterANumber", "archiveRoomDescription", "roomUpgradeDescription", "allCorrect", @@ -42494,7 +42588,15 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ], "zh_Hant": [ @@ -43484,6 +43586,14 @@ "banUserDescription", "unbanUserDescription", "kickUserDescription", - "makeAdminDescription" + "makeAdminDescription", + "removeDevicesDescription", + "todoLists", + "newTodo", + "noTodosYet", + "editTodo", + "pleaseAddATitle", + "todoListChangedError", + "todosUnencrypted" ] } diff --git a/pubspec.lock b/pubspec.lock index b1cfd5f7f..c242ca0aa 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: "direct main" description: name: adaptive_dialog - sha256: "3b8abc7d1ba0834061759ee0be8e623eff5bffbcd1e6df5608a11983cfad6b2b" + sha256: "2861f072027046912caaaec9a688e50e10b712ab11ea18d266ef83c391e5f4d6" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.0+2" analyzer: dependency: transitive description: @@ -74,7 +74,7 @@ packages: source: hosted version: "2.4.2" async: - dependency: transitive + dependency: "direct main" description: name: async sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" @@ -428,10 +428,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: "21145c9c268d54b1f771d8380c195d2d6f655e0567dc1ca2f9c134c02c819e0a" + sha256: be325344c1f3070354a1d84a231a1ba75ea85d413774ec4bdf444c023342e030 url: "https://pub.dev" source: hosted - version: "5.3.3" + version: "5.5.0" file_selector_linux: dependency: transitive description: @@ -694,18 +694,18 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" + sha256: ad76540d21c066228ee3f9d1dad64a9f7e46530e8bb7c85011a88bc1fd874bc5 url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "3.0.0" flutter_local_notifications: dependency: "direct main" description: name: flutter_local_notifications - sha256: "3cc40fe8c50ab8383f3e053a499f00f975636622ecdc8e20a77418ece3b1e975" + sha256: "6d11ea777496061e583623aaf31923f93a9409ef8fcaeeefdd6cd78bf4fe5bb3" url: "https://pub.dev" source: hosted - version: "15.1.0+1" + version: "16.1.0" flutter_local_notifications_linux: dependency: transitive description: @@ -787,10 +787,10 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: "98352186ee7ad3639ccc77ad7924b773ff6883076ab952437d20f18a61f0a7c5" + sha256: "22dbf16f23a4bcf9d35e51be1c84ad5bb6f627750565edd70dab70f3ff5fff8f" url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "8.1.0" flutter_secure_storage_linux: dependency: transitive description: @@ -827,10 +827,10 @@ packages: dependency: transitive description: name: flutter_secure_storage_windows - sha256: fc2910ec9b28d60598216c29ea763b3a96c401f0ce1d13cdf69ccb0e5c93c3ee + sha256: "38f9501c7cb6f38961ef0e1eacacee2b2d4715c63cc83fe56449c4d3d0b47255" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.1" flutter_svg: dependency: "direct main" description: @@ -856,18 +856,18 @@ packages: dependency: "direct main" description: name: flutter_web_auth_2 - sha256: "70e4df72940183b8e269c4163f78dd5bf9102ba3329bfe00c0f2373f30fb32d0" + sha256: "75613aa4d8e43df3de0fc3d93df36ae5b4ba2e94070384c5a9baeda99f5a235f" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "3.0.3" flutter_web_auth_2_platform_interface: dependency: transitive description: name: flutter_web_auth_2_platform_interface - sha256: f6fa7059ff3428c19cd756c02fef8eb0147131c7e64591f9060c90b5ab84f094 + sha256: "9124824cbd21e12680bf58190e27b77f251c897e80ec81cd557ec1fde9aecabf" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "3.0.0" flutter_web_plugins: dependency: transitive description: flutter @@ -898,10 +898,10 @@ packages: dependency: "direct main" description: name: future_loading_dialog - sha256: "6227dddb32ad5c7d233a54668f862acb4beb5a5e0dde072de372347cc0799e63" + sha256: "2718b1a308db452da32ab9bca9ad496ff92b683e217add9e92cf50520f90537e" url: "https://pub.dev" source: hosted - version: "0.2.4" + version: "0.3.0" geolocator: dependency: "direct main" description: @@ -1264,10 +1264,10 @@ packages: dependency: transitive description: name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "3.0.0" list_counter: dependency: transitive description: @@ -1312,10 +1312,10 @@ packages: dependency: transitive description: name: markdown - sha256: "01512006c8429f604eb10f9848717baeaedf99e991d14a50d540d9beff08e5c6" + sha256: acf35edccc0463a9d7384e437c015a3535772e09714cf60e07eeef3a15870dcd url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "7.1.1" matcher: dependency: transitive description: @@ -1336,10 +1336,10 @@ packages: dependency: "direct main" description: name: matrix - sha256: "10389562a4562db6150291b538e025a9a1b7a79998a71d38cb5c78a34ca6b007" + sha256: "1e4bef4923fa1e33124843aa59932739e69a5507178f18313ec1067c046156f3" url: "https://pub.dev" source: hosted - version: "0.22.3" + version: "0.22.6" matrix_api_lite: dependency: transitive description: @@ -1448,10 +1448,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "6ff267fcd9d48cb61c8df74a82680e8b82e940231bb5f68356672fde0397334a" + sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.2.0" package_info_plus_platform_interface: dependency: transitive description: @@ -1536,18 +1536,18 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81" + sha256: "284a66179cabdf942f838543e10413246f06424d960c92ba95c84439154fcac8" url: "https://pub.dev" source: hosted - version: "10.4.3" + version: "11.0.1" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: "2ffaf52a21f64ac9b35fe7369bb9533edbd4f698e5604db8645b1064ff4cf221" + sha256: f9fddd3b46109bd69ff3f9efa5006d2d309b7aec0f3c1c5637a60a2d5659e76e url: "https://pub.dev" source: hosted - version: "10.3.3" + version: "11.1.0" permission_handler_apple: dependency: transitive description: @@ -1560,10 +1560,10 @@ packages: dependency: transitive description: name: permission_handler_platform_interface - sha256: "7c6b1500385dd1d2ca61bb89e2488ca178e274a69144d26bbd65e33eae7c02a9" + sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4" url: "https://pub.dev" source: hosted - version: "3.11.3" + version: "3.12.0" permission_handler_windows: dependency: transitive description: @@ -2349,10 +2349,10 @@ packages: dependency: "direct main" description: name: wakelock_plus - sha256: aac3f3258f01781ec9212df94eecef1eb9ba9350e106728def405baa096ba413 + sha256: f45a6c03aa3f8322e0a9d7f4a0482721c8789cb41d555407367650b8f9c26018 url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.3" wakelock_plus_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 029263b5b..39135ed20 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,9 +7,10 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: - adaptive_dialog: ^1.9.0-x-macos-beta.1 + adaptive_dialog: ^1.9.0+2 animations: ^2.0.7 archive: ^3.3.9 + async: ^2.11.0 badges: ^3.1.1 blurhash_dart: ^1.1.0 callkeep: ^0.3.2 @@ -47,7 +48,7 @@ dependencies: flutter_html: ^3.0.0-beta.2 flutter_html_table: ^3.0.0-beta.2 flutter_linkify: ^6.0.0 - flutter_local_notifications: ^15.1.0+1 + flutter_local_notifications: ^16.1.0 flutter_localizations: sdk: flutter flutter_map: ^4.0.0 @@ -58,9 +59,9 @@ dependencies: flutter_secure_storage: ^8.0.0 flutter_svg: ^2.0.0+1 flutter_typeahead: ^4.3.2 - flutter_web_auth_2: ^2.1.1 + flutter_web_auth_2: ^3.0.3 flutter_webrtc: ^0.9.37 - future_loading_dialog: ^0.2.3 + future_loading_dialog: ^0.3.0 geolocator: ^7.6.2 get_storage: ^2.1.1 go_router: ^12.0.1 @@ -76,7 +77,7 @@ dependencies: language_tool: ^2.1.1 latlong2: ^0.8.1 linkify: ^5.0.0 - matrix: ^0.22.3 + matrix: ^0.22.6 matrix_homeserver_recommendations: ^0.3.0 native_imaging: ^0.1.0 new_version_plus: ^0.0.10 @@ -84,14 +85,14 @@ dependencies: package_info_plus: ^4.0.0 pasteboard: ^0.2.0 path_provider: ^2.0.9 - permission_handler: ^10.0.0 + permission_handler: ^11.0.1 provider: ^6.0.2 punycode: ^1.0.0 purchases_flutter: ^5.6.0 qr_code_scanner: ^1.0.0 qr_flutter: ^4.0.0 receive_sharing_intent: ^1.4.5 - record: ^4.4.4 + record: ^4.4.4 # Upgrade to 5 currently breaks playing on iOS scroll_to_index: ^3.0.1 sentry_flutter: ^7.4.0 share_plus: ^7.0.0 @@ -107,12 +108,12 @@ dependencies: vibration: ^1.7.4-nullsafety.0 video_compress: ^3.1.1 video_player: ^2.2.18 - wakelock_plus: ^1.1.1 + wakelock_plus: ^1.1.3 webrtc_interface: ^1.0.13 dev_dependencies: dart_code_metrics: ^5.7.5 - flutter_lints: ^2.0.1 + flutter_lints: ^3.0.0 flutter_native_splash: ^2.0.3+1 flutter_test: sdk: flutter diff --git a/scripts/enable-android-google-services.patch b/scripts/enable-android-google-services.patch index ab4ec8a6a..10800f419 100644 --- a/scripts/enable-android-google-services.patch +++ b/scripts/enable-android-google-services.patch @@ -123,11 +123,11 @@ index 85aa8647..3b7e09e7 100644 } diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart -index cd79b0ab..c2db0f1e 100644 +index 8e67ae92..da4da5c3 100644 --- a/lib/utils/background_push.dart +++ b/lib/utils/background_push.dart @@ -39,7 +39,7 @@ import '../config/setting_keys.dart'; - import 'famedlysdk_store.dart'; + import '../widgets/matrix.dart'; import 'platform_infos.dart'; -//import 'package:fcm_shared_isolate/fcm_shared_isolate.dart'; @@ -135,7 +135,7 @@ index cd79b0ab..c2db0f1e 100644 class NoTokenException implements Exception { String get cause => 'Cannot get firebase token'; -@@ -65,7 +65,7 @@ class BackgroundPush { +@@ -64,7 +64,7 @@ class BackgroundPush { final pendingTests = >{}; @@ -154,6 +154,6 @@ index 6999d0b8..b2c9144f 100644 emojis: ^0.9.9 - #fcm_shared_isolate: ^0.1.0 + fcm_shared_isolate: ^0.1.0 - file_picker: ^5.3.0 + file_picker: ^6.0.0 flutter: sdk: flutter