merge in newer fluffychat commits
This commit is contained in:
commit
5e30845a09
151 changed files with 2979 additions and 1407 deletions
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
7
.github/ISSUE_TEMPLATE/config.yml
vendored
7
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -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.
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
|
|
@ -1,6 +1,6 @@
|
|||
name: 💡 Feature Request
|
||||
description: Suggest an idea for this project
|
||||
labels: ["Enhancement"]
|
||||
labels: enhancement
|
||||
body:
|
||||
- type: textarea
|
||||
id: feature-description
|
||||
|
|
|
|||
41
.github/ISSUE_TEMPLATE/test_report.md
vendored
Normal file
41
.github/ISSUE_TEMPLATE/test_report.md
vendored
Normal file
|
|
@ -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
|
||||
16
.github/workflows/auto_merge.yaml
vendored
16
.github/workflows/auto_merge.yaml
vendored
|
|
@ -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 }}"
|
||||
26
.github/workflows/issue_pr_management.yaml
vendored
Normal file
26
.github/workflows/issue_pr_management.yaml
vendored
Normal file
|
|
@ -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 }}
|
||||
2
.github/workflows/main_deploy.yaml
vendored
2
.github/workflows/main_deploy.yaml
vendored
|
|
@ -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 ..
|
||||
|
|
|
|||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
.github/workflows/versions.env
vendored
2
.github/workflows/versions.env
vendored
|
|
@ -1,2 +1,2 @@
|
|||
FLUTTER_VERSION=3.13.7
|
||||
FLUTTER_VERSION=3.13.9
|
||||
JAVA_VERSION=17
|
||||
39
CHANGELOG.md
39
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.talktolearn.chat">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
|
|
@ -99,6 +99,11 @@
|
|||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="video/*" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="audio/*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.talktolearn.chat">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
|
|
@ -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": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,250 @@
|
|||
{}
|
||||
{
|
||||
"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": {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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#
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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<void> startGui(List<Client> clients) async {
|
||||
Future<void> startGui(List<Client> 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<void> startGui(List<Client> 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<Client> 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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<Archive> {
|
||||
List<Room>? archive;
|
||||
List<Room> archive = [];
|
||||
|
||||
Future<List<Room>> 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<Archive> {
|
|||
}
|
||||
},
|
||||
);
|
||||
client.clearArchivesFromCache();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<List<Room>>(
|
||||
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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<bool?> show(BuildContext context) => showAdaptiveBottomSheet(
|
||||
context: context,
|
||||
|
|
|
|||
|
|
@ -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<AddWidgetTile> createState() => AddWidgetTileState();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<ChatPageWithRoom> {
|
|||
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<ChatPageWithRoom> {
|
|||
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<ChatPageWithRoom> {
|
|||
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<ChatPageWithRoom> {
|
|||
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<ChatPageWithRoom> {
|
|||
}
|
||||
|
||||
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<ChatPageWithRoom> {
|
|||
}
|
||||
|
||||
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<ChatPageWithRoom> {
|
|||
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<ChatPageWithRoom> {
|
|||
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<ChatPageWithRoom> {
|
|||
if (await Record().hasPermission() == false) return;
|
||||
final result = await showDialog<RecordingResult>(
|
||||
context: context,
|
||||
useRootNavigator: false,
|
||||
barrierDismissible: false,
|
||||
builder: (c) => const RecordingDialog(),
|
||||
);
|
||||
|
|
@ -862,9 +866,8 @@ class ChatController extends State<ChatPageWithRoom> {
|
|||
}
|
||||
|
||||
void sendLocationAction() async {
|
||||
await showDialog(
|
||||
await showAdaptiveDialog(
|
||||
context: context,
|
||||
useRootNavigator: false,
|
||||
builder: (c) => SendLocationDialog(room: room),
|
||||
);
|
||||
}
|
||||
|
|
@ -919,7 +922,6 @@ class ChatController extends State<ChatPageWithRoom> {
|
|||
);
|
||||
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<ChatPageWithRoom> {
|
|||
);
|
||||
}
|
||||
|
||||
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<ChatPageWithRoom> {
|
|||
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<ChatPageWithRoom> {
|
|||
!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<ChatPageWithRoom> {
|
|||
}
|
||||
await scrollController.scrollToIndex(
|
||||
eventIndex,
|
||||
preferPosition: AutoScrollPosition.end,
|
||||
preferPosition: AutoScrollPosition.middle,
|
||||
);
|
||||
_updateScrollController();
|
||||
}
|
||||
|
|
@ -1153,15 +1174,6 @@ class ChatController extends State<ChatPageWithRoom> {
|
|||
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<ChatPageWithRoom> {
|
|||
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<ChatPageWithRoom> {
|
|||
context: context,
|
||||
title: L10n.of(context)!.unavailable,
|
||||
okLabel: L10n.of(context)!.next,
|
||||
useRootNavigator: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
? <Widget>[
|
||||
SizedBox(
|
||||
height: 56,
|
||||
child: TextButton(
|
||||
onPressed: controller.forwardEventsAction,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
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: <Widget>[
|
||||
const Icon(Icons.delete),
|
||||
Text(L10n.of(context)!.delete),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
SizedBox(
|
||||
height: 56,
|
||||
child: TextButton(
|
||||
onPressed: controller.forwardEventsAction,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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<Widget> _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
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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<CuteContent> 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<CuteEventOverlay> 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) {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<User?>(
|
||||
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<User?>(
|
||||
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<User?>(
|
||||
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<User?>(
|
||||
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: <Widget>[
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
if (event.relationshipType ==
|
||||
RelationshipTypes.reply)
|
||||
FutureBuilder<Event?>(
|
||||
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: <Widget>[
|
||||
if (event.relationshipType ==
|
||||
RelationshipTypes.reply)
|
||||
FutureBuilder<Event?>(
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<bool?> 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<bool?> 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -43,8 +43,8 @@ class InputBar extends StatelessWidget {
|
|||
this.autofocus,
|
||||
this.textInputAction,
|
||||
this.readOnly = false,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
List<Map<String, String?>> getSuggestions(String text) {
|
||||
// #Pangea
|
||||
|
|
|
|||
|
|
@ -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<void> _displayPinnedEventsDialog(
|
||||
BuildContext context,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<SendFileDialog> {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
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: <Widget>[
|
||||
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<SendFileDialog> {
|
|||
} else {
|
||||
contentWidget = Text('$fileName ($sizeString)');
|
||||
}
|
||||
return AlertDialog(
|
||||
return AlertDialog.adaptive(
|
||||
title: Text(sendStr),
|
||||
content: contentWidget,
|
||||
actions: <Widget>[
|
||||
|
|
|
|||
|
|
@ -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<SendLocationDialog> {
|
|||
],
|
||||
);
|
||||
}
|
||||
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: [
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<ChatDetails> {
|
|||
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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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() =>
|
||||
|
|
|
|||
|
|
@ -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<ChatList>
|
|||
],
|
||||
);
|
||||
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<ChatList>
|
|||
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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<ChatPermissionsSettings> {
|
|||
okLabel: L10n.of(context)!.yes,
|
||||
cancelLabel: L10n.of(context)!.cancel,
|
||||
title: L10n.of(context)!.areYouSure,
|
||||
message: L10n.of(context)!.roomUpgradeDescription,
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<DevicesSettings> {
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ class HomeserverPicker extends StatefulWidget {
|
|||
|
||||
class HomeserverPickerController extends State<HomeserverPicker> {
|
||||
bool isLoading = false;
|
||||
bool isLoggingIn = false;
|
||||
|
||||
final TextEditingController homeserverController = TextEditingController(
|
||||
text: AppConfig.defaultHomeserver,
|
||||
);
|
||||
|
|
@ -133,7 +135,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
? 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<HomeserverPicker> {
|
|||
List<IdentityProvider>? 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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<void> show(BuildContext context) => showAdaptiveBottomSheet(
|
||||
Future<void> 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<KeyVerificationDialog> {
|
|||
);
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<NewPrivateChat> {
|
|||
}
|
||||
await showAdaptiveBottomSheet(
|
||||
context: context,
|
||||
builder: (_) => const QrScannerModal(),
|
||||
builder: (_) => QrScannerModal(
|
||||
onScan: (link) => UrlLauncher(context, link).openMatrixToUrl(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<QrScannerModal> {
|
|||
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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue