merge inn fluffychat changes

pull/795/head
ggurdin 2 years ago
commit 33b5de0afb

@ -2,7 +2,7 @@
name: 📝 Test
about: A detailed protocol for testing all features
title: 'Test Report'
label: test
labels: test
---
1. App receives push notifications over Firebase Cloud Messaging when it is in background/terminated:

@ -60,6 +60,10 @@ jobs:
run: gem install fastlane -NV
- name: Apply Google Services Patch
run: git apply ./scripts/enable-android-google-services.patch
- name: Remove Emoji Font
run: |
rm -rf fonts/NotoEmoji
yq -i 'del( .flutter.fonts[] | select(.family == "NotoEmoji") )' pubspec.yaml
- run: flutter pub get
- name: Prepare Android Release Build
env:

@ -0,0 +1,28 @@
on:
push:
tags:
- "v*"
- "rc*"
name: Process Tags
jobs:
build:
name: Create Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Get Changelog Entry
id: changelog_reader
uses: mindsers/changelog-reader-action@v2
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body: ${{ steps.changelog_reader.outputs.changes }}
draft: false
prerelease: ${{ startsWith(github.ref, 'rc') }}

@ -57,6 +57,10 @@ jobs:
cache: true
- name: Apply Google Services Patch
run: git apply ./scripts/enable-android-google-services.patch
- name: Remove Emoji Font
run: |
rm -rf fonts/NotoEmoji
yq -i 'del( .flutter.fonts[] | select(.family == "NotoEmoji") )' pubspec.yaml
- run: flutter pub get
- name: Prepare Android Release Build
env:
@ -120,6 +124,10 @@ jobs:
run: gem install fastlane -NV
- name: Apply Google Services Patch
run: git apply ./scripts/enable-android-google-services.patch
- name: Remove Emoji Font
run: |
rm -rf fonts/NotoEmoji
yq -i 'del( .flutter.fonts[] | select(.family == "NotoEmoji") )' pubspec.yaml
- run: flutter pub get
- name: Prepare Android Release Build
env:

@ -1,2 +1,2 @@
FLUTTER_VERSION=3.13.9
FLUTTER_VERSION=3.16.0
JAVA_VERSION=17

@ -1,3 +1,28 @@
## v1.15.1
- feat: Make all text in chat selectable on desktop (krille-chan)
- chore: Add border to images in timeline (krille-chan)
- chore: added android audio sharing intent (Aryan Arora)
- fix: Dockerfile: install jq in the builder image (David Douard)
- fix: Cannot pin messages of other users (Krille)
- fix: Emojipicker flickering because noRecent (krille-chan)
- fix: LoadProfileBottomSheet accessing disposed outerContext (Aryan Arora)
- fix: More stable scroll up to event (krille-chan)
- fix: Properly capitalize Linux window title (kramo)
- fix: Remove failed to sent events (krille-chan)
- fix: Routing glitch when using SSO on desktop (krille-chan)
- fix: SSO with no identity providers (krille-chan)
- refactor: Do not init client in background mode on Android (krille-chan)
- refactor: Store and fix missing persistence of some values (krille-chan)
- Translated using Weblate (Arabic) (Rex_sa)
- Translated using Weblate (Basque) (xabirequejo)
- Translated using Weblate (Chinese (Simplified)) (Eric)
- Translated using Weblate (Czech) (Vojtěch Fošnár)
- Translated using Weblate (Dutch) (Jelv)
- Translated using Weblate (Estonian) (Priit Jõerüüt)
- Translated using Weblate (Finnish) (Aminda Suomalainen)
- Translated using Weblate (German) (Haui)
- Translated using Weblate (Ukrainian) (Ihor Hordiichuk)
## v1.15.0
- feat: Add experimental todo list for rooms (krille-chan)
- feat: better scroll to last read message handling (krille-chan)

@ -1,5 +1,5 @@
FROM ghcr.io/cirruslabs/flutter as builder
RUN sudo apt update && sudo apt install curl -y
RUN sudo apt update && sudo apt install curl jq -y
COPY . /app
WORKDIR /app
RUN ./scripts/prepare-web.sh
@ -8,4 +8,4 @@ RUN flutter build web --dart-define=FLUTTER_WEB_CANVASKIT_URL=canvaskit/ --relea
FROM docker.io/nginx:alpine
RUN rm -rf /usr/share/nginx/html
COPY --from=builder /app/build/web /usr/share/nginx/html
COPY --from=builder /app/build/web /usr/share/nginx/html

@ -66,4 +66,4 @@ Please visit the [Wiki](https://github.com/krille-chan/fluffychat/wiki) for buil
* <a href="https://github.com/madsrh/WoodenBeaver">WoodenBeaver</a> sound theme for the notification sound.
* The Matrix Foundation for making and maintaining the [emoji translations](https://github.com/matrix-org/matrix-doc/blob/main/data-definitions/sas-emoji.json) used for emoji verification, licensed Apache 2.0
* The Matrix Foundation for making and maintaining the [emoji translations](https://github.com/matrix-org/matrix-spec/blob/main/data-definitions/sas-emoji.json) used for emoji verification, licensed Apache 2.0

@ -73,6 +73,13 @@ android {
signingConfig signingConfigs.release
}
}
// https://stackoverflow.com/a/77494454/8222484
packagingOptions {
pickFirst 'lib/x86/libc++_shared.so'
pickFirst 'lib/x86_64/libc++_shared.so'
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
pickFirst 'lib/arm64-v8a/libc++_shared.so'
}
}
flutter {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -2343,7 +2343,7 @@
"@notAnImage": {},
"importNow": "Importovat nyní",
"@importNow": {},
"redactedByBecause": "Redigováno uživatelem {username} s důvodem: \"{reason}\"",
"redactedByBecause": "Smazáno uživatelem {username} s důvodem: \"{reason}\"",
"@redactedByBecause": {
"type": "text",
"placeholders": {
@ -2415,7 +2415,7 @@
"@jumpToLastReadMessage": {},
"signInWithPassword": "Přihlásit se pomocí hesla",
"@signInWithPassword": {},
"redactedBy": "Redigováno uživatelem {username}",
"redactedBy": "Smazáno uživatelem {username}",
"@redactedBy": {
"type": "text",
"placeholders": {
@ -2432,9 +2432,9 @@
"@noOneCanJoin": {},
"tryAgain": "Zkuste to znovu",
"@tryAgain": {},
"redactMessageDescription": "Tato zpráva bude skryta pro všechny účastníky konverzace. Tuto akci nelze vzít zpět.",
"redactMessageDescription": "Tato zpráva bude smazána pro všechny účastníky konverzace. Tuto akci nelze vzít zpět.",
"@redactMessageDescription": {},
"optionalRedactReason": "(Nepovinné) Důvod redakce této zprávy…",
"optionalRedactReason": "(Nepovinné) Důvod smazání této zprávy…",
"@optionalRedactReason": {},
"messagesStyle": "Zprávy:",
"@messagesStyle": {},
@ -2566,5 +2566,96 @@
}
},
"requests": "Žádosti",
"@requests": {}
"@requests": {},
"reportErrorDescription": "",
"@reportErrorDescription": {},
"banUserDescription": "",
"@banUserDescription": {},
"removeDevicesDescription": "",
"@removeDevicesDescription": {},
"unbanUserDescription": "",
"@unbanUserDescription": {},
"todoLists": "",
"@todoLists": {},
"editTodo": "",
"@editTodo": {},
"pushNotificationsNotAvailable": "",
"@pushNotificationsNotAvailable": {},
"pleaseAddATitle": "",
"@pleaseAddATitle": {},
"makeAdminDescription": "",
"@makeAdminDescription": {},
"noTodosYet": "",
"@noTodosYet": {},
"archiveRoomDescription": "",
"@archiveRoomDescription": {},
"savedEmotePack": "",
"@savedEmotePack": {
"type": "text",
"placeholders": {
"path": {}
}
},
"invalidInput": "",
"@invalidInput": {},
"todosUnencrypted": "",
"@todosUnencrypted": {},
"report": "",
"@report": {},
"hasKnocked": "",
"@hasKnocked": {
"placeholders": {
"user": {}
}
},
"wrongPinEntered": "",
"@wrongPinEntered": {
"type": "text",
"placeholders": {
"seconds": {}
}
},
"inviteGroupChat": "",
"@inviteGroupChat": {},
"invitePrivateChat": "",
"@invitePrivateChat": {},
"newTodo": "",
"@newTodo": {},
"learnMore": "",
"@learnMore": {},
"todoListChangedError": "",
"@todoListChangedError": {},
"roomUpgradeDescription": "",
"@roomUpgradeDescription": {},
"pleaseEnterANumber": "",
"@pleaseEnterANumber": {},
"emoteKeyboardNoRecents": "",
"@emoteKeyboardNoRecents": {
"type": "text",
"placeholders": {}
},
"kickUserDescription": "",
"@kickUserDescription": {},
"invite": "",
"@invite": {},
"indexedDbErrorLong": "",
"@indexedDbErrorLong": {},
"callingAccount": "",
"@callingAccount": {},
"enterSpace": "",
"@enterSpace": {},
"noKeyForThisMessage": "",
"@noKeyForThisMessage": {},
"readUpToHere": "",
"@readUpToHere": {},
"appearOnTopDetails": "",
"@appearOnTopDetails": {},
"enterRoom": "",
"@enterRoom": {},
"hideUnimportantStateEvents": "",
"@hideUnimportantStateEvents": {},
"noBackupWarning": "",
"@noBackupWarning": {},
"indexedDbErrorTitle": "",
"@indexedDbErrorTitle": {}
}

File diff suppressed because it is too large Load Diff

@ -3925,5 +3925,17 @@
"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.",
"noAddToSpacePermissions": "You can't add a chat to this space",
"alreadyInSpace": "The chat is already in this space"
"alreadyInSpace": "The chat is already in this space",
"yourGlobalUserIdIs": "Your global user-ID is: ",
"noUsersFoundWithQuery": "Unfortunately no user could be found with \"{query}\". Please check whether you made a typo.",
"@noUsersFoundWithQuery": {
"type": "text",
"placeholders": {
"query": {}
}
},
"searchChatsRooms": "Search for #chats, @users...",
"groupName": "Group name",
"createGroupAndInviteUsers": "Create a group and invite users",
"groupCanBeFoundViaSearch": "Group can be found via search"
}

File diff suppressed because it is too large Load Diff

@ -3618,11 +3618,358 @@
"placeholders": {}
},
"commandHint_create": "Crear un chat grupal vacío\nUse --no-encryption para deshabilitar el cifrado",
"replace": "Reemplazar",
"@replace": {},
"unsupportedAndroidVersionLong": "",
"@unsupportedAndroidVersionLong": {},
"storeSecurlyOnThisDevice": "",
"@storeSecurlyOnThisDevice": {},
"iUnderstand": "",
"@iUnderstand": {},
"encryptThisChat": "",
"@encryptThisChat": {},
"letsStart": "",
"@letsStart": {},
"endToEndEncryption": "",
"@endToEndEncryption": {},
"openChat": "",
"@openChat": {},
"screenSharingDetail": "",
"@screenSharingDetail": {},
"jumpToLastReadMessage": "",
"@jumpToLastReadMessage": {},
"allRooms": "",
"@allRooms": {
"type": "text",
"placeholders": {}
},
"widgetVideo": "",
"@widgetVideo": {},
"dismiss": "",
"@dismiss": {},
"reportErrorDescription": "",
"@reportErrorDescription": {},
"unsupportedAndroidVersion": "",
"@unsupportedAndroidVersion": {},
"noEmailWarning": "",
"@noEmailWarning": {},
"indexedDbErrorLong": "",
"@indexedDbErrorLong": {},
"startFirstChat": "",
"@startFirstChat": {},
"callingAccount": "",
"@callingAccount": {},
"setColorTheme": "",
"@setColorTheme": {},
"commandHint_create": "",
"@commandHint_create": {
"type": "text",
"description": "Usage hint for the command /create"
},
"commandHint_dm": "Empezar un chat privado\nUse --no-encryption para deshabilitar el cifrado",
"user": "",
"@user": {},
"banUserDescription": "",
"@banUserDescription": {},
"requests": "",
"@requests": {},
"addToStory": "",
"@addToStory": {},
"removeDevicesDescription": "",
"@removeDevicesDescription": {},
"separateChatTypes": "",
"@separateChatTypes": {
"type": "text",
"placeholders": {}
},
"tryAgain": "",
"@tryAgain": {},
"youKickedAndBanned": "",
"@youKickedAndBanned": {
"placeholders": {
"user": {}
}
},
"showDirectChatsInSpaces": "",
"@showDirectChatsInSpaces": {
"type": "text",
"placeholders": {}
},
"unbanUserDescription": "",
"@unbanUserDescription": {},
"todoLists": "",
"@todoLists": {},
"messagesStyle": "",
"@messagesStyle": {},
"newSpaceDescription": "",
"@newSpaceDescription": {},
"chatDescription": "",
"@chatDescription": {},
"callingAccountDetails": "",
"@callingAccountDetails": {},
"enterSpace": "",
"@enterSpace": {},
"reopenChat": "",
"@reopenChat": {},
"pleaseEnterRecoveryKey": "",
"@pleaseEnterRecoveryKey": {},
"widgetNameError": "",
"@widgetNameError": {},
"addWidget": "",
"@addWidget": {},
"noKeyForThisMessage": "",
"@noKeyForThisMessage": {},
"editTodo": "",
"@editTodo": {},
"hydrateTor": "",
"@hydrateTor": {},
"pushNotificationsNotAvailable": "",
"@pushNotificationsNotAvailable": {},
"storeInAppleKeyChain": "",
"@storeInAppleKeyChain": {},
"hydrate": "",
"@hydrate": {},
"invalidServerName": "",
"@invalidServerName": {},
"chatPermissions": "",
"@chatPermissions": {},
"sender": "",
"@sender": {},
"storeInAndroidKeystore": "",
"@storeInAndroidKeystore": {},
"signInWithPassword": "",
"@signInWithPassword": {},
"pleaseAddATitle": "",
"@pleaseAddATitle": {},
"makeAdminDescription": "",
"@makeAdminDescription": {},
"saveKeyManuallyDescription": "",
"@saveKeyManuallyDescription": {},
"whyIsThisMessageEncrypted": "",
"@whyIsThisMessageEncrypted": {},
"setChatDescription": "",
"@setChatDescription": {},
"dehydrateWarning": "",
"@dehydrateWarning": {},
"noOtherDevicesFound": "",
"@noOtherDevicesFound": {},
"redactedBy": "",
"@redactedBy": {
"type": "text",
"placeholders": {
"username": {}
}
},
"videoCallsBetaWarning": "",
"@videoCallsBetaWarning": {},
"storyPrivacyWarning": "",
"@storyPrivacyWarning": {},
"matrixWidgets": "",
"@matrixWidgets": {},
"signInWith": "",
"@signInWith": {
"type": "text",
"placeholders": {
"provider": {}
}
},
"fileIsTooBigForServer": "",
"@fileIsTooBigForServer": {},
"noTodosYet": "",
"@noTodosYet": {},
"callingPermissions": "",
"@callingPermissions": {},
"readUpToHere": "",
"@readUpToHere": {},
"unlockOldMessages": "",
"@unlockOldMessages": {},
"numChats": "",
"@numChats": {
"type": "number",
"placeholders": {
"number": {}
}
},
"optionalRedactReason": "",
"@optionalRedactReason": {},
"dehydrate": "",
"@dehydrate": {},
"archiveRoomDescription": "",
"@archiveRoomDescription": {},
"switchToAccount": "",
"@switchToAccount": {
"type": "number",
"placeholders": {
"number": {}
}
},
"experimentalVideoCalls": "",
"@experimentalVideoCalls": {},
"pleaseEnterRecoveryKeyDescription": "",
"@pleaseEnterRecoveryKeyDescription": {},
"inviteContactToGroupQuestion": "",
"@inviteContactToGroupQuestion": {},
"redactedByBecause": "",
"@redactedByBecause": {
"type": "text",
"placeholders": {
"username": {},
"reason": {}
}
},
"youHaveWithdrawnTheInvitationFor": "",
"@youHaveWithdrawnTheInvitationFor": {
"placeholders": {
"user": {}
}
},
"appearOnTopDetails": "",
"@appearOnTopDetails": {},
"enterRoom": "",
"@enterRoom": {},
"confirmEventUnpin": "",
"@confirmEventUnpin": {},
"youInvitedUser": "",
"@youInvitedUser": {
"placeholders": {
"user": {}
}
},
"fileHasBeenSavedAt": "",
"@fileHasBeenSavedAt": {
"type": "text",
"placeholders": {
"path": {}
}
},
"anyoneCanKnock": "",
"@anyoneCanKnock": {},
"redactMessageDescription": "",
"@redactMessageDescription": {},
"recoveryKey": "",
"@recoveryKey": {},
"invalidInput": "",
"@invalidInput": {},
"todosUnencrypted": "",
"@todosUnencrypted": {},
"dehydrateTorLong": "",
"@dehydrateTorLong": {},
"doNotShowAgain": "",
"@doNotShowAgain": {},
"report": "",
"@report": {},
"hideUnimportantStateEvents": "",
"@hideUnimportantStateEvents": {},
"screenSharingTitle": "",
"@screenSharingTitle": {},
"widgetCustom": "",
"@widgetCustom": {},
"whoCanSeeMyStoriesDesc": "",
"@whoCanSeeMyStoriesDesc": {},
"youBannedUser": "",
"@youBannedUser": {
"placeholders": {
"user": {}
}
},
"unsubscribeStories": "",
"@unsubscribeStories": {},
"hasKnocked": "",
"@hasKnocked": {
"placeholders": {
"user": {}
}
},
"openLinkInBrowser": "",
"@openLinkInBrowser": {},
"disableEncryptionWarning": "",
"@disableEncryptionWarning": {},
"directChat": "",
"@directChat": {},
"noOneCanJoin": "",
"@noOneCanJoin": {},
"wrongPinEntered": "",
"@wrongPinEntered": {
"type": "text",
"placeholders": {
"seconds": {}
}
},
"inviteGroupChat": "",
"@inviteGroupChat": {},
"appearOnTop": "",
"@appearOnTop": {},
"invitePrivateChat": "",
"@invitePrivateChat": {},
"foregroundServiceRunning": "",
"@foregroundServiceRunning": {},
"wasDirectChatDisplayName": "",
"@wasDirectChatDisplayName": {
"type": "text",
"placeholders": {
"oldDisplayName": {}
}
},
"noChatDescriptionYet": "",
"@noChatDescriptionYet": {},
"newTodo": "",
"@newTodo": {},
"learnMore": "",
"@learnMore": {},
"chatDescriptionHasBeenChanged": "",
"@chatDescriptionHasBeenChanged": {},
"dehydrateTor": "",
"@dehydrateTor": {},
"todoListChangedError": "",
"@todoListChangedError": {},
"enterInviteLinkOrMatrixId": "",
"@enterInviteLinkOrMatrixId": {},
"roomUpgradeDescription": "",
"@roomUpgradeDescription": {},
"pleaseEnterANumber": "",
"@pleaseEnterANumber": {},
"youKicked": "",
"@youKicked": {
"placeholders": {
"user": {}
}
},
"profileNotFound": "",
"@profileNotFound": {},
"jump": "",
"@jump": {},
"sorryThatsNotPossible": "",
"@sorryThatsNotPossible": {},
"storyFrom": "",
"@storyFrom": {
"type": "text",
"placeholders": {
"date": {},
"body": {}
}
},
"shareInviteLink": "",
"@shareInviteLink": {},
"commandHint_markasdm": "",
"@commandHint_markasdm": {},
"recoveryKeyLost": "",
"@recoveryKeyLost": {},
"deviceKeys": "",
"@deviceKeys": {},
"emoteKeyboardNoRecents": "",
"@emoteKeyboardNoRecents": {
"type": "text",
"placeholders": {}
},
"setTheme": "",
"@setTheme": {},
"youJoinedTheChat": "",
"@youJoinedTheChat": {},
"thisUserHasNotPostedAnythingYet": "",
"@thisUserHasNotPostedAnythingYet": {},
"errorAddingWidget": "",
"@errorAddingWidget": {},
"commandHint_dm": "",
"@commandHint_dm": {
"type": "text",
"description": "Usage hint for the command /dm"
@ -4262,5 +4609,55 @@
"why": "¿Por qué?",
"definition": "Definición",
"exampleSentence": "Ejemplo de frase",
"addToClassTitle": "Añadir intercambio a la clase"
"addToClassTitle": "Añadir intercambio a la clase",
"youUnbannedUser": "",
"@youUnbannedUser": {
"placeholders": {
"user": {}
}
},
"emojis": "",
"@emojis": {},
"pleaseTryAgainLaterOrChooseDifferentServer": "",
"@pleaseTryAgainLaterOrChooseDifferentServer": {},
"createGroup": "",
"@createGroup": {},
"hydrateTorLong": "",
"@hydrateTorLong": {},
"custom": "",
"@custom": {},
"noBackupWarning": "",
"@noBackupWarning": {},
"editWidgets": "",
"@editWidgets": {},
"storeInSecureStorageDescription": "",
"@storeInSecureStorageDescription": {},
"kickUserDescription": "",
"@kickUserDescription": {},
"pinMessage": "",
"@pinMessage": {},
"invite": "",
"@invite": {},
"continueWith": "",
"@continueWith": {},
"indexedDbErrorTitle": "",
"@indexedDbErrorTitle": {},
"googlyEyesContent": "",
"@googlyEyesContent": {
"type": "text",
"placeholders": {
"senderName": {}
}
},
"cuddleContent": "",
"@cuddleContent": {
"type": "text",
"placeholders": {
"senderName": {}
}
},
"commandHint_googly": "",
"@commandHint_googly": {},
"placeCall": "",
"@placeCall": {}
}

@ -746,7 +746,7 @@
"type": "text",
"placeholders": {}
},
"noGoogleServicesWarning": "Dirudienez Firebase Cloud Messaging ez dago erabilgarri zure mugikorrean. Jakinarazpenak jasotzeko MicroG edo Unified Push instalatzea gomendatzen dugu.",
"noGoogleServicesWarning": "Ez dirudi Firebase Cloud Messaging zure mugikorrean erabilgarri dagoenik. Jakinarazpenak jasotzeko ntfy instalatzea gomendatzen dugu. ntfy edo beste Unified Push hornitzaileren batekin, push jakinarazpenak jaso ditzazkezu datuentzako segurua den modu batean. ntfy PlayStore edo F-Droid dendetatik deskarga dezakezu.",
"@noGoogleServicesWarning": {
"type": "text",
"placeholders": {}
@ -2504,7 +2504,7 @@
"@continueWith": {},
"pleaseTryAgainLaterOrChooseDifferentServer": "Saiatu geroago edo aukeratu beste zerbitzari bat.",
"@pleaseTryAgainLaterOrChooseDifferentServer": {},
"signInWith": "Hasi saioa {provider}(e)kin",
"signInWith": "Hasi saioa {provider}(r)ekin",
"@signInWith": {
"type": "text",
"placeholders": {

@ -2502,5 +2502,152 @@
"continueWith": "ادامه دادن با:",
"@continueWith": {},
"pleaseTryAgainLaterOrChooseDifferentServer": "لطفا بعدا تلاش کنید یا سرور دیگری انتخاب کنید.",
"@pleaseTryAgainLaterOrChooseDifferentServer": {}
"@pleaseTryAgainLaterOrChooseDifferentServer": {},
"setColorTheme": "",
"@setColorTheme": {},
"banUserDescription": "",
"@banUserDescription": {},
"requests": "",
"@requests": {},
"removeDevicesDescription": "",
"@removeDevicesDescription": {},
"tryAgain": "",
"@tryAgain": {},
"unbanUserDescription": "",
"@unbanUserDescription": {},
"todoLists": "",
"@todoLists": {},
"messagesStyle": "",
"@messagesStyle": {},
"chatDescription": "",
"@chatDescription": {},
"editTodo": "",
"@editTodo": {},
"pushNotificationsNotAvailable": "",
"@pushNotificationsNotAvailable": {},
"invalidServerName": "",
"@invalidServerName": {},
"chatPermissions": "",
"@chatPermissions": {},
"pleaseAddATitle": "",
"@pleaseAddATitle": {},
"makeAdminDescription": "",
"@makeAdminDescription": {},
"setChatDescription": "",
"@setChatDescription": {},
"importFromZipFile": "",
"@importFromZipFile": {},
"redactedBy": "",
"@redactedBy": {
"type": "text",
"placeholders": {
"username": {}
}
},
"signInWith": "",
"@signInWith": {
"type": "text",
"placeholders": {
"provider": {}
}
},
"noTodosYet": "",
"@noTodosYet": {},
"optionalRedactReason": "",
"@optionalRedactReason": {},
"archiveRoomDescription": "",
"@archiveRoomDescription": {},
"exportEmotePack": "",
"@exportEmotePack": {},
"savedEmotePack": "",
"@savedEmotePack": {
"type": "text",
"placeholders": {
"path": {}
}
},
"inviteContactToGroupQuestion": "",
"@inviteContactToGroupQuestion": {},
"redactedByBecause": "",
"@redactedByBecause": {
"type": "text",
"placeholders": {
"username": {},
"reason": {}
}
},
"importZipFile": "",
"@importZipFile": {},
"anyoneCanKnock": "",
"@anyoneCanKnock": {},
"redactMessageDescription": "",
"@redactMessageDescription": {},
"invalidInput": "",
"@invalidInput": {},
"todosUnencrypted": "",
"@todosUnencrypted": {},
"addChatDescription": "",
"@addChatDescription": {},
"hasKnocked": "",
"@hasKnocked": {
"placeholders": {
"user": {}
}
},
"directChat": "",
"@directChat": {},
"noOneCanJoin": "",
"@noOneCanJoin": {},
"wrongPinEntered": "",
"@wrongPinEntered": {
"type": "text",
"placeholders": {
"seconds": {}
}
},
"sendTypingNotifications": "",
"@sendTypingNotifications": {},
"inviteGroupChat": "",
"@inviteGroupChat": {},
"invitePrivateChat": "",
"@invitePrivateChat": {},
"importEmojis": "",
"@importEmojis": {},
"noChatDescriptionYet": "",
"@noChatDescriptionYet": {},
"newTodo": "",
"@newTodo": {},
"learnMore": "",
"@learnMore": {},
"notAnImage": "",
"@notAnImage": {},
"chatDescriptionHasBeenChanged": "",
"@chatDescriptionHasBeenChanged": {},
"todoListChangedError": "",
"@todoListChangedError": {},
"roomUpgradeDescription": "",
"@roomUpgradeDescription": {},
"pleaseEnterANumber": "",
"@pleaseEnterANumber": {},
"profileNotFound": "",
"@profileNotFound": {},
"shareInviteLink": "",
"@shareInviteLink": {},
"emoteKeyboardNoRecents": "",
"@emoteKeyboardNoRecents": {
"type": "text",
"placeholders": {}
},
"setTheme": "",
"@setTheme": {},
"replace": "",
"@replace": {},
"createGroup": "",
"@createGroup": {},
"kickUserDescription": "",
"@kickUserDescription": {},
"importNow": "",
"@importNow": {},
"invite": "",
"@invite": {}
}

@ -2620,5 +2620,33 @@
"importNow": "Tuo nyt",
"@importNow": {},
"invite": "Kutsu",
"@invite": {}
"@invite": {},
"banUserDescription": "",
"@banUserDescription": {},
"removeDevicesDescription": "",
"@removeDevicesDescription": {},
"unbanUserDescription": "",
"@unbanUserDescription": {},
"todoLists": "",
"@todoLists": {},
"editTodo": "",
"@editTodo": {},
"pushNotificationsNotAvailable": "",
"@pushNotificationsNotAvailable": {},
"pleaseAddATitle": "",
"@pleaseAddATitle": {},
"makeAdminDescription": "",
"@makeAdminDescription": {},
"noTodosYet": "",
"@noTodosYet": {},
"todosUnencrypted": "",
"@todosUnencrypted": {},
"newTodo": "",
"@newTodo": {},
"learnMore": "",
"@learnMore": {},
"todoListChangedError": "",
"@todoListChangedError": {},
"kickUserDescription": "",
"@kickUserDescription": {}
}

@ -2514,5 +2514,148 @@
"createGroup": "Créer un groupe",
"@createGroup": {},
"importNow": "Importer maintenant",
"@importNow": {}
"@importNow": {},
"reportErrorDescription": "",
"@reportErrorDescription": {},
"setColorTheme": "",
"@setColorTheme": {},
"banUserDescription": "",
"@banUserDescription": {},
"requests": "",
"@requests": {},
"removeDevicesDescription": "",
"@removeDevicesDescription": {},
"tryAgain": "",
"@tryAgain": {},
"unbanUserDescription": "",
"@unbanUserDescription": {},
"todoLists": "",
"@todoLists": {},
"messagesStyle": "",
"@messagesStyle": {},
"chatDescription": "",
"@chatDescription": {},
"editTodo": "",
"@editTodo": {},
"pushNotificationsNotAvailable": "",
"@pushNotificationsNotAvailable": {},
"invalidServerName": "",
"@invalidServerName": {},
"signInWithPassword": "",
"@signInWithPassword": {},
"pleaseAddATitle": "",
"@pleaseAddATitle": {},
"makeAdminDescription": "",
"@makeAdminDescription": {},
"setChatDescription": "",
"@setChatDescription": {},
"redactedBy": "",
"@redactedBy": {
"type": "text",
"placeholders": {
"username": {}
}
},
"signInWith": "",
"@signInWith": {
"type": "text",
"placeholders": {
"provider": {}
}
},
"noTodosYet": "",
"@noTodosYet": {},
"optionalRedactReason": "",
"@optionalRedactReason": {},
"archiveRoomDescription": "",
"@archiveRoomDescription": {},
"exportEmotePack": "",
"@exportEmotePack": {},
"savedEmotePack": "",
"@savedEmotePack": {
"type": "text",
"placeholders": {
"path": {}
}
},
"redactedByBecause": "",
"@redactedByBecause": {
"type": "text",
"placeholders": {
"username": {},
"reason": {}
}
},
"anyoneCanKnock": "",
"@anyoneCanKnock": {},
"redactMessageDescription": "",
"@redactMessageDescription": {},
"invalidInput": "",
"@invalidInput": {},
"todosUnencrypted": "",
"@todosUnencrypted": {},
"report": "",
"@report": {},
"addChatDescription": "",
"@addChatDescription": {},
"hasKnocked": "",
"@hasKnocked": {
"placeholders": {
"user": {}
}
},
"directChat": "",
"@directChat": {},
"noOneCanJoin": "",
"@noOneCanJoin": {},
"wrongPinEntered": "",
"@wrongPinEntered": {
"type": "text",
"placeholders": {
"seconds": {}
}
},
"sendTypingNotifications": "",
"@sendTypingNotifications": {},
"inviteGroupChat": "",
"@inviteGroupChat": {},
"invitePrivateChat": "",
"@invitePrivateChat": {},
"noChatDescriptionYet": "",
"@noChatDescriptionYet": {},
"newTodo": "",
"@newTodo": {},
"learnMore": "",
"@learnMore": {},
"todoListChangedError": "",
"@todoListChangedError": {},
"roomUpgradeDescription": "",
"@roomUpgradeDescription": {},
"pleaseEnterANumber": "",
"@pleaseEnterANumber": {},
"profileNotFound": "",
"@profileNotFound": {},
"jump": "",
"@jump": {},
"shareInviteLink": "",
"@shareInviteLink": {},
"emoteKeyboardNoRecents": "",
"@emoteKeyboardNoRecents": {
"type": "text",
"placeholders": {}
},
"setTheme": "",
"@setTheme": {},
"replace": "",
"@replace": {},
"pleaseTryAgainLaterOrChooseDifferentServer": "",
"@pleaseTryAgainLaterOrChooseDifferentServer": {},
"kickUserDescription": "",
"@kickUserDescription": {},
"invite": "",
"@invite": {},
"continueWith": "",
"@continueWith": {},
"openLinkInBrowser": "",
"@openLinkInBrowser": {}
}

File diff suppressed because it is too large Load Diff

@ -2657,5 +2657,7 @@
"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": {}
"@kickUserDescription": {},
"todosUnencrypted": "",
"@todosUnencrypted": {}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -2625,5 +2625,37 @@
}
},
"pleaseEnterANumber": "Upiši broj veći od 0",
"@pleaseEnterANumber": {}
"@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": "",
"@roomUpgradeDescription": {},
"kickUserDescription": "",
"@kickUserDescription": {}
}

@ -2004,5 +2004,651 @@
"placeholders": {}
},
"notAnImage": "Nem kép fájl.",
"@notAnImage": {}
"@notAnImage": {},
"showPassword": "",
"@showPassword": {
"type": "text",
"placeholders": {}
},
"hugContent": "",
"@hugContent": {
"type": "text",
"placeholders": {
"senderName": {}
}
},
"jumpToLastReadMessage": "",
"@jumpToLastReadMessage": {},
"allRooms": "",
"@allRooms": {
"type": "text",
"placeholders": {}
},
"whoCanSeeMyStories": "",
"@whoCanSeeMyStories": {},
"widgetVideo": "",
"@widgetVideo": {},
"dismiss": "",
"@dismiss": {},
"reportErrorDescription": "",
"@reportErrorDescription": {},
"setPermissionsLevel": "",
"@setPermissionsLevel": {
"type": "text",
"placeholders": {}
},
"removeYourAvatar": "",
"@removeYourAvatar": {
"type": "text",
"placeholders": {}
},
"unsupportedAndroidVersion": "",
"@unsupportedAndroidVersion": {},
"widgetJitsi": "",
"@widgetJitsi": {},
"messageType": "",
"@messageType": {},
"noEmailWarning": "",
"@noEmailWarning": {},
"indexedDbErrorLong": "",
"@indexedDbErrorLong": {},
"oneClientLoggedOut": "",
"@oneClientLoggedOut": {},
"toggleMuted": "",
"@toggleMuted": {
"type": "text",
"placeholders": {}
},
"startFirstChat": "",
"@startFirstChat": {},
"callingAccount": "",
"@callingAccount": {},
"setColorTheme": "",
"@setColorTheme": {},
"nextAccount": "",
"@nextAccount": {},
"singlesignon": "",
"@singlesignon": {
"type": "text",
"placeholders": {}
},
"allSpaces": "",
"@allSpaces": {},
"supposedMxid": "",
"@supposedMxid": {
"type": "text",
"placeholders": {
"mxid": {}
}
},
"user": "",
"@user": {},
"youAcceptedTheInvitation": "",
"@youAcceptedTheInvitation": {},
"youInvitedBy": "",
"@youInvitedBy": {
"placeholders": {
"user": {}
}
},
"banUserDescription": "",
"@banUserDescription": {},
"requests": "",
"@requests": {},
"widgetEtherpad": "",
"@widgetEtherpad": {},
"stories": "",
"@stories": {},
"addToStory": "",
"@addToStory": {},
"removeDevicesDescription": "",
"@removeDevicesDescription": {},
"separateChatTypes": "",
"@separateChatTypes": {
"type": "text",
"placeholders": {}
},
"tryAgain": "",
"@tryAgain": {},
"youKickedAndBanned": "",
"@youKickedAndBanned": {
"placeholders": {
"user": {}
}
},
"showDirectChatsInSpaces": "",
"@showDirectChatsInSpaces": {
"type": "text",
"placeholders": {}
},
"unbanUserDescription": "",
"@unbanUserDescription": {},
"todoLists": "",
"@todoLists": {},
"pleaseClickOnLink": "",
"@pleaseClickOnLink": {
"type": "text",
"placeholders": {}
},
"youRejectedTheInvitation": "",
"@youRejectedTheInvitation": {},
"otherCallingPermissions": "",
"@otherCallingPermissions": {},
"messagesStyle": "",
"@messagesStyle": {},
"widgetUrlError": "",
"@widgetUrlError": {},
"emailOrUsername": "",
"@emailOrUsername": {},
"newSpaceDescription": "",
"@newSpaceDescription": {},
"chatDescription": "",
"@chatDescription": {},
"callingAccountDetails": "",
"@callingAccountDetails": {},
"pleaseFollowInstructionsOnWeb": "",
"@pleaseFollowInstructionsOnWeb": {
"type": "text",
"placeholders": {}
},
"enterSpace": "",
"@enterSpace": {},
"encryptThisChat": "",
"@encryptThisChat": {},
"previousAccount": "",
"@previousAccount": {},
"reopenChat": "",
"@reopenChat": {},
"pleaseEnterRecoveryKey": "",
"@pleaseEnterRecoveryKey": {},
"toggleFavorite": "",
"@toggleFavorite": {
"type": "text",
"placeholders": {}
},
"widgetNameError": "",
"@widgetNameError": {},
"addToBundle": "",
"@addToBundle": {},
"spaceIsPublic": "",
"@spaceIsPublic": {
"type": "text",
"placeholders": {}
},
"addWidget": "",
"@addWidget": {},
"countFiles": "",
"@countFiles": {
"placeholders": {
"count": {}
}
},
"noKeyForThisMessage": "",
"@noKeyForThisMessage": {},
"shareLocation": "",
"@shareLocation": {
"type": "text",
"placeholders": {}
},
"editTodo": "",
"@editTodo": {},
"pushNotificationsNotAvailable": "",
"@pushNotificationsNotAvailable": {},
"storeInAppleKeyChain": "",
"@storeInAppleKeyChain": {},
"replaceRoomWithNewerVersion": "",
"@replaceRoomWithNewerVersion": {
"type": "text",
"placeholders": {}
},
"invalidServerName": "",
"@invalidServerName": {},
"chatPermissions": "",
"@chatPermissions": {},
"wipeChatBackup": "",
"@wipeChatBackup": {
"type": "text",
"placeholders": {}
},
"sender": "",
"@sender": {},
"storeInAndroidKeystore": "",
"@storeInAndroidKeystore": {},
"signInWithPassword": "",
"@signInWithPassword": {},
"pleaseAddATitle": "",
"@pleaseAddATitle": {},
"makeAdminDescription": "",
"@makeAdminDescription": {},
"synchronizingPleaseWait": "",
"@synchronizingPleaseWait": {
"type": "text",
"placeholders": {}
},
"transferFromAnotherDevice": "",
"@transferFromAnotherDevice": {
"type": "text",
"placeholders": {}
},
"pushRules": "",
"@pushRules": {
"type": "text",
"placeholders": {}
},
"saveKeyManuallyDescription": "",
"@saveKeyManuallyDescription": {},
"editBundlesForAccount": "",
"@editBundlesForAccount": {},
"whyIsThisMessageEncrypted": "",
"@whyIsThisMessageEncrypted": {},
"setChatDescription": "",
"@setChatDescription": {},
"spaceName": "",
"@spaceName": {
"type": "text",
"placeholders": {}
},
"importFromZipFile": "",
"@importFromZipFile": {},
"toggleUnread": "",
"@toggleUnread": {
"type": "text",
"placeholders": {}
},
"noOtherDevicesFound": "",
"@noOtherDevicesFound": {},
"addDescription": "",
"@addDescription": {},
"redactedBy": "",
"@redactedBy": {
"type": "text",
"placeholders": {
"username": {}
}
},
"videoCallsBetaWarning": "",
"@videoCallsBetaWarning": {},
"storyPrivacyWarning": "",
"@storyPrivacyWarning": {},
"matrixWidgets": "",
"@matrixWidgets": {},
"signInWith": "",
"@signInWith": {
"type": "text",
"placeholders": {
"provider": {}
}
},
"fileIsTooBigForServer": "",
"@fileIsTooBigForServer": {},
"noTodosYet": "",
"@noTodosYet": {},
"verified": "",
"@verified": {
"type": "text",
"placeholders": {}
},
"callingPermissions": "",
"@callingPermissions": {},
"readUpToHere": "",
"@readUpToHere": {},
"start": "",
"@start": {},
"unlockOldMessages": "",
"@unlockOldMessages": {},
"numChats": "",
"@numChats": {
"type": "number",
"placeholders": {
"number": {}
}
},
"whatIsGoingOn": "",
"@whatIsGoingOn": {},
"optionalRedactReason": "",
"@optionalRedactReason": {},
"sendAsText": "",
"@sendAsText": {
"type": "text"
},
"archiveRoomDescription": "",
"@archiveRoomDescription": {},
"exportEmotePack": "",
"@exportEmotePack": {},
"switchToAccount": "",
"@switchToAccount": {
"type": "number",
"placeholders": {
"number": {}
}
},
"setAsCanonicalAlias": "",
"@setAsCanonicalAlias": {
"type": "text",
"placeholders": {}
},
"whyDoYouWantToReportThis": "",
"@whyDoYouWantToReportThis": {
"type": "text",
"placeholders": {}
},
"letsStart": "",
"@letsStart": {},
"experimentalVideoCalls": "",
"@experimentalVideoCalls": {},
"savedEmotePack": "",
"@savedEmotePack": {
"type": "text",
"placeholders": {
"path": {}
}
},
"pleaseEnterRecoveryKeyDescription": "",
"@pleaseEnterRecoveryKeyDescription": {},
"inviteContactToGroupQuestion": "",
"@inviteContactToGroupQuestion": {},
"redactedByBecause": "",
"@redactedByBecause": {
"type": "text",
"placeholders": {
"username": {},
"reason": {}
}
},
"youHaveWithdrawnTheInvitationFor": "",
"@youHaveWithdrawnTheInvitationFor": {
"placeholders": {
"user": {}
}
},
"appearOnTopDetails": "",
"@appearOnTopDetails": {},
"enterRoom": "",
"@enterRoom": {},
"pleaseChooseAPasscode": "",
"@pleaseChooseAPasscode": {
"type": "text",
"placeholders": {}
},
"reportUser": "",
"@reportUser": {},
"confirmEventUnpin": "",
"@confirmEventUnpin": {},
"youInvitedUser": "",
"@youInvitedUser": {
"placeholders": {
"user": {}
}
},
"fileHasBeenSavedAt": "",
"@fileHasBeenSavedAt": {
"type": "text",
"placeholders": {
"path": {}
}
},
"importZipFile": "",
"@importZipFile": {},
"anyoneCanKnock": "",
"@anyoneCanKnock": {},
"redactMessageDescription": "",
"@redactMessageDescription": {},
"recoveryKey": "",
"@recoveryKey": {},
"invalidInput": "",
"@invalidInput": {},
"todosUnencrypted": "",
"@todosUnencrypted": {},
"yourPublicKey": "",
"@yourPublicKey": {
"type": "text",
"placeholders": {}
},
"tooManyRequestsWarning": "",
"@tooManyRequestsWarning": {
"type": "text",
"placeholders": {}
},
"replyHasBeenSent": "",
"@replyHasBeenSent": {},
"doNotShowAgain": "",
"@doNotShowAgain": {},
"report": "",
"@report": {},
"status": "",
"@status": {
"type": "text",
"placeholders": {}
},
"yourStory": "",
"@yourStory": {},
"unverified": "",
"@unverified": {},
"serverRequiresEmail": "",
"@serverRequiresEmail": {},
"hideUnimportantStateEvents": "",
"@hideUnimportantStateEvents": {},
"screenSharingTitle": "",
"@screenSharingTitle": {},
"widgetCustom": "",
"@widgetCustom": {},
"addToSpaceDescription": "",
"@addToSpaceDescription": {},
"whoCanSeeMyStoriesDesc": "",
"@whoCanSeeMyStoriesDesc": {},
"youBannedUser": "",
"@youBannedUser": {
"placeholders": {
"user": {}
}
},
"unsubscribeStories": "",
"@unsubscribeStories": {},
"addChatDescription": "",
"@addChatDescription": {},
"hasKnocked": "",
"@hasKnocked": {
"placeholders": {
"user": {}
}
},
"publish": "",
"@publish": {},
"openLinkInBrowser": "",
"@openLinkInBrowser": {},
"messageInfo": "",
"@messageInfo": {},
"disableEncryptionWarning": "",
"@disableEncryptionWarning": {},
"directChat": "",
"@directChat": {},
"noOneCanJoin": "",
"@noOneCanJoin": {},
"wrongPinEntered": "",
"@wrongPinEntered": {
"type": "text",
"placeholders": {
"seconds": {}
}
},
"sendTypingNotifications": "",
"@sendTypingNotifications": {},
"inviteGroupChat": "",
"@inviteGroupChat": {},
"appearOnTop": "",
"@appearOnTop": {},
"invitePrivateChat": "",
"@invitePrivateChat": {},
"foregroundServiceRunning": "",
"@foregroundServiceRunning": {},
"voiceCall": "",
"@voiceCall": {},
"importEmojis": "",
"@importEmojis": {},
"wasDirectChatDisplayName": "",
"@wasDirectChatDisplayName": {
"type": "text",
"placeholders": {
"oldDisplayName": {}
}
},
"noChatDescriptionYet": "",
"@noChatDescriptionYet": {},
"newTodo": "",
"@newTodo": {},
"removeFromBundle": "",
"@removeFromBundle": {},
"whoCanPerformWhichAction": "",
"@whoCanPerformWhichAction": {
"type": "text",
"placeholders": {}
},
"learnMore": "",
"@learnMore": {},
"users": "",
"@users": {},
"openGallery": "",
"@openGallery": {},
"chatDescriptionHasBeenChanged": "",
"@chatDescriptionHasBeenChanged": {},
"newGroup": "",
"@newGroup": {},
"bundleName": "",
"@bundleName": {},
"todoListChangedError": "",
"@todoListChangedError": {},
"removeFromSpace": "",
"@removeFromSpace": {},
"enterInviteLinkOrMatrixId": "",
"@enterInviteLinkOrMatrixId": {},
"roomUpgradeDescription": "",
"@roomUpgradeDescription": {},
"pleaseEnterANumber": "",
"@pleaseEnterANumber": {},
"youKicked": "",
"@youKicked": {
"placeholders": {
"user": {}
}
},
"profileNotFound": "",
"@profileNotFound": {},
"jump": "",
"@jump": {},
"reactedWith": "",
"@reactedWith": {
"type": "text",
"placeholders": {
"sender": {},
"reaction": {}
}
},
"sorryThatsNotPossible": "",
"@sorryThatsNotPossible": {},
"storyFrom": "",
"@storyFrom": {
"type": "text",
"placeholders": {
"date": {},
"body": {}
}
},
"videoWithSize": "",
"@videoWithSize": {
"type": "text",
"placeholders": {
"size": {}
}
},
"shareInviteLink": "",
"@shareInviteLink": {},
"commandHint_markasdm": "",
"@commandHint_markasdm": {},
"recoveryKeyLost": "",
"@recoveryKeyLost": {},
"deviceKeys": "",
"@deviceKeys": {},
"emoteKeyboardNoRecents": "",
"@emoteKeyboardNoRecents": {
"type": "text",
"placeholders": {}
},
"setCustomEmotes": "",
"@setCustomEmotes": {
"type": "text",
"placeholders": {}
},
"endToEndEncryption": "",
"@endToEndEncryption": {},
"setTheme": "",
"@setTheme": {},
"youJoinedTheChat": "",
"@youJoinedTheChat": {},
"thisUserHasNotPostedAnythingYet": "",
"@thisUserHasNotPostedAnythingYet": {},
"markAsRead": "",
"@markAsRead": {},
"widgetName": "",
"@widgetName": {},
"errorAddingWidget": "",
"@errorAddingWidget": {},
"replace": "",
"@replace": {},
"youUnbannedUser": "",
"@youUnbannedUser": {
"placeholders": {
"user": {}
}
},
"newSpace": "",
"@newSpace": {},
"emojis": "",
"@emojis": {},
"pleaseTryAgainLaterOrChooseDifferentServer": "",
"@pleaseTryAgainLaterOrChooseDifferentServer": {},
"createGroup": "",
"@createGroup": {},
"hydrateTorLong": "",
"@hydrateTorLong": {},
"time": "",
"@time": {},
"custom": "",
"@custom": {},
"noBackupWarning": "",
"@noBackupWarning": {},
"editWidgets": "",
"@editWidgets": {},
"storeInSecureStorageDescription": "",
"@storeInSecureStorageDescription": {},
"openChat": "",
"@openChat": {},
"kickUserDescription": "",
"@kickUserDescription": {},
"importNow": "",
"@importNow": {},
"pinMessage": "",
"@pinMessage": {},
"invite": "",
"@invite": {},
"enableMultiAccounts": "",
"@enableMultiAccounts": {},
"continueWith": "",
"@continueWith": {},
"indexedDbErrorTitle": "",
"@indexedDbErrorTitle": {},
"discover": "",
"@discover": {
"type": "text",
"placeholders": {}
},
"unsupportedAndroidVersionLong": "",
"@unsupportedAndroidVersionLong": {},
"storeSecurlyOnThisDevice": "",
"@storeSecurlyOnThisDevice": {},
"iUnderstand": "",
"@iUnderstand": {},
"screenSharingDetail": "",
"@screenSharingDetail": {},
"placeCall": "",
"@placeCall": {}
}

@ -2616,5 +2616,45 @@
"placeholders": {
"seconds": {}
}
}
},
"banUserDescription": "",
"@banUserDescription": {},
"removeDevicesDescription": "",
"@removeDevicesDescription": {},
"unbanUserDescription": "",
"@unbanUserDescription": {},
"todoLists": "",
"@todoLists": {},
"editTodo": "",
"@editTodo": {},
"pushNotificationsNotAvailable": "",
"@pushNotificationsNotAvailable": {},
"pleaseAddATitle": "",
"@pleaseAddATitle": {},
"makeAdminDescription": "",
"@makeAdminDescription": {},
"noTodosYet": "",
"@noTodosYet": {},
"archiveRoomDescription": "",
"@archiveRoomDescription": {},
"todosUnencrypted": "",
"@todosUnencrypted": {},
"hasKnocked": "",
"@hasKnocked": {
"placeholders": {
"user": {}
}
},
"newTodo": "",
"@newTodo": {},
"learnMore": "",
"@learnMore": {},
"todoListChangedError": "",
"@todoListChangedError": {},
"roomUpgradeDescription": "",
"@roomUpgradeDescription": {},
"pleaseEnterANumber": "",
"@pleaseEnterANumber": {},
"kickUserDescription": "",
"@kickUserDescription": {}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -2395,5 +2395,260 @@
}
},
"signInWithPassword": "パスワードでログイン",
"@signInWithPassword": {}
"@signInWithPassword": {},
"discover": "",
"@discover": {
"type": "text",
"placeholders": {}
},
"hugContent": "",
"@hugContent": {
"type": "text",
"placeholders": {
"senderName": {}
}
},
"jumpToLastReadMessage": "",
"@jumpToLastReadMessage": {},
"allRooms": "",
"@allRooms": {
"type": "text",
"placeholders": {}
},
"whoCanSeeMyStories": "",
"@whoCanSeeMyStories": {},
"commandHint_cuddle": "",
"@commandHint_cuddle": {},
"dismiss": "",
"@dismiss": {},
"reportErrorDescription": "",
"@reportErrorDescription": {},
"indexedDbErrorLong": "",
"@indexedDbErrorLong": {},
"setColorTheme": "",
"@setColorTheme": {},
"supposedMxid": "",
"@supposedMxid": {
"type": "text",
"placeholders": {
"mxid": {}
}
},
"banUserDescription": "",
"@banUserDescription": {},
"requests": "",
"@requests": {},
"widgetEtherpad": "",
"@widgetEtherpad": {},
"removeDevicesDescription": "",
"@removeDevicesDescription": {},
"separateChatTypes": "",
"@separateChatTypes": {
"type": "text",
"placeholders": {}
},
"tryAgain": "",
"@tryAgain": {},
"unbanUserDescription": "",
"@unbanUserDescription": {},
"todoLists": "",
"@todoLists": {},
"messagesStyle": "",
"@messagesStyle": {},
"newSpaceDescription": "",
"@newSpaceDescription": {},
"chatDescription": "",
"@chatDescription": {},
"callingAccountDetails": "",
"@callingAccountDetails": {},
"noKeyForThisMessage": "",
"@noKeyForThisMessage": {},
"editTodo": "",
"@editTodo": {},
"pushNotificationsNotAvailable": "",
"@pushNotificationsNotAvailable": {},
"invalidServerName": "",
"@invalidServerName": {},
"chatPermissions": "",
"@chatPermissions": {},
"pleaseAddATitle": "",
"@pleaseAddATitle": {},
"makeAdminDescription": "",
"@makeAdminDescription": {},
"saveKeyManuallyDescription": "",
"@saveKeyManuallyDescription": {},
"setChatDescription": "",
"@setChatDescription": {},
"importFromZipFile": "",
"@importFromZipFile": {},
"redactedBy": "",
"@redactedBy": {
"type": "text",
"placeholders": {
"username": {}
}
},
"fileIsTooBigForServer": "",
"@fileIsTooBigForServer": {},
"noTodosYet": "",
"@noTodosYet": {},
"readUpToHere": "",
"@readUpToHere": {},
"whatIsGoingOn": "",
"@whatIsGoingOn": {},
"optionalRedactReason": "",
"@optionalRedactReason": {},
"archiveRoomDescription": "",
"@archiveRoomDescription": {},
"exportEmotePack": "",
"@exportEmotePack": {},
"savedEmotePack": "",
"@savedEmotePack": {
"type": "text",
"placeholders": {
"path": {}
}
},
"openInMaps": "",
"@openInMaps": {
"type": "text",
"placeholders": {}
},
"inviteContactToGroupQuestion": "",
"@inviteContactToGroupQuestion": {},
"redactedByBecause": "",
"@redactedByBecause": {
"type": "text",
"placeholders": {
"username": {},
"reason": {}
}
},
"fileHasBeenSavedAt": "",
"@fileHasBeenSavedAt": {
"type": "text",
"placeholders": {
"path": {}
}
},
"importZipFile": "",
"@importZipFile": {},
"anyoneCanKnock": "",
"@anyoneCanKnock": {},
"redactMessageDescription": "",
"@redactMessageDescription": {},
"invalidInput": "",
"@invalidInput": {},
"todosUnencrypted": "",
"@todosUnencrypted": {},
"report": "",
"@report": {},
"googlyEyesContent": "",
"@googlyEyesContent": {
"type": "text",
"placeholders": {
"senderName": {}
}
},
"addChatDescription": "",
"@addChatDescription": {},
"hasKnocked": "",
"@hasKnocked": {
"placeholders": {
"user": {}
}
},
"openLinkInBrowser": "",
"@openLinkInBrowser": {},
"commandHint_me": "",
"@commandHint_me": {
"type": "text",
"description": "Usage hint for the command /me"
},
"directChat": "",
"@directChat": {},
"noOneCanJoin": "",
"@noOneCanJoin": {},
"wrongPinEntered": "",
"@wrongPinEntered": {
"type": "text",
"placeholders": {
"seconds": {}
}
},
"sendTypingNotifications": "",
"@sendTypingNotifications": {},
"inviteGroupChat": "",
"@inviteGroupChat": {},
"appearOnTop": "",
"@appearOnTop": {},
"invitePrivateChat": "",
"@invitePrivateChat": {},
"commandHint_kick": "",
"@commandHint_kick": {
"type": "text",
"description": "Usage hint for the command /kick"
},
"commandHint_unban": "",
"@commandHint_unban": {
"type": "text",
"description": "Usage hint for the command /unban"
},
"importEmojis": "",
"@importEmojis": {},
"noChatDescriptionYet": "",
"@noChatDescriptionYet": {},
"newTodo": "",
"@newTodo": {},
"learnMore": "",
"@learnMore": {},
"notAnImage": "",
"@notAnImage": {},
"chatDescriptionHasBeenChanged": "",
"@chatDescriptionHasBeenChanged": {},
"todoListChangedError": "",
"@todoListChangedError": {},
"commandHint_op": "",
"@commandHint_op": {
"type": "text",
"description": "Usage hint for the command /op"
},
"roomUpgradeDescription": "",
"@roomUpgradeDescription": {},
"pleaseEnterANumber": "",
"@pleaseEnterANumber": {},
"profileNotFound": "",
"@profileNotFound": {},
"jump": "",
"@jump": {},
"shareInviteLink": "",
"@shareInviteLink": {},
"cuddleContent": "",
"@cuddleContent": {
"type": "text",
"placeholders": {
"senderName": {}
}
},
"emoteKeyboardNoRecents": "",
"@emoteKeyboardNoRecents": {
"type": "text",
"placeholders": {}
},
"setTheme": "",
"@setTheme": {},
"replace": "",
"@replace": {},
"pleaseTryAgainLaterOrChooseDifferentServer": "",
"@pleaseTryAgainLaterOrChooseDifferentServer": {},
"createGroup": "",
"@createGroup": {},
"kickUserDescription": "",
"@kickUserDescription": {},
"importNow": "",
"@importNow": {},
"invite": "",
"@invite": {},
"continueWith": "",
"@continueWith": {}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1167,7 +1167,7 @@
"type": "text",
"placeholders": {}
},
"noGoogleServicesWarning": "Het lijkt erop dat je geen Google-services op je telefoon hebt. Dat is een goede beslissing voor je privacy! Om pushmeldingen in FluffyChat te ontvangen raden we je https://microg.org/ of https://unifiedpush.org aan.",
"noGoogleServicesWarning": "Firebase Cloud Messaging lijkt niet beschikbaar op je apparaat. Om nog steeds meldingen te krijgen, adviseren we om ntfy te installeren. Met ntfy of een andere Unified Push provider kun je meldingen ontvangen op een veilige manier. Je kunt ntfy downloaden van de PlayStore of van F-Droid.",
"@noGoogleServicesWarning": {
"type": "text",
"placeholders": {}
@ -2616,5 +2616,45 @@
"placeholders": {
"seconds": {}
}
}
},
"banUserDescription": "De persoon zal worden verbannen van de chat en kan niet meer toetreden totdat de verbanning is opgeheven.",
"@banUserDescription": {},
"removeDevicesDescription": "Je wordt op dit apparaat uitgelogd en zal niet langer in staat zijn om berichten te ontvangen.",
"@removeDevicesDescription": {},
"unbanUserDescription": "De persoon zal weer in staat zijn om de chat te betreden als ze het proberen.",
"@unbanUserDescription": {},
"todoLists": "(Beta) Todo's",
"@todoLists": {},
"editTodo": "Wijzig todo",
"@editTodo": {},
"pushNotificationsNotAvailable": "Meldingen zijn niet beschikbaar",
"@pushNotificationsNotAvailable": {},
"pleaseAddATitle": "Voeg een titel toe",
"@pleaseAddATitle": {},
"makeAdminDescription": "Wanneer je deze persoon beheerder maakt kun je dit niet ongedaan maken als jullie dezelfde rechten hebben.",
"@makeAdminDescription": {},
"noTodosYet": "Er zijn nog geen todo's toegevoegd aan deze chat. Maak je eerste todo en start met samenwerken met anderen. 📝",
"@noTodosYet": {},
"archiveRoomDescription": "De chat zal naar het archief worden verplaatst. Andere personen zullen in staat zijn te zien dat je de chat hebt verlaten.",
"@archiveRoomDescription": {},
"todosUnencrypted": "Let op dat todo's zichtbaar zijn voor iedereen in de chat en niet zijn versleuteld.",
"@todosUnencrypted": {},
"hasKnocked": "{user} heeft geklopt",
"@hasKnocked": {
"placeholders": {
"user": {}
}
},
"newTodo": "Nieuwe todo",
"@newTodo": {},
"learnMore": "Lees meer",
"@learnMore": {},
"todoListChangedError": "Oeps... De todo's zijn aangepast terwijl je aan het bewerken was.",
"@todoListChangedError": {},
"roomUpgradeDescription": "De chat zal dan opnieuw gemaakt worden met de nieuwe kamerversie. Alle deelnemers worden geïnformeerd dat ze moeten overstappen naar de nieuwe chat. Je kan meer lezen over kamerversies op https://spec.matrix.org/latest/rooms/",
"@roomUpgradeDescription": {},
"pleaseEnterANumber": "Vul een getal in groter dan 0",
"@pleaseEnterANumber": {},
"kickUserDescription": "De persoon is verwijderd uit de chat, maar is niet verbannen. In publieke chats kan de persoon op elk moment opnieuw deelnemen.",
"@kickUserDescription": {}
}

@ -2610,5 +2610,45 @@
"invite": "Zaproszenie",
"@invite": {},
"continueWith": "Kontynuuj z:",
"@continueWith": {}
"@continueWith": {},
"banUserDescription": "",
"@banUserDescription": {},
"removeDevicesDescription": "",
"@removeDevicesDescription": {},
"unbanUserDescription": "",
"@unbanUserDescription": {},
"todoLists": "",
"@todoLists": {},
"editTodo": "",
"@editTodo": {},
"pushNotificationsNotAvailable": "",
"@pushNotificationsNotAvailable": {},
"pleaseAddATitle": "",
"@pleaseAddATitle": {},
"makeAdminDescription": "",
"@makeAdminDescription": {},
"noTodosYet": "",
"@noTodosYet": {},
"archiveRoomDescription": "",
"@archiveRoomDescription": {},
"todosUnencrypted": "",
"@todosUnencrypted": {},
"hasKnocked": "",
"@hasKnocked": {
"placeholders": {
"user": {}
}
},
"newTodo": "",
"@newTodo": {},
"learnMore": "",
"@learnMore": {},
"todoListChangedError": "",
"@todoListChangedError": {},
"roomUpgradeDescription": "",
"@roomUpgradeDescription": {},
"pleaseEnterANumber": "",
"@pleaseEnterANumber": {},
"kickUserDescription": "",
"@kickUserDescription": {}
}

File diff suppressed because it is too large Load Diff

@ -2476,5 +2476,185 @@
"exportEmotePack": "Exportar pacote de Emotes como .zip",
"@exportEmotePack": {},
"replace": "Substituir",
"@replace": {}
"@replace": {},
"jumpToLastReadMessage": "",
"@jumpToLastReadMessage": {},
"reportErrorDescription": "",
"@reportErrorDescription": {},
"setColorTheme": "",
"@setColorTheme": {},
"banUserDescription": "",
"@banUserDescription": {},
"requests": "",
"@requests": {},
"removeDevicesDescription": "",
"@removeDevicesDescription": {},
"tryAgain": "",
"@tryAgain": {},
"unbanUserDescription": "",
"@unbanUserDescription": {},
"todoLists": "",
"@todoLists": {},
"messagesStyle": "",
"@messagesStyle": {},
"newSpaceDescription": "",
"@newSpaceDescription": {},
"chatDescription": "",
"@chatDescription": {},
"encryptThisChat": "",
"@encryptThisChat": {},
"reopenChat": "",
"@reopenChat": {},
"editTodo": "",
"@editTodo": {},
"pushNotificationsNotAvailable": "",
"@pushNotificationsNotAvailable": {},
"invalidServerName": "",
"@invalidServerName": {},
"chatPermissions": "",
"@chatPermissions": {},
"signInWithPassword": "",
"@signInWithPassword": {},
"pleaseAddATitle": "",
"@pleaseAddATitle": {},
"makeAdminDescription": "",
"@makeAdminDescription": {},
"setChatDescription": "",
"@setChatDescription": {},
"noOtherDevicesFound": "",
"@noOtherDevicesFound": {},
"redactedBy": "",
"@redactedBy": {
"type": "text",
"placeholders": {
"username": {}
}
},
"signInWith": "",
"@signInWith": {
"type": "text",
"placeholders": {
"provider": {}
}
},
"fileIsTooBigForServer": "",
"@fileIsTooBigForServer": {},
"noTodosYet": "",
"@noTodosYet": {},
"readUpToHere": "",
"@readUpToHere": {},
"optionalRedactReason": "",
"@optionalRedactReason": {},
"archiveRoomDescription": "",
"@archiveRoomDescription": {},
"letsStart": "",
"@letsStart": {},
"inviteContactToGroupQuestion": "",
"@inviteContactToGroupQuestion": {},
"redactedByBecause": "",
"@redactedByBecause": {
"type": "text",
"placeholders": {
"username": {},
"reason": {}
}
},
"fileHasBeenSavedAt": "",
"@fileHasBeenSavedAt": {
"type": "text",
"placeholders": {
"path": {}
}
},
"anyoneCanKnock": "",
"@anyoneCanKnock": {},
"redactMessageDescription": "",
"@redactMessageDescription": {},
"invalidInput": "",
"@invalidInput": {},
"todosUnencrypted": "",
"@todosUnencrypted": {},
"report": "",
"@report": {},
"addChatDescription": "",
"@addChatDescription": {},
"hasKnocked": "",
"@hasKnocked": {
"placeholders": {
"user": {}
}
},
"openLinkInBrowser": "",
"@openLinkInBrowser": {},
"disableEncryptionWarning": "",
"@disableEncryptionWarning": {},
"directChat": "",
"@directChat": {},
"noOneCanJoin": "",
"@noOneCanJoin": {},
"wrongPinEntered": "",
"@wrongPinEntered": {
"type": "text",
"placeholders": {
"seconds": {}
}
},
"inviteGroupChat": "",
"@inviteGroupChat": {},
"invitePrivateChat": "",
"@invitePrivateChat": {},
"wasDirectChatDisplayName": "",
"@wasDirectChatDisplayName": {
"type": "text",
"placeholders": {
"oldDisplayName": {}
}
},
"noChatDescriptionYet": "",
"@noChatDescriptionYet": {},
"newTodo": "",
"@newTodo": {},
"learnMore": "",
"@learnMore": {},
"chatDescriptionHasBeenChanged": "",
"@chatDescriptionHasBeenChanged": {},
"todoListChangedError": "",
"@todoListChangedError": {},
"enterInviteLinkOrMatrixId": "",
"@enterInviteLinkOrMatrixId": {},
"roomUpgradeDescription": "",
"@roomUpgradeDescription": {},
"pleaseEnterANumber": "",
"@pleaseEnterANumber": {},
"profileNotFound": "",
"@profileNotFound": {},
"jump": "",
"@jump": {},
"sorryThatsNotPossible": "",
"@sorryThatsNotPossible": {},
"shareInviteLink": "",
"@shareInviteLink": {},
"deviceKeys": "",
"@deviceKeys": {},
"emoteKeyboardNoRecents": "",
"@emoteKeyboardNoRecents": {
"type": "text",
"placeholders": {}
},
"endToEndEncryption": "",
"@endToEndEncryption": {},
"setTheme": "",
"@setTheme": {},
"pleaseTryAgainLaterOrChooseDifferentServer": "",
"@pleaseTryAgainLaterOrChooseDifferentServer": {},
"createGroup": "",
"@createGroup": {},
"noBackupWarning": "",
"@noBackupWarning": {},
"kickUserDescription": "",
"@kickUserDescription": {},
"invite": "",
"@invite": {},
"continueWith": "",
"@continueWith": {}
}

File diff suppressed because it is too large Load Diff

@ -2502,5 +2502,152 @@
"pleaseTryAgainLaterOrChooseDifferentServer": "Vă rugăm să încercați din nou mai târziu sau să alegeți un server diferit.",
"@pleaseTryAgainLaterOrChooseDifferentServer": {},
"signInWithPassword": "Conectați-vă cu parolă",
"@signInWithPassword": {}
"@signInWithPassword": {},
"setColorTheme": "",
"@setColorTheme": {},
"banUserDescription": "",
"@banUserDescription": {},
"requests": "",
"@requests": {},
"removeDevicesDescription": "",
"@removeDevicesDescription": {},
"tryAgain": "",
"@tryAgain": {},
"unbanUserDescription": "",
"@unbanUserDescription": {},
"todoLists": "",
"@todoLists": {},
"messagesStyle": "",
"@messagesStyle": {},
"chatDescription": "",
"@chatDescription": {},
"editTodo": "",
"@editTodo": {},
"pushNotificationsNotAvailable": "",
"@pushNotificationsNotAvailable": {},
"invalidServerName": "",
"@invalidServerName": {},
"chatPermissions": "",
"@chatPermissions": {},
"pleaseAddATitle": "",
"@pleaseAddATitle": {},
"makeAdminDescription": "",
"@makeAdminDescription": {},
"setChatDescription": "",
"@setChatDescription": {},
"importFromZipFile": "",
"@importFromZipFile": {},
"redactedBy": "",
"@redactedBy": {
"type": "text",
"placeholders": {
"username": {}
}
},
"signInWith": "",
"@signInWith": {
"type": "text",
"placeholders": {
"provider": {}
}
},
"noTodosYet": "",
"@noTodosYet": {},
"optionalRedactReason": "",
"@optionalRedactReason": {},
"archiveRoomDescription": "",
"@archiveRoomDescription": {},
"exportEmotePack": "",
"@exportEmotePack": {},
"savedEmotePack": "",
"@savedEmotePack": {
"type": "text",
"placeholders": {
"path": {}
}
},
"inviteContactToGroupQuestion": "",
"@inviteContactToGroupQuestion": {},
"redactedByBecause": "",
"@redactedByBecause": {
"type": "text",
"placeholders": {
"username": {},
"reason": {}
}
},
"importZipFile": "",
"@importZipFile": {},
"anyoneCanKnock": "",
"@anyoneCanKnock": {},
"redactMessageDescription": "",
"@redactMessageDescription": {},
"invalidInput": "",
"@invalidInput": {},
"todosUnencrypted": "",
"@todosUnencrypted": {},
"addChatDescription": "",
"@addChatDescription": {},
"hasKnocked": "",
"@hasKnocked": {
"placeholders": {
"user": {}
}
},
"directChat": "",
"@directChat": {},
"noOneCanJoin": "",
"@noOneCanJoin": {},
"wrongPinEntered": "",
"@wrongPinEntered": {
"type": "text",
"placeholders": {
"seconds": {}
}
},
"sendTypingNotifications": "",
"@sendTypingNotifications": {},
"inviteGroupChat": "",
"@inviteGroupChat": {},
"invitePrivateChat": "",
"@invitePrivateChat": {},
"importEmojis": "",
"@importEmojis": {},
"noChatDescriptionYet": "",
"@noChatDescriptionYet": {},
"newTodo": "",
"@newTodo": {},
"learnMore": "",
"@learnMore": {},
"notAnImage": "",
"@notAnImage": {},
"chatDescriptionHasBeenChanged": "",
"@chatDescriptionHasBeenChanged": {},
"todoListChangedError": "",
"@todoListChangedError": {},
"roomUpgradeDescription": "",
"@roomUpgradeDescription": {},
"pleaseEnterANumber": "",
"@pleaseEnterANumber": {},
"profileNotFound": "",
"@profileNotFound": {},
"shareInviteLink": "",
"@shareInviteLink": {},
"emoteKeyboardNoRecents": "",
"@emoteKeyboardNoRecents": {
"type": "text",
"placeholders": {}
},
"setTheme": "",
"@setTheme": {},
"replace": "",
"@replace": {},
"createGroup": "",
"@createGroup": {},
"kickUserDescription": "",
"@kickUserDescription": {},
"importNow": "",
"@importNow": {},
"invite": "",
"@invite": {}
}

@ -2617,5 +2617,45 @@
"@emoteKeyboardNoRecents": {
"type": "text",
"placeholders": {}
}
},
"banUserDescription": "",
"@banUserDescription": {},
"removeDevicesDescription": "",
"@removeDevicesDescription": {},
"unbanUserDescription": "",
"@unbanUserDescription": {},
"todoLists": "",
"@todoLists": {},
"editTodo": "",
"@editTodo": {},
"pushNotificationsNotAvailable": "",
"@pushNotificationsNotAvailable": {},
"pleaseAddATitle": "",
"@pleaseAddATitle": {},
"makeAdminDescription": "",
"@makeAdminDescription": {},
"noTodosYet": "",
"@noTodosYet": {},
"archiveRoomDescription": "",
"@archiveRoomDescription": {},
"todosUnencrypted": "",
"@todosUnencrypted": {},
"hasKnocked": "",
"@hasKnocked": {
"placeholders": {
"user": {}
}
},
"newTodo": "",
"@newTodo": {},
"learnMore": "",
"@learnMore": {},
"todoListChangedError": "",
"@todoListChangedError": {},
"roomUpgradeDescription": "",
"@roomUpgradeDescription": {},
"pleaseEnterANumber": "",
"@pleaseEnterANumber": {},
"kickUserDescription": "",
"@kickUserDescription": {}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -2617,5 +2617,45 @@
"placeholders": {
"seconds": {}
}
}
},
"banUserDescription": "",
"@banUserDescription": {},
"removeDevicesDescription": "",
"@removeDevicesDescription": {},
"unbanUserDescription": "",
"@unbanUserDescription": {},
"todoLists": "",
"@todoLists": {},
"editTodo": "",
"@editTodo": {},
"pushNotificationsNotAvailable": "",
"@pushNotificationsNotAvailable": {},
"pleaseAddATitle": "",
"@pleaseAddATitle": {},
"makeAdminDescription": "",
"@makeAdminDescription": {},
"noTodosYet": "",
"@noTodosYet": {},
"archiveRoomDescription": "",
"@archiveRoomDescription": {},
"todosUnencrypted": "",
"@todosUnencrypted": {},
"hasKnocked": "",
"@hasKnocked": {
"placeholders": {
"user": {}
}
},
"newTodo": "",
"@newTodo": {},
"learnMore": "",
"@learnMore": {},
"todoListChangedError": "",
"@todoListChangedError": {},
"roomUpgradeDescription": "",
"@roomUpgradeDescription": {},
"pleaseEnterANumber": "",
"@pleaseEnterANumber": {},
"kickUserDescription": "",
"@kickUserDescription": {}
}

File diff suppressed because it is too large Load Diff

@ -2625,5 +2625,37 @@
}
},
"pleaseEnterANumber": "请输入大于 0 的数",
"@pleaseEnterANumber": {}
"@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": "",
"@roomUpgradeDescription": {},
"kickUserDescription": "",
"@kickUserDescription": {}
}

File diff suppressed because it is too large Load Diff

@ -94,16 +94,16 @@
code</a>
-
<a class="text-slate-700 dark:text-slate-200 no-underline hover:text-purple-800"
href="https://github.com/krille-chan/fluffychat/-/blob/main/PRIVACY.md">Privacy</a>
href="https://github.com/krille-chan/fluffychat/blob/main/PRIVACY.md">Privacy</a>
-
<a class="text-slate-700 dark:text-slate-200 no-underline hover:text-purple-800"
href="https://github.com/krille-chan/fluffychat/-/blob/main/CHANGELOG.md">Changelog</a>
href="https://github.com/krille-chan/fluffychat/blob/main/CHANGELOG.md">Changelog</a>
-
<a class="text-slate-700 dark:text-slate-200 no-underline hover:text-purple-800"
href="https://hosted.weblate.org/projects/fluffychat/">Translations</a>
-
<a class="text-slate-700 dark:text-slate-200 no-underline hover:text-purple-800"
href="https://github.com/krille-chan/fluffychat/-/blob/main/docs/fdroid_repo.md">FluffyChat F-Droid
href="https://github.com/krille-chan/fluffychat/blob/main/docs/fdroid_repo.md">FluffyChat F-Droid
repository</a>
-
<a class="text-slate-700 dark:text-slate-200 no-underline hover:text-purple-800"

@ -38,6 +38,8 @@ abstract class AppConfig {
'https://github.com/krille-chan/fluffychat/wiki/Push-Notifications-without-Google-Services';
static const String encryptionTutorial =
'https://github.com/krille-chan/fluffychat/wiki/How-to-use-end-to-end-encryption-in-FluffyChat';
static const String startChatTutorial =
'https://github.com/krille-chan/fluffychat/wiki/How-to-Find-Users-in-FluffyChat';
static const String appId = 'im.fluffychat.FluffyChat';
// #Pangea
// static const String appOpenUrlScheme = 'im.fluffychat';

@ -52,6 +52,11 @@ void main() async {
// currently only supported on Android.
if (PlatformInfos.isAndroid &&
AppLifecycleState.detached == WidgetsBinding.instance.lifecycleState) {
// Do not send online presences when app is in background fetch mode.
for (final client in clients) {
client.syncPresence = PresenceType.offline;
}
// In the background fetch mode we do not want to waste ressources with
// starting the Flutter engine but process incoming push notifications.
BackgroundPush.clientOnly(clients.first);
@ -109,6 +114,10 @@ class AppStarter with WidgetsBindingObserver {
Logs().i(
'${AppConfig.applicationName} switches from the detached background-fetch mode to ${state.name} mode. Rendering GUI...',
);
// Switching to foreground mode needs to reenable send online sync presence.
for (final client in clients) {
client.syncPresence = PresenceType.online;
}
startGui(clients, store);
// We must make sure that the GUI is only started once.
guiStarted = true;

@ -1,6 +1,9 @@
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/utils/date_time_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/presence_builder.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
@ -32,17 +35,48 @@ class ChatAppBarTitle extends StatelessWidget {
MatrixLocals(L10n.of(context)!),
),
size: 32,
presenceUserId: room.directChatMatrixID,
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 16,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 16,
),
),
AnimatedSize(
duration: FluffyThemes.animationDuration,
child: PresenceBuilder(
userId: room.directChatMatrixID,
builder: (context, presence) {
final lastActiveTimestamp = presence?.lastActiveTimestamp;
final style = Theme.of(context).textTheme.bodySmall;
if (presence?.currentlyActive == true) {
return Text(
L10n.of(context)!.currentlyActive,
style: style,
);
}
if (lastActiveTimestamp != null) {
return Text(
L10n.of(context)!.lastActiveAgo(
lastActiveTimestamp.localizedTimeShort(context),
),
style: style,
);
}
return const SizedBox.shrink();
},
),
),
],
),
),
],

@ -149,16 +149,15 @@ class ChatView extends StatelessWidget {
final bottomSheetPadding = FluffyThemes.isColumnMode(context) ? 16.0 : 8.0;
final scrollUpBannerEventId = controller.scrollUpBannerEventId;
return WillPopScope(
onWillPop: () async {
return PopScope(
canPop: controller.selectedEvents.isEmpty && !controller.showEmojiPicker,
onPopInvoked: (pop) async {
if (pop) return;
if (controller.selectedEvents.isNotEmpty) {
controller.clearSelectedEvents();
return false;
} else if (controller.showEmojiPicker) {
controller.emojiPickerAction();
return false;
}
return true;
},
child: GestureDetector(
onTapDown: (_) => controller.setReadMarker(),

@ -51,6 +51,8 @@ class EventInfoDialog extends StatelessWidget {
leading: Avatar(
mxContent: event.senderFromMemoryOrFallback.avatarUrl,
name: event.senderFromMemoryOrFallback.calcDisplayname(),
client: event.room.client,
presenceUserId: event.senderId,
),
title: Text(L10n.of(context)!.sender),
subtitle: Text(

@ -140,7 +140,11 @@ class HtmlMessage extends StatelessWidget {
const ImageExtension(),
FontColorExtension(),
],
onLinkTap: (url, _, __) => UrlLauncher(context, url).launchUrl(),
onLinkTap: (url, _, element) => UrlLauncher(
context,
url,
element?.text,
).launchUrl(),
onlyRenderTheseTags: const {
...allowedHtmlTags,
// Needed to make it work properly

@ -1,3 +1,4 @@
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/image_viewer/image_viewer.dart';
import 'package:fluffychat/widgets/mxc_image.dart';
import 'package:flutter/material.dart';
@ -15,6 +16,7 @@ class ImageBubble extends StatelessWidget {
final double width;
final double height;
final void Function()? onTap;
final BorderRadius? borderRadius;
const ImageBubble(
this.event, {
@ -27,6 +29,7 @@ class ImageBubble extends StatelessWidget {
this.height = 300,
this.animated = false,
this.onTap,
this.borderRadius,
super.key,
});
@ -47,8 +50,10 @@ class ImageBubble extends StatelessWidget {
var height = 32;
if (ratio > 1.0) {
height = (width / ratio).round();
if (height <= 0) height = 1;
} else {
width = (height * ratio).round();
if (width <= 0) width = 1;
}
return SizedBox(
width: this.width,
@ -77,19 +82,25 @@ class ImageBubble extends StatelessWidget {
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () => _onTap(context),
child: Hero(
tag: event.eventId,
child: AnimatedSwitcher(
duration: const Duration(seconds: 1),
child: Container(
final borderRadius =
this.borderRadius ?? BorderRadius.circular(AppConfig.borderRadius);
return Material(
shape: RoundedRectangleBorder(
borderRadius: borderRadius,
side: BorderSide(color: Theme.of(context).dividerColor),
),
child: InkWell(
onTap: () => _onTap(context),
borderRadius: borderRadius,
child: Hero(
tag: event.eventId,
child: ConstrainedBox(
constraints: maxSize
? BoxConstraints(
maxWidth: width,
maxHeight: height,
)
: null,
: const BoxConstraints.expand(),
child: MxcImage(
event: event,
width: width,

@ -12,7 +12,6 @@ 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';
@ -23,10 +22,10 @@ class Message extends StatelessWidget {
final Event event;
final Event? nextEvent;
final bool displayReadMarker;
final void Function(Event)? onSelect;
final void Function(Event)? onAvatarTab;
final void Function(Event)? onInfoTab;
final void Function(String)? scrollToEventId;
final void Function(Event) onSelect;
final void Function(Event) onAvatarTab;
final void Function(Event) onInfoTab;
final void Function(String) scrollToEventId;
final void Function() onSwipe;
final bool longPressSelect;
final bool selected;
@ -35,17 +34,17 @@ class Message extends StatelessWidget {
final LanguageModel? selectedDisplayLang;
final bool immersionMode;
final bool definitions;
// #Pangea
// Pangea#
const Message(
this.event, {
this.nextEvent,
this.displayReadMarker = false,
this.longPressSelect = false,
this.onSelect,
this.onInfoTab,
this.onAvatarTab,
this.scrollToEventId,
required this.onSelect,
required this.onInfoTab,
required this.onAvatarTab,
required this.scrollToEventId,
required this.onSwipe,
this.selected = false,
required this.timeline,
@ -53,7 +52,7 @@ class Message extends StatelessWidget {
required this.selectedDisplayLang,
required this.immersionMode,
required this.definitions,
// #Pangea
// Pangea#
super.key,
});
@ -129,231 +128,218 @@ class Message extends StatelessWidget {
: Theme.of(context).colorScheme.primaryContainer;
}
//#Pangea
// #Pangea
final pangeaMessageEvent = PangeaMessageEvent(
event: event,
timeline: timeline,
ownMessage: ownMessage,
selected: selected,
);
//#Pangea
// Pangea#
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),
),
)
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,
),
final row = Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: rowMainAxisAlignment,
children: [
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,
),
)
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)
.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,
)
else
FutureBuilder<User?>(
future: event.fetchSenderUser(),
builder: (context, snapshot) {
final user = snapshot.data ?? event.senderFromMemoryOrFallback;
return Avatar(
mxContent: user.avatarUrl,
name: user.calcDisplayname(),
presenceUserId: user.stateKey,
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),
),
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,
),
),
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
.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: () =>
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,
MessageContent(
displayEvent,
textColor: textColor,
onInfoTab: onInfoTab,
borderRadius: borderRadius,
// #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),
size: 14,
fontSize: 12,
),
Text(
' - ${displayEvent.originServerTs.localizedTimeShort(context)}',
style: TextStyle(
color: textColor.withAlpha(164),
fontSize: 12,
),
),
],
),
],
),
],
),
],
),
),
],
),
),
),
),
],
),
),
],
),
],
),
),
],
);
Widget container;
if (event.hasAggregatedEvents(timeline, RelationshipTypes.reaction) ||
@ -452,23 +438,20 @@ class Message extends StatelessWidget {
),
direction: SwipeDirection.endToStart,
onSwipe: (_) => onSwipe(),
child: Center(
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,
child: InkWell(
onTap: () => 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,
),
),
);

@ -26,6 +26,7 @@ class MessageContent extends StatelessWidget {
final Event event;
final Color textColor;
final void Function(Event)? onInfoTab;
final BorderRadius borderRadius;
// #Pangea
final bool selected;
final PangeaMessageEvent pangeaMessageEvent;
@ -49,6 +50,7 @@ class MessageContent extends StatelessWidget {
required this.immersionMode,
required this.definitions,
// Pangea#
required this.borderRadius,
});
void _verifyOrRequestKey(BuildContext context) async {
@ -97,6 +99,7 @@ class MessageContent extends StatelessWidget {
leading: Avatar(
mxContent: sender.avatarUrl,
name: sender.calcDisplayname(),
presenceUserId: sender.stateKey,
),
title: Text(sender.calcDisplayname()),
subtitle: Text(event.originServerTs.localizedTime(context)),
@ -130,6 +133,7 @@ class MessageContent extends StatelessWidget {
width: 400,
height: 300,
fit: BoxFit.cover,
borderRadius: borderRadius,
);
case MessageTypes.Sticker:
if (event.redacted) continue textmessage;

@ -208,6 +208,7 @@ class _AdaptableReactorsDialog extends StatelessWidget {
mxContent: reactor.avatarUrl,
name: reactor.displayName,
client: client,
presenceUserId: reactor.stateKey,
),
label: Text(reactor.displayName!),
),

@ -58,6 +58,7 @@ class EventVideoPlayerState extends State<EventVideoPlayer> {
);
} else if (!kIsWeb && tmpFile != null && _chewieManager == null) {
_chewieManager ??= ChewieController(
useRootNavigator: false,
videoPlayerController: VideoPlayerController.file(tmpFile),
autoPlay: true,
autoInitialize: true,

@ -103,8 +103,11 @@ class ParticipantListItem extends StatelessWidget {
],
),
subtitle: Text(user.id),
leading:
Avatar(mxContent: user.avatarUrl, name: user.calcDisplayname()),
leading: Avatar(
mxContent: user.avatarUrl,
name: user.calcDisplayname(),
presenceUserId: user.stateKey,
),
),
);
}

@ -53,7 +53,7 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
// borderSide: BorderSide.none,
// borderRadius: BorderRadius.circular(99),
// ),
// hintText: L10n.of(context)!.search,
// hintText: L10n.of(context)!.searchChatsRooms,
// floatingLabelBehavior: FloatingLabelBehavior.never,
// prefixIcon: controller.isSearchMode
// ? IconButton(

@ -159,6 +159,11 @@ class ChatListItem extends StatelessWidget {
: 14.0
: 0.0;
final hasNotifications = room.notificationCount > 0;
final backgroundColor = selected
? Theme.of(context).colorScheme.primaryContainer
: activeChat
? Theme.of(context).colorScheme.secondaryContainer
: null;
final displayname = room.getLocalizedDisplayname(
MatrixLocals(L10n.of(context)!),
);
@ -170,11 +175,7 @@ class ChatListItem extends StatelessWidget {
child: Material(
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
clipBehavior: Clip.hardEdge,
color: selected
? Theme.of(context).colorScheme.primaryContainer
: activeChat
? Theme.of(context).colorScheme.secondaryContainer
: Colors.transparent,
color: backgroundColor,
child: ListTile(
visualDensity: const VisualDensity(vertical: -0.5),
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
@ -196,6 +197,8 @@ class ChatListItem extends StatelessWidget {
//#Pangea
littleIcon: room.roomTypeIcon,
// Pangea#
presenceUserId: room.directChatMatrixID,
presenceBackgroundColor: backgroundColor,
),
title: Row(
children: <Widget>[

@ -98,16 +98,23 @@ class ChatListView extends StatelessWidget {
stream: Matrix.of(context).onShareContentChanged.stream,
builder: (_, __) {
final selectMode = controller.selectMode;
return WillPopScope(
onWillPop: () async {
return PopScope(
canPop: controller.selectMode == SelectMode.normal &&
!controller.isSearchMode &&
controller.activeFilter ==
(AppConfig.separateChatTypes
? ActiveFilter.messages
: ActiveFilter.allChats),
onPopInvoked: (pop) async {
if (pop) return;
final selMode = controller.selectMode;
if (controller.isSearchMode) {
controller.cancelSearch();
return false;
return;
}
if (selMode != SelectMode.normal) {
controller.cancelAction();
return false;
return;
}
if (controller.activeFilter !=
(AppConfig.separateChatTypes
@ -115,9 +122,8 @@ class ChatListView extends StatelessWidget {
: ActiveFilter.allChats)) {
controller
.onDestinationSelected(AppConfig.separateChatTypes ? 1 : 0);
return false;
return;
}
return true;
},
child: Row(
children: [

@ -395,13 +395,13 @@ class _SpaceViewState extends State<SpaceView> {
// Pangea#
final canLoadMore = response.nextBatch != null;
return WillPopScope(
onWillPop: () async {
return PopScope(
canPop: parentSpace == null,
onPopInvoked: (pop) async {
if (pop) return;
if (parentSpace != null) {
widget.controller.setActiveSpace(parentSpace.id);
return false;
}
return true;
},
child: CustomScrollView(
controller: widget.scrollController,

@ -167,6 +167,7 @@ class _InviteContactListTile extends StatelessWidget {
leading: Avatar(
mxContent: avatarUrl,
name: displayname,
presenceUserId: userId,
),
title: Text(
displayname,

@ -1,18 +1,16 @@
import 'dart:developer';
import 'dart:typed_data';
import 'package:file_picker/file_picker.dart';
import 'package:fluffychat/pages/new_group/new_group_view.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/models/chat_topic_model.dart';
import 'package:fluffychat/pangea/models/lemma.dart';
import 'package:fluffychat/pangea/utils/class_chat_power_levels.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart' as sdk;
@ -24,25 +22,53 @@ class NewGroup extends StatefulWidget {
}
class NewGroupController extends State<NewGroup> {
TextEditingController controller = TextEditingController();
TextEditingController nameController = TextEditingController();
TextEditingController topicController = TextEditingController();
bool publicGroup = false;
bool groupCanBeFound = true;
//#Pangea
Uint8List? avatar;
Uri? avatarUrl;
Object? error;
bool loading = false;
// #Pangea
PangeaController pangeaController = MatrixState.pangeaController;
final GlobalKey<AddToSpaceState> addToSpaceKey = GlobalKey<AddToSpaceState>();
ChatTopic chatTopic = ChatTopic.empty;
void setVocab(List<Lemma> vocab) => setState(() => chatTopic.vocab = vocab);
String? get activeSpaceId =>
GoRouterState.of(context).pathParameters['spaceid'];
// Pangea#
void setPublicGroup(bool b) => setState(() => publicGroup = b);
void setGroupCanBeFound(bool b) => setState(() => groupCanBeFound = b);
void selectPhoto() async {
final photo = await FilePicker.platform.pickFiles(
type: FileType.image,
allowMultiple: false,
withData: true,
);
setState(() {
avatarUrl = null;
avatar = photo?.files.singleOrNull?.bytes;
});
}
void submitAction([_]) async {
// #Pangea
if (controller.text.isEmpty) {
if (nameController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.emptyChatNameWarning),
@ -52,48 +78,68 @@ class NewGroupController extends State<NewGroup> {
}
// Pangea#
final client = Matrix.of(context).client;
final roomID = await showFutureLoadingDialog(
context: context,
future: () async {
final roomId = await client.createGroupChat(
// #Pangea
// visibility:
// publicGroup ? sdk.Visibility.public : sdk.Visibility.private,
// preset: publicGroup
// ? sdk.CreateRoomPreset.publicChat
// : sdk.CreateRoomPreset.privateChat,
preset: sdk.CreateRoomPreset.publicChat,
groupName: controller.text.isNotEmpty ? controller.text : null,
powerLevelContentOverride:
await ClassChatPowerLevels.powerLevelOverrideForClassChat(
context,
addToSpaceKey.currentState!.parents
.map((suggestionStatus) => suggestionStatus.room)
.toList(),
),
// Pangea#
try {
setState(() {
loading = true;
error = null;
});
final avatar = this.avatar;
avatarUrl ??= avatar == null ? null : await client.uploadContent(avatar);
if (!mounted) return;
final roomId = await client.createGroupChat(
// #Pangea
// visibility:
// publicGroup ? sdk.Visibility.public : sdk.Visibility.private,
// preset: publicGroup
// ? sdk.CreateRoomPreset.publicChat
// : sdk.CreateRoomPreset.privateChat,
// groupName: nameController.text.isNotEmpty ? nameController.text : null,
// initialState: [
// if (topicController.text.isNotEmpty)
// sdk.StateEvent(
// type: sdk.EventTypes.RoomTopic,
// content: {'topic': topicController.text},
// ),
// if (avatar != null)
// sdk.StateEvent(
// type: sdk.EventTypes.RoomAvatar,
// content: {'url': avatarUrl.toString()},
// ),
// ],
groupName: nameController.text,
preset: sdk.CreateRoomPreset.publicChat,
powerLevelContentOverride:
await ClassChatPowerLevels.powerLevelOverrideForClassChat(
context,
addToSpaceKey.currentState!.parents
.map((suggestionStatus) => suggestionStatus.room)
.toList(),
),
// Pangea#
);
if (!mounted) return;
if (publicGroup && groupCanBeFound) {
await client.setRoomVisibilityOnDirectory(
roomId,
visibility: sdk.Visibility.public,
);
return roomId;
},
// #Pangea
onError: (exception) {
ErrorHandler.logError(e: exception, s: StackTrace.current);
return exception.toString();
},
// Pangea#
);
if (roomID.error == null) {
}
//#Pangea
GoogleAnalytics.createChat(roomID.result!);
await addToSpaceKey.currentState!.addSpaces(roomID.result!);
GoogleAnalytics.createChat(roomId);
await addToSpaceKey.currentState!.addSpaces(roomId);
//Pangea#
context.go('/rooms/${roomID.result!}/invite');
//#Pangea
} else {
debugger(when: kDebugMode);
ErrorHandler.logError(e: roomID.error, s: StackTrace.current);
context.go('/rooms/$roomId/invite');
} catch (e, s) {
sdk.Logs().d('Unable to create group', e, s);
setState(() {
error = e;
loading = false;
});
}
//Pangea#
}
//#Pangea

@ -1,6 +1,9 @@
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/new_group/new_group.dart';
import 'package:fluffychat/pangea/widgets/class/add_class_and_invite.dart';
import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
@ -12,62 +15,161 @@ class NewGroupView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final avatar = controller.avatar;
final error = controller.error;
return Scaffold(
appBar: AppBar(
leading: Center(
child: BackButton(
onPressed: controller.loading ? null : Navigator.of(context).pop,
),
),
title: Text(L10n.of(context)!.createGroup),
),
body: MaxWidthBody(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.all(12.0),
child: TextField(
controller: controller.controller,
autofocus: true,
autocorrect: false,
textInputAction: TextInputAction.go,
onSubmitted: controller.submitAction,
decoration: InputDecoration(
// #Pangea
labelText: L10n.of(context)!.enterAGroupName,
// labelText: L10n.of(context)!.optionalGroupName,
prefixIcon: const Icon(Icons.people_outlined),
// hintText: L10n.of(context)!.enterAGroupName,
// Pangea#
),
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
children: [
InkWell(
borderRadius: BorderRadius.circular(90),
onTap: controller.loading ? null : controller.selectPhoto,
child: CircleAvatar(
radius: Avatar.defaultSize / 2,
child: avatar == null
? const Icon(Icons.camera_alt_outlined)
: ClipRRect(
borderRadius: BorderRadius.circular(90),
child: Image.memory(
avatar,
width: Avatar.defaultSize,
height: Avatar.defaultSize,
fit: BoxFit.cover,
),
),
),
),
const SizedBox(width: 16),
Expanded(
child: TextField(
controller: controller.nameController,
autocorrect: false,
readOnly: controller.loading,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.people_outlined),
hintText: L10n.of(context)!.groupName,
),
),
),
],
),
),
const SizedBox(height: 16),
// #Pangea
// Padding(
// padding: const EdgeInsets.symmetric(horizontal: 16.0),
// child: TextField(
// controller: controller.topicController,
// minLines: 4,
// maxLines: 4,
// maxLength: 255,
// readOnly: controller.loading,
// decoration: InputDecoration(
// hintText: L10n.of(context)!.addChatDescription,
// ),
// ),
// ),
AddToSpaceToggles(
key: controller.addToSpaceKey,
startOpen: false,
activeSpaceId: controller.activeSpaceId,
mode: AddToClassMode.chat,
),
// const SizedBox(height: 16),
// SwitchListTile.adaptive(
// secondary: const Icon(Icons.public_outlined),
// title: Text(L10n.of(context)!.groupIsPublic),
// value: controller.publicGroup,
// onChanged: controller.setPublicGroup,
// onChanged: controller.loading ? null : controller.setPublicGroup,
// ),
// AnimatedSize(
// duration: FluffyThemes.animationDuration,
// child: controller.publicGroup
// ? SwitchListTile.adaptive(
// secondary: const Icon(Icons.search_outlined),
// title: Text(L10n.of(context)!.groupCanBeFoundViaSearch),
// value: controller.groupCanBeFound,
// onChanged: controller.loading
// ? null
// : controller.setGroupCanBeFound,
// )
// : const SizedBox.shrink(),
// ),
// SwitchListTile.adaptive(
// secondary: const Icon(Icons.lock_outlined),
// title: Text(L10n.of(context)!.enableEncryption),
// secondary: Icon(
// Icons.lock_outlined,
// color: Theme.of(context).colorScheme.onBackground,
// ),
// title: Text(
// L10n.of(context)!.enableEncryption,
// style: TextStyle(
// color: Theme.of(context).colorScheme.onBackground,
// ),
// ),
// value: !controller.publicGroup,
// onChanged: null,
// ),
AddToSpaceToggles(
key: controller.addToSpaceKey,
startOpen: false,
activeSpaceId: controller.activeSpaceId,
mode: AddToClassMode.chat,
// Pangea#
Padding(
padding: const EdgeInsets.all(16.0),
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.onPrimary,
backgroundColor: Theme.of(context).colorScheme.primary,
),
onPressed:
controller.loading ? null : controller.submitAction,
child: controller.loading
? const LinearProgressIndicator()
: Row(
children: [
Expanded(
child: Text(
L10n.of(context)!.createGroupAndInviteUsers,
),
),
Icon(Icons.adaptive.arrow_forward_outlined),
],
),
),
),
),
const SizedBox(
height: 50,
AnimatedSize(
duration: FluffyThemes.animationDuration,
child: error == null
? const SizedBox.shrink()
: ListTile(
leading: Icon(
Icons.warning_outlined,
color: Theme.of(context).colorScheme.error,
),
title: Text(
error.toLocalizedString(context),
style: TextStyle(
color: Theme.of(context).colorScheme.error,
),
),
),
),
// Pangea#
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: controller.submitAction,
child: const Icon(Icons.arrow_forward_outlined),
),
);
}
}

@ -1,6 +1,9 @@
import 'dart:async';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:fluffychat/pages/new_private_chat/new_private_chat_view.dart';
import 'package:fluffychat/pages/new_private_chat/qr_scanner_modal.dart';
import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
import 'package:fluffychat/utils/fluffy_share.dart';
import 'package:fluffychat/utils/platform_infos.dart';
@ -21,38 +24,43 @@ class NewPrivateChat extends StatefulWidget {
class NewPrivateChatController extends State<NewPrivateChat> {
final TextEditingController controller = TextEditingController();
final FocusNode textFieldFocus = FocusNode();
final formKey = GlobalKey<FormState>();
bool loading = false;
// remove leading matrix.to from text field in order to simplify pasting
final List<TextInputFormatter> removeMatrixToFormatters = [
FilteringTextInputFormatter.deny(NewPrivateChatController.prefix),
FilteringTextInputFormatter.deny(NewPrivateChatController.prefixNoProtocol),
];
Future<List<Profile>>? searchResponse;
Timer? _searchCoolDown;
static const Set<String> supportedSigils = {'@', '!', '#'};
static const Duration _coolDown = Duration(milliseconds: 500);
static const String prefix = 'https://matrix.to/#/';
static const String prefixNoProtocol = 'matrix.to/#/';
void searchUsers([String? input]) async {
final searchTerm = input ?? controller.text;
if (searchTerm.isEmpty) {
_searchCoolDown?.cancel();
setState(() {
searchResponse = _searchCoolDown = null;
});
return;
}
void submitAction([_]) async {
controller.text = controller.text.trim();
if (!formKey.currentState!.validate()) return;
UrlLauncher(context, '$prefix${controller.text}').openMatrixToUrl();
_searchCoolDown?.cancel();
_searchCoolDown = Timer(_coolDown, () {
setState(() {
searchResponse = _searchUser(searchTerm);
});
});
}
String? validateForm(String? value) {
if (value!.isEmpty) {
return L10n.of(context)!.pleaseEnterAMatrixIdentifier;
}
if (!controller.text.isValidMatrixId ||
!supportedSigils.contains(controller.text.sigil)) {
return L10n.of(context)!.makeSureTheIdentifierIsValid;
}
if (controller.text == Matrix.of(context).client.userID) {
return L10n.of(context)!.youCannotInviteYourself;
Future<List<Profile>> _searchUser(String searchTerm) async {
final result =
await Matrix.of(context).client.searchUserDirectory(searchTerm);
final profiles = result.results;
if (searchTerm.isValidMatrixId &&
searchTerm.sigil == '@' &&
!profiles.any((profile) => profile.userId == searchTerm)) {
profiles.add(Profile(userId: searchTerm));
}
return null;
return profiles;
}
void inviteAction() => FluffyShare.shareInviteLink(context);
@ -79,6 +87,23 @@ class NewPrivateChatController extends State<NewPrivateChat> {
);
}
void copyUserId() async {
await Clipboard.setData(
ClipboardData(text: Matrix.of(context).client.userID!),
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context)!.copiedToClipboard)),
);
}
void openUserModal(Profile profile) => showAdaptiveBottomSheet(
context: context,
builder: (c) => UserBottomSheet(
profile: profile,
outerContext: context,
),
);
@override
Widget build(BuildContext context) => NewPrivateChatView(this);
}

@ -1,131 +1,254 @@
import 'dart:math';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/new_private_chat/new_private_chat.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/utils/url_launcher.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
class NewPrivateChatView extends StatelessWidget {
final NewPrivateChatController controller;
const NewPrivateChatView(this.controller, {super.key});
static const double _qrCodePadding = 8;
@override
Widget build(BuildContext context) {
final searchResponse = controller.searchResponse;
final qrCodeSize =
min(MediaQuery.of(context).size.width - 16, 256).toDouble();
return Scaffold(
appBar: AppBar(
scrolledUnderElevation: 0,
leading: const Center(child: BackButton()),
title: Text(L10n.of(context)!.newChat),
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
actions: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextButton(
onPressed: () => context.go('/rooms/newgroup'),
child: Text(
L10n.of(context)!.createGroup,
style:
TextStyle(color: Theme.of(context).colorScheme.secondary),
),
),
IconButton(
onPressed:
UrlLauncher(context, AppConfig.startChatTutorial).launchUrl,
icon: const Icon(Icons.info_outlined),
),
],
),
body: Column(
children: [
Expanded(
child: MaxWidthBody(
withFrame: false,
child: Container(
margin: const EdgeInsets.all(_qrCodePadding),
alignment: Alignment.center,
padding: const EdgeInsets.all(_qrCodePadding * 2),
child: Material(
borderRadius: BorderRadius.circular(12),
elevation: 10,
color: Colors.white,
shadowColor: Theme.of(context).appBarTheme.shadowColor,
clipBehavior: Clip.hardEdge,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
//#Pangea - commenting this out because it's not super important and is throwing an error
// QrImageView(
// data:
// 'https://matrix.to/#/${Matrix.of(context).client.userID}',
// version: QrVersions.auto,
// size: qrCodeSize,
// ),
// Pangea#
TextButton.icon(
style: TextButton.styleFrom(
fixedSize:
Size.fromWidth(qrCodeSize - (2 * _qrCodePadding)),
foregroundColor: Colors.black,
body: MaxWidthBody(
withScrolling: false,
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: TextField(
controller: controller.controller,
onChanged: controller.searchUsers,
decoration: InputDecoration(
hintText: 'Search for @users...',
prefixIcon: searchResponse == null
? const Icon(Icons.search_outlined)
: FutureBuilder(
future: searchResponse,
builder: (context, snapshot) {
if (snapshot.connectionState !=
ConnectionState.done) {
return const Padding(
padding: EdgeInsets.all(10.0),
child: SizedBox.square(
dimension: 24,
child: CircularProgressIndicator.adaptive(
strokeWidth: 1,
),
),
);
}
return const Icon(Icons.search_outlined);
},
),
icon: Icon(Icons.adaptive.share_outlined),
label: Text(L10n.of(context)!.shareInviteLink),
onPressed: controller.inviteAction,
),
const SizedBox(height: 8),
if (PlatformInfos.isMobile) ...[
OutlinedButton.icon(
style: OutlinedButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.primaryContainer,
fixedSize: Size.fromWidth(
qrCodeSize - (2 * _qrCodePadding),
),
),
icon: const Icon(Icons.qr_code_scanner_outlined),
label: Text(L10n.of(context)!.scanQrCode),
onPressed: controller.openScannerAction,
suffixIcon: controller.controller.text.isEmpty
? null
: IconButton(
icon: const Icon(Icons.clear_outlined),
onPressed: () {
controller.controller.clear();
controller.searchUsers();
},
),
const SizedBox(height: 8),
],
],
),
),
),
),
),
MaxWidthBody(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Form(
key: controller.formKey,
child: TextFormField(
controller: controller.controller,
autocorrect: false,
textInputAction: TextInputAction.go,
focusNode: controller.textFieldFocus,
onFieldSubmitted: controller.submitAction,
validator: controller.validateForm,
inputFormatters: controller.removeMatrixToFormatters,
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
Expanded(
child: AnimatedCrossFade(
duration: FluffyThemes.animationDuration,
crossFadeState: searchResponse == null
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
firstChild: ListView(
children: [
ListTile(
title: SelectableText.rich(
TextSpan(
children: [
TextSpan(
text: L10n.of(context)!.yourGlobalUserIdIs,
),
TextSpan(
text: Matrix.of(context).client.userID,
style: const TextStyle(
fontWeight: FontWeight.w600,
),
),
],
),
style: TextStyle(
color:
Theme.of(context).colorScheme.onPrimaryContainer,
fontSize: 14,
),
),
trailing: IconButton(
icon: Icon(
Icons.copy_outlined,
size: 16,
color:
Theme.of(context).colorScheme.onPrimaryContainer,
),
onPressed: controller.copyUserId,
),
),
if (PlatformInfos.isMobile)
ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).colorScheme.primaryContainer,
foregroundColor:
Theme.of(context).colorScheme.onPrimaryContainer,
child: const Icon(Icons.qr_code_scanner_outlined),
),
title: Text(L10n.of(context)!.scanQrCode),
onTap: controller.openScannerAction,
),
ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).colorScheme.secondaryContainer,
foregroundColor:
Theme.of(context).colorScheme.onSecondaryContainer,
child: Icon(Icons.adaptive.share_outlined),
),
title: Text(L10n.of(context)!.shareInviteLink),
onTap: controller.inviteAction,
),
ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).colorScheme.tertiaryContainer,
foregroundColor:
Theme.of(context).colorScheme.onTertiaryContainer,
child: const Icon(Icons.group_add_outlined),
),
title: Text(L10n.of(context)!.createGroup),
onTap: () => context.go('/rooms/newgroup'),
),
labelText: L10n.of(context)!.enterInviteLinkOrMatrixId,
hintText: '@username',
prefixText: NewPrivateChatController.prefixNoProtocol,
suffixIcon: IconButton(
icon: const Icon(Icons.send_outlined),
onPressed: controller.submitAction,
const SizedBox(height: 24),
Center(
child: Material(
borderRadius: BorderRadius.circular(12),
elevation: 10,
color: Colors.white,
shadowColor: Theme.of(context).appBarTheme.shadowColor,
clipBehavior: Clip.hardEdge,
// #Pangea - commenting this out because it's not super important and is throwing an error
// child: QrImageView(
// data:
// 'https://matrix.to/#/${Matrix.of(context).client.userID}',
// version: QrVersions.auto,
// size: qrCodeSize,
// ),
// Pangea#
),
),
),
],
),
secondChild: FutureBuilder(
future: searchResponse,
builder: (context, snapshot) {
final result = snapshot.data;
final error = snapshot.error;
if (error != null) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
error.toLocalizedString(context),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).colorScheme.error,
),
),
const SizedBox(height: 12),
OutlinedButton.icon(
onPressed: controller.searchUsers,
icon: const Icon(Icons.refresh_outlined),
label: Text(L10n.of(context)!.tryAgain),
),
],
);
}
if (result == null) {
return const Center(
child: CircularProgressIndicator.adaptive(),
);
}
if (result.isEmpty) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.search_outlined, size: 86),
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
L10n.of(context)!.noUsersFoundWithQuery(
controller.controller.text,
),
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
textAlign: TextAlign.center,
),
),
],
);
}
return ListView.builder(
itemCount: result.length,
itemBuilder: (context, i) {
final contact = result[i];
final displayname = contact.displayName ??
contact.userId.localpart ??
contact.userId;
return ListTile(
leading: Avatar(
name: displayname,
mxContent: contact.avatarUrl,
presenceUserId: contact.userId,
),
title: Text(displayname),
subtitle: Text(contact.userId),
onTap: () => controller.openUserModal(contact),
);
},
);
},
),
),
),
),
],
],
),
),
);
}

@ -33,6 +33,8 @@ class _ImportEmoteArchiveDialogState extends State<ImportEmoteArchiveDialog> {
bool _loading = false;
double _progress = 0;
@override
void initState() {
_importFileMap();
@ -44,7 +46,11 @@ class _ImportEmoteArchiveDialogState extends State<ImportEmoteArchiveDialog> {
return AlertDialog(
title: Text(L10n.of(context)!.importEmojis),
content: _loading
? const Center(child: CircularProgressIndicator())
? Center(
child: CircularProgressIndicator(
value: _progress,
),
)
: SingleChildScrollView(
child: Wrap(
alignment: WrapAlignment.spaceEvenly,
@ -97,6 +103,7 @@ class _ImportEmoteArchiveDialogState extends State<ImportEmoteArchiveDialog> {
Future<void> _addEmotePack() async {
setState(() {
_loading = true;
_progress = 0;
});
final imports = _importMap;
final successfulUploads = <String>{};
@ -134,52 +141,56 @@ class _ImportEmoteArchiveDialogState extends State<ImportEmoteArchiveDialog> {
}
for (final entry in imports.entries) {
setState(() {
_progress += 1 / imports.length;
});
final file = entry.key;
final imageCode = entry.value;
// try {
var mxcFile = MatrixImageFile(
bytes: file.content,
name: file.name,
);
try {
mxcFile = (await mxcFile.generateThumbnail(
nativeImplementations: ClientManager.nativeImplementations,
))!;
} catch (e, s) {
Logs().w('Unable to create thumbnail', e, s);
}
final uri = await Matrix.of(context).client.uploadContent(
mxcFile.bytes,
filename: mxcFile.name,
contentType: mxcFile.mimeType,
);
final info = <String, dynamic>{
...mxcFile.info,
};
var mxcFile = MatrixImageFile(
bytes: file.content,
name: file.name,
);
// normalize width / height to 256, required for stickers
if (info['w'] is int && info['h'] is int) {
final ratio = info['w'] / info['h'];
if (info['w'] > info['h']) {
info['w'] = 256;
info['h'] = (256.0 / ratio).round();
final thumbnail = (await mxcFile.generateThumbnail(
nativeImplementations: ClientManager.nativeImplementations,
));
if (thumbnail == null) {
Logs().w('Unable to create thumbnail');
} else {
info['h'] = 256;
info['w'] = (ratio * 256.0).round();
mxcFile = thumbnail;
}
}
widget.controller.pack!.images[imageCode] =
ImagePackImageContent.fromJson(<String, dynamic>{
'url': uri.toString(),
'info': info,
});
successfulUploads.add(file.name);
/*} catch (e) {
final uri = await Matrix.of(context).client.uploadContent(
mxcFile.bytes,
filename: mxcFile.name,
contentType: mxcFile.mimeType,
);
Logs().d('Could not upload emote $imageCode');
}*/
final info = <String, dynamic>{
...mxcFile.info,
};
// normalize width / height to 256, required for stickers
if (info['w'] is int && info['h'] is int) {
final ratio = info['w'] / info['h'];
if (info['w'] > info['h']) {
info['w'] = 256;
info['h'] = (256.0 / ratio).round();
} else {
info['h'] = 256;
info['w'] = (ratio * 256.0).round();
}
}
widget.controller.pack!.images[imageCode] =
ImagePackImageContent.fromJson(<String, dynamic>{
'url': uri.toString(),
'info': info,
});
successfulUploads.add(file.name);
} catch (e) {
Logs().d('Could not upload emote $imageCode');
}
}
await widget.controller.save(context);
@ -188,6 +199,7 @@ class _ImportEmoteArchiveDialogState extends State<ImportEmoteArchiveDialog> {
);
_loading = false;
_progress = 0;
// in case we have unhandled / duplicated emotes left, don't pop
if (mounted) setState(() {});

@ -307,6 +307,8 @@ class EmotesSettingsController extends State<EmotesSettings> {
await showDialog(
context: context,
// breaks [Matrix.of] calls otherwise
useRootNavigator: false,
builder: (context) => ImportEmoteArchiveDialog(
controller: this,
archive: archive,

@ -1,5 +1,7 @@
import 'package:fluffychat/utils/date_time_extension.dart';
import 'package:fluffychat/utils/fluffy_share.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/presence_builder.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
@ -29,7 +31,60 @@ class UserBottomSheetView extends StatelessWidget {
leading: CloseButton(
onPressed: Navigator.of(context, rootNavigator: false).pop,
),
title: Text(displayname.trim().split(' ').first),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(displayname),
PresenceBuilder(
userId: userId,
client: client,
builder: (context, presence) {
if (presence == null ||
(presence.presence == PresenceType.offline &&
presence.lastActiveTimestamp == null)) {
return const SizedBox.shrink();
}
final dotColor = presence.presence.isOnline
? Colors.green
: presence.presence.isUnavailable
? Colors.red
: Colors.grey;
final lastActiveTimestamp = presence.lastActiveTimestamp;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 8,
height: 8,
margin: const EdgeInsets.only(right: 8),
decoration: BoxDecoration(
color: dotColor,
borderRadius: BorderRadius.circular(16),
),
),
if (presence.currentlyActive == true)
Text(
L10n.of(context)!.currentlyActive,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodySmall,
)
else if (lastActiveTimestamp != null)
Text(
L10n.of(context)!.lastActiveAgo(
lastActiveTimestamp.localizedTimeShort(context),
),
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodySmall,
),
],
);
},
),
],
),
actions: [
if (userId != client.userID &&
!client.ignoredUsers.contains(userId))

@ -6,7 +6,6 @@ import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:fluffychat/widgets/public_room_bottom_sheet.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
@ -17,10 +16,16 @@ import 'package:url_launcher/url_launcher_string.dart';
import 'platform_infos.dart';
class UrlLauncher {
/// The url to open.
final String? url;
/// The visible name in the GUI. For example the name of a markdown link
/// which may differ from the actual url to open.
final String? name;
final BuildContext context;
const UrlLauncher(this.context, this.url);
const UrlLauncher(this.context, this.url, [this.name]);
void launchUrl() async {
if (url!.toLowerCase().startsWith(AppConfig.deepLinkPrefix) ||
@ -37,35 +42,20 @@ class UrlLauncher {
);
return;
}
final consent = await showModalActionSheet<_LaunchUrlResponse>(
context: context,
title: url,
style: AdaptiveStyle.material,
actions: [
SheetAction(
key: _LaunchUrlResponse.copy,
icon: Icons.copy_outlined,
label: L10n.of(context)!.copy,
),
SheetAction(
key: _LaunchUrlResponse.launch,
icon: Icons.launch_outlined,
label: L10n.of(context)!.openLinkInBrowser,
),
],
);
if (consent == _LaunchUrlResponse.copy) {
await Clipboard.setData(ClipboardData(text: uri.toString()));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.copiedToClipboard),
),
if (name != null && url != name) {
// If there is a name which differs from the url, we need to make sure
// that the user can see the actual url before opening the browser.
final consent = await showOkCancelAlertDialog(
context: context,
title: L10n.of(context)!.openLinkInBrowser,
message: url,
okLabel: L10n.of(context)!.yes,
cancelLabel: L10n.of(context)!.cancel,
);
return;
if (consent != OkCancelResult.ok) return;
}
if (consent != _LaunchUrlResponse.launch) return;
if (!{'https', 'http'}.contains(uri.scheme)) {
// just launch non-https / non-http uris directly
@ -240,8 +230,3 @@ class UrlLauncher {
}
}
}
enum _LaunchUrlResponse {
launch,
copy,
}

@ -11,6 +11,8 @@ class Avatar extends StatelessWidget {
static const double defaultSize = 44;
final Client? client;
final double fontSize;
final String? presenceUserId;
final Color? presenceBackgroundColor;
//#Pangea
final IconData? littleIcon;
// Pangea#
@ -22,6 +24,8 @@ class Avatar extends StatelessWidget {
this.onTap,
this.client,
this.fontSize = 18,
this.presenceUserId,
this.presenceBackgroundColor,
//#Pangea
this.littleIcon,
// Pangea#
@ -53,27 +57,10 @@ class Avatar extends StatelessWidget {
);
final borderRadius = BorderRadius.circular(size / 2);
// #Pangea
// final container = ClipRRect(
// borderRadius: borderRadius,
// child: Container(
// width: size,
// height: size,
// color: noPic
// ? name?.lightColorAvatar
// : Theme.of(context).secondaryHeaderColor,
// child: noPic
// ? textWidget
// : MxcImage(
// key: Key(mxContent.toString()),
// uri: mxContent,
// fit: BoxFit.cover,
// width: size,
// height: size,
// placeholder: (_) => textWidget,
// cacheKey: mxContent.toString(),
// ),
// ),
// );
// final presenceUserId = this.presenceUserId;
// final color =
// noPic ? name?.lightColorAvatar : Theme.of(context).secondaryHeaderColor;
// Pangea#
final container = Stack(
children: [
ClipRRect(
@ -97,6 +84,7 @@ class Avatar extends StatelessWidget {
),
),
),
// #Pangea
if (littleIcon != null)
Positioned(
bottom: 0,
@ -117,9 +105,47 @@ class Avatar extends StatelessWidget {
),
),
),
// PresenceBuilder(
// client: client,
// userId: presenceUserId,
// builder: (context, presence) {
// if (presence == null ||
// (presence.presence == PresenceType.offline &&
// presence.lastActiveTimestamp == null)) {
// return const SizedBox.shrink();
// }
// final dotColor = presence.presence.isOnline
// ? Colors.green
// : presence.presence.isUnavailable
// ? Colors.red
// : Colors.grey;
// return Positioned(
// bottom: -4,
// right: -4,
// child: Container(
// width: 16,
// height: 16,
// decoration: BoxDecoration(
// color: presenceBackgroundColor ??
// Theme.of(context).colorScheme.background,
// borderRadius: BorderRadius.circular(32),
// ),
// alignment: Alignment.center,
// child: Container(
// width: 8,
// height: 8,
// decoration: BoxDecoration(
// color: dotColor,
// borderRadius: BorderRadius.circular(16),
// ),
// ),
// ),
// );
// },
// ),
// Pangea#
],
);
// Pangea#
if (onTap == null) return container;
return InkWell(
onTap: onTap,

@ -448,7 +448,6 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
final foreground = state != AppLifecycleState.detached &&
state != AppLifecycleState.paused;
client.backgroundSync = foreground;
client.syncPresence = foreground ? null : PresenceType.unavailable;
client.requestHistoryOnLimitedTimeline = !foreground;
}

@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/widgets/matrix.dart';
class PresenceBuilder extends StatelessWidget {
final Widget Function(BuildContext context, CachedPresence? presence) builder;
final String? userId;
final Client? client;
const PresenceBuilder({
required this.builder,
this.userId,
this.client,
super.key,
});
@override
Widget build(BuildContext context) {
final userId = this.userId;
if (userId == null) return builder(context, null);
final client = this.client ?? Matrix.of(context).client;
return StreamBuilder(
stream: client.onPresenceChanged.stream
.where((cachedPresence) => cachedPresence.userid == userId),
builder: (context, snapshot) => builder(
context,
snapshot.data ?? client.presences[userId],
),
);
}
}

@ -5,6 +5,7 @@
import FlutterMacOS
import Foundation
import appkit_ui_element_colors
import audio_session
import connectivity_plus
import desktop_drop
@ -37,10 +38,12 @@ import shared_preferences_foundation
import sqflite
import url_launcher_macos
import video_compress
import video_player_avfoundation
import wakelock_plus
import window_to_front
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppkitUiElementColorsPlugin.register(with: registry.registrar(forPlugin: "AppkitUiElementColorsPlugin"))
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
@ -73,6 +76,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
VideoCompressPlugin.register(with: registry.registrar(forPlugin: "VideoCompressPlugin"))
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
WindowToFrontPlugin.register(with: registry.registrar(forPlugin: "WindowToFrontPlugin"))
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -7,29 +7,29 @@ environment:
sdk: ">=3.0.0 <4.0.0"
dependencies:
adaptive_dialog: ^1.9.0+2
animations: ^2.0.7
archive: ^3.3.9
adaptive_dialog: ^1.10.0
animations: ^2.0.8
archive: ^3.4.9
async: ^2.11.0
badges: ^3.1.1
blurhash_dart: ^1.1.0
badges: ^3.1.2
blurhash_dart: ^1.2.1
callkeep: ^0.3.2
chewie: ^1.3.6
collection: ^1.16.0
chewie: ^1.7.1
collection: ^1.17.2
connectivity_plus: ^3.0.2
country_picker: ^2.0.20
csv: ^5.0.2
cupertino_icons: any
desktop_drop: ^0.4.0
desktop_drop: ^0.4.4
desktop_lifecycle: ^0.1.0
desktop_notifications: ^0.6.3
device_info_plus: ^9.0.2
dynamic_color: ^1.6.0
emoji_picker_flutter: ^1.5.1
device_info_plus: ^9.1.0
dynamic_color: ^1.6.8
emoji_picker_flutter: ^1.6.3
emoji_proposal: ^0.0.1
emojis: ^0.9.9
#fcm_shared_isolate: ^0.1.0
file_picker: ^5.3.0
file_picker: ^6.1.1
fl_chart: ^0.61.0
firebase_analytics: ^10.2.1
firebase_core: ^2.10.0
@ -56,15 +56,15 @@ dependencies:
flutter_olm: ^1.2.0
flutter_openssl_crypto: ^0.1.0
flutter_ringtone_player: ^3.1.1
flutter_secure_storage: ^8.0.0
flutter_secure_storage: ^9.0.0
flutter_svg: ^2.0.0+1
flutter_typeahead: ^4.3.2
flutter_typeahead: ^4.8.0
flutter_web_auth_2: ^3.0.3
flutter_webrtc: ^0.9.37
flutter_webrtc: ^0.9.46
future_loading_dialog: ^0.3.0
geolocator: ^7.6.2
get_storage: ^2.1.1
go_router: ^12.0.1
go_router: ^12.1.1
hive: ^2.2.3
hive_flutter: ^1.1.0
http: ^0.13.4
@ -92,22 +92,22 @@ dependencies:
qr_code_scanner: ^1.0.0
qr_flutter: ^4.0.0
receive_sharing_intent: ^1.4.5
record: ^4.4.4 # Upgrade to 5 currently breaks playing on iOS
record: 4.4.4 # Upgrade to 5 currently breaks playing on iOS
scroll_to_index: ^3.0.1
sentry_flutter: ^7.4.0
share_plus: ^7.0.0
share_plus: ^7.2.1
shared_preferences: ^2.2.0 # Pinned because https://github.com/flutter/flutter/issues/118401
slugify: ^2.0.0
swipe_to_action: ^0.2.0
syncfusion_flutter_xlsio: ^22.2.9
tor_detector_web: ^1.1.0
uni_links: ^0.5.1
unifiedpush: ^5.0.0
universal_html: ^2.0.8
url_launcher: ^6.0.20
vibration: ^1.7.4-nullsafety.0
unifiedpush: ^5.0.1
universal_html: ^2.2.4
url_launcher: ^6.2.1
vibration: ^1.8.3
video_compress: ^3.1.1
video_player: ^2.2.18
video_player: ^2.8.1
wakelock_plus: ^1.1.3
webrtc_interface: ^1.0.13
@ -179,6 +179,18 @@ msix_config:
install_certificate: false
dependency_overrides:
# https://github.com/fluttercommunity/flutter_blurhash/pull/58
flutter_blurhash:
git:
url: https://github.com/Craftplacer/flutter_blurhash.git
ref: eb9565f9d5731d4729bd7605510cec0f9e172e5f
# https://github.com/simpleclub-extended/flutter_math_fork/pull/87
flutter_math_fork:
git:
url: https://github.com/The-Redhat/flutter_math_fork.git
ref: 3442b36a436880ce1c023e25c868d6f4004c4c24
# Until https://github.com/mogol/flutter_secure_storage/issues/616 is fixed
flutter_secure_storage_linux: 1.1.3
geolocator_android:
hosted:
name: geolocator_android

@ -2,7 +2,7 @@ diff --git a/android/app/build.gradle b/android/app/build.gradle
index 001fbd72..339b35af 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -68,6 +68,10 @@ android {
@@ -70,6 +70,10 @@
}
release {
signingConfig signingConfigs.release
@ -12,7 +12,7 @@ index 001fbd72..339b35af 100644
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
// https://stackoverflow.com/a/77494454/8222484
@@ -78,8 +82,11 @@ flutter {
dependencies {
@ -149,11 +149,11 @@ index 6999d0b8..b2c9144f 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -26,7 +26,7 @@ dependencies:
emoji_picker_flutter: ^1.5.1
emoji_picker_flutter: ^1.6.3
emoji_proposal: ^0.0.1
emojis: ^0.9.9
- #fcm_shared_isolate: ^0.1.0
+ fcm_shared_isolate: ^0.1.0
file_picker: ^6.0.0
file_picker: ^6.1.1
flutter:
sdk: flutter

@ -1,5 +1,7 @@
#!/bin/sh -ve
git apply ./scripts/enable-android-google-services.patch
rm -rf fonts/NotoEmoji
yq -i 'del( .flutter.fonts[] | select(.family == "NotoEmoji") )' pubspec.yaml
flutter clean
flutter pub get
cd ios

Loading…
Cancel
Save