diff --git a/.github/workflows/sanity_test.yml b/.github/workflows/sanity_test.yml
index 53b70276c5..213c43b716 100644
--- a/.github/workflows/sanity_test.yml
+++ b/.github/workflows/sanity_test.yml
@@ -56,10 +56,10 @@ jobs:
java-version: '11'
- name: Run sanity tests on API ${{ matrix.api-level }}
uses: reactivecircus/android-emulator-runner@v2
- continue-on-error: true # allow pipeline to upload failure results
with:
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
api-level: ${{ matrix.api-level }}
+ profile: 24 # Pixel 5
emulator-build: 7425822 # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
script: |
adb root
@@ -67,13 +67,12 @@ jobs:
touch emulator.log
chmod 777 emulator.log
adb logcat >> emulator.log &
- ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || adb pull storage/emulated/0/Pictures/failure_screenshots
-
+ ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || adb pull storage/emulated/0/Pictures/failure_screenshots && exit 1
- name: Upload Failing Test Report Log
- if: failure()
uses: actions/upload-artifact@v2
+ if: failure()
with:
name: sanity-error-results
path: |
emulator.log
- failure_screenshots/
+ failure_screenshots/
\ No newline at end of file
diff --git a/.github/workflows/sync-from-external-sources.yml b/.github/workflows/sync-from-external-sources.yml
index 6a4f8ef147..2da8e10542 100644
--- a/.github/workflows/sync-from-external-sources.yml
+++ b/.github/workflows/sync-from-external-sources.yml
@@ -7,6 +7,8 @@ on:
jobs:
sync-emojis:
runs-on: ubuntu-latest
+ # Skip in forks
+ if: github.repository == 'vector-im/element-android'
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
@@ -39,6 +41,8 @@ jobs:
sync-sas-strings:
runs-on: ubuntu-latest
+ # Skip in forks
+ if: github.repository == 'vector-im/element-android'
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
diff --git a/.github/workflows/triage-move-labelled.yml b/.github/workflows/triage-move-labelled.yml
index bbfab9b0a9..f910cdf7ea 100644
--- a/.github/workflows/triage-move-labelled.yml
+++ b/.github/workflows/triage-move-labelled.yml
@@ -99,3 +99,26 @@ jobs:
env:
PROJECT_ID: "PN_kwDOAM0swc2KCw"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
+
+ move_threads_issues:
+ name: Move A-Threads to Thread board
+ runs-on: ubuntu-latest
+ if: >
+ contains(github.event.issue.labels.*.name, 'A-Threads')
+ steps:
+ - uses: octokit/graphql-action@v2.x
+ with:
+ headers: '{"GraphQL-Features": "projects_next_graphql"}'
+ query: |
+ mutation add_to_project($projectid:String!,$contentid:String!) {
+ addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
+ projectNextItem {
+ id
+ }
+ }
+ }
+ projectid: ${{ env.PROJECT_ID }}
+ contentid: ${{ github.event.issue.node_id }}
+ env:
+ PROJECT_ID: "PN_kwDOAM0swc0rRA"
+ GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
diff --git a/CHANGES.md b/CHANGES.md
index 9590adc060..561bae9b9f 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,35 @@
+Changes in Element v1.3.8 (2021-11-17)
+======================================
+
+Features ✨
+----------
+ - Android 12 support ([#4433](https://github.com/vector-im/element-android/issues/4433))
+ - Make notification text spoiler aware ([#3477](https://github.com/vector-im/element-android/issues/3477))
+ - Poll Feature - Create Poll Screen (Disabled for now) ([#4367](https://github.com/vector-im/element-android/issues/4367))
+ - Adds support for images inside message notifications ([#4402](https://github.com/vector-im/element-android/issues/4402))
+
+Bugfixes 🐛
+----------
+ - Render markdown in room list ([#452](https://github.com/vector-im/element-android/issues/452))
+ - Fix incorrect cropping of conversation icons ([#4424](https://github.com/vector-im/element-android/issues/4424))
+ - Fix potential NullPointerException crashes in Room and User account data sources ([#4428](https://github.com/vector-im/element-android/issues/4428))
+ - Unable to establish Olm outbound session from fallback key ([#4446](https://github.com/vector-im/element-android/issues/4446))
+ - Fixes intermittent crash on sign out due to the session being incorrectly recreated whilst being closed ([#4480](https://github.com/vector-im/element-android/issues/4480))
+
+SDK API changes ⚠️
+------------------
+ - Add content scanner API from MSC1453
+ API documentation : https://github.com/matrix-org/matrix-content-scanner#api ([#4392](https://github.com/vector-im/element-android/issues/4392))
+ - Breaking SDK API change to PushRuleListener, the separated callbacks have been merged into one with a data class which includes all the previously separated push information ([#4401](https://github.com/vector-im/element-android/issues/4401))
+
+Other changes
+-------------
+ - Finish migration from RxJava to Flow ([#4219](https://github.com/vector-im/element-android/issues/4219))
+ - Remove redundant text in feature request issue form ([#4257](https://github.com/vector-im/element-android/issues/4257))
+ - Add and improve issue triage workflows ([#4435](https://github.com/vector-im/element-android/issues/4435))
+ - Update issue template to bring in line with element-web ([#4452](https://github.com/vector-im/element-android/issues/4452))
+
+
Changes in Element v1.3.7 (2021-11-04)
======================================
diff --git a/README.md b/README.md
index e89fb15010..a085bf7da1 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,20 @@ At each Element release, the SDK module is copied to a dedicated repository: htt
The version 1.0.0 of Element still misses some features which was previously included in Riot-Android.
The team will work to add them on a regular basis.
+# Releases to app stores
+
+There is some delay between when a release is created and when it appears in the app stores (Google Play Store and F-Droid). Here are some of the reasons:
+
+* Not all versioned releases that appear on GitHub are considered stable. Each release is first considered beta: this continues for at least two days. If the release is stable (no serious issues or crashes are reported), then it is released as a production release in Google Play Store, and a request is sent to F-Droid too.
+* Each release on the Google Play Store undergoes review by Google before it comes out. This can take an unpredictable amount of time. In some cases it has taken several weeks.
+* In order for F-Droid to guarantee that the app you receive exactly matches the public source code, they build releases themselves. When a release is considered stable, Element staff inform the F-Droid maintainers and it is added to the build queue. Depending on the load on F-Droid's infrastructure, it can take some time for releases to be built. This always takes at least 24 hours, and can take several days.
+
+If you would like to receive releases more quickly (bearing in mind that they may not be stable) you have a number of options:
+
+1. [Sign up to receive beta releases](https://play.google.com/apps/testing/im.vector.app) via the Google Play Store.
+2. Install a [release APK](https://github.com/vector-im/element-android/releases) directly - download the relevant .apk file and allow installing from untrusted sources in your device settings. Note: these releases are the Google Play version, which depend on some Google services. If you prefer to avoid that, try the latest dev builds, and choose the F-Droid version.
+3. If you're really brave, install the [very latest dev build](https://buildkite.com/matrix-dot-org/element-android/builds/latest?branch=develop&state=passed) - click on *Assemble (GPlay or FDroid) Debug version* then on *Artifacts*.
+
## Contributing
Please refer to [CONTRIBUTING.md](https://github.com/vector-im/element-android/blob/develop/CONTRIBUTING.md) if you want to contribute on Matrix Android projects!
diff --git a/build.gradle b/build.gradle
index e9045b99c7..4c3734892d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -69,9 +69,9 @@ allprojects {
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
// Jitsi repo
maven {
- url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-3.1.0"
+ url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-3.10.0"
// Note: to test Jitsi release you can use a local file like this:
- // url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.1.0"
+ // url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.10.0"
}
google()
mavenCentral()
diff --git a/changelog.d/3449.bugfix b/changelog.d/3449.bugfix
new file mode 100644
index 0000000000..a4385fda2e
--- /dev/null
+++ b/changelog.d/3449.bugfix
@@ -0,0 +1 @@
+Fixes left over text when inserting emojis via the ':' menu and replaces the last typed ':' rather than the one at the end of the message
\ No newline at end of file
diff --git a/changelog.d/3833.bugfix b/changelog.d/3833.bugfix
new file mode 100644
index 0000000000..7d25fb2aad
--- /dev/null
+++ b/changelog.d/3833.bugfix
@@ -0,0 +1 @@
+Fixing queued voice message failing to send or retry
\ No newline at end of file
diff --git a/changelog.d/4022.bugfix b/changelog.d/4022.bugfix
new file mode 100644
index 0000000000..517926e018
--- /dev/null
+++ b/changelog.d/4022.bugfix
@@ -0,0 +1 @@
+Keeping device screen on whilst recording and playing back voice messages
\ No newline at end of file
diff --git a/changelog.d/4067.bugfix b/changelog.d/4067.bugfix
new file mode 100644
index 0000000000..63d62df840
--- /dev/null
+++ b/changelog.d/4067.bugfix
@@ -0,0 +1 @@
+Allow voice messages to continue recording during device rotation
\ No newline at end of file
diff --git a/changelog.d/4144.bugfix b/changelog.d/4144.bugfix
new file mode 100644
index 0000000000..1168245ecc
--- /dev/null
+++ b/changelog.d/4144.bugfix
@@ -0,0 +1 @@
+Allowing users to hang up VOIP calls during the initialisation phase (avoids getting stuck in the call screen if something goes wrong)
\ No newline at end of file
diff --git a/changelog.d/4219.misc b/changelog.d/4219.misc
deleted file mode 100644
index 69950e0915..0000000000
--- a/changelog.d/4219.misc
+++ /dev/null
@@ -1 +0,0 @@
-Finish migration from RxJava to Flow
\ No newline at end of file
diff --git a/changelog.d/4246.feature b/changelog.d/4246.feature
new file mode 100644
index 0000000000..6695edf590
--- /dev/null
+++ b/changelog.d/4246.feature
@@ -0,0 +1 @@
+Make Element Android Thread aware
diff --git a/changelog.d/4257.misc b/changelog.d/4257.misc
deleted file mode 100644
index fa0657bfea..0000000000
--- a/changelog.d/4257.misc
+++ /dev/null
@@ -1 +0,0 @@
-Remove redundant text in feature request issue form
diff --git a/changelog.d/4338.bugfix b/changelog.d/4338.bugfix
new file mode 100644
index 0000000000..539c32672c
--- /dev/null
+++ b/changelog.d/4338.bugfix
@@ -0,0 +1 @@
+Make the verification shields the same in Element Web and Element Android
\ No newline at end of file
diff --git a/changelog.d/4343.bugfix b/changelog.d/4343.bugfix
new file mode 100644
index 0000000000..a516af351b
--- /dev/null
+++ b/changelog.d/4343.bugfix
@@ -0,0 +1 @@
+Fix a display issue in the composer when the replied message is changed.
diff --git a/changelog.d/4367.feature b/changelog.d/4367.feature
deleted file mode 100644
index c001cc778f..0000000000
--- a/changelog.d/4367.feature
+++ /dev/null
@@ -1 +0,0 @@
-Poll Feature - Create Poll Screen (Disabled for now)
\ No newline at end of file
diff --git a/changelog.d/4401.removal b/changelog.d/4401.removal
deleted file mode 100644
index ce58372a18..0000000000
--- a/changelog.d/4401.removal
+++ /dev/null
@@ -1 +0,0 @@
-Breaking SDK API change to PushRuleListener, the separated callbacks have been merged into one with a data class which includes all the previously separated push information
diff --git a/changelog.d/4402.feature b/changelog.d/4402.feature
deleted file mode 100644
index 29b9f9a337..0000000000
--- a/changelog.d/4402.feature
+++ /dev/null
@@ -1 +0,0 @@
-Adds support for images inside message notifications
\ No newline at end of file
diff --git a/changelog.d/4424.bugfix b/changelog.d/4424.bugfix
deleted file mode 100644
index 679440d204..0000000000
--- a/changelog.d/4424.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Fix incorrect cropping of conversation icons
\ No newline at end of file
diff --git a/changelog.d/4428.bugfix b/changelog.d/4428.bugfix
deleted file mode 100644
index 381f246fee..0000000000
--- a/changelog.d/4428.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Fix potential NullPointerException crashes in Room and User account data sources
diff --git a/changelog.d/4435.misc b/changelog.d/4435.misc
deleted file mode 100644
index 14e2425d79..0000000000
--- a/changelog.d/4435.misc
+++ /dev/null
@@ -1 +0,0 @@
-Add and improve issue triage workflows
diff --git a/changelog.d/4446.bugfix b/changelog.d/4446.bugfix
deleted file mode 100644
index 140e18a3ba..0000000000
--- a/changelog.d/4446.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Unable to establish Olm outbound session from fallback key
\ No newline at end of file
diff --git a/changelog.d/4452.misc b/changelog.d/4452.misc
deleted file mode 100644
index 10d98c32c3..0000000000
--- a/changelog.d/4452.misc
+++ /dev/null
@@ -1 +0,0 @@
-Update issue template to bring in line with element-web
diff --git a/changelog.d/4488.bugfix b/changelog.d/4488.bugfix
new file mode 100644
index 0000000000..a0b4aa661e
--- /dev/null
+++ b/changelog.d/4488.bugfix
@@ -0,0 +1 @@
+Dismissing the Fdroid variant Listening for notifications on sign out, fixes crash when tapping the notification when signed out
\ No newline at end of file
diff --git a/changelog.d/4504.misc b/changelog.d/4504.misc
new file mode 100644
index 0000000000..1f7741618e
--- /dev/null
+++ b/changelog.d/4504.misc
@@ -0,0 +1 @@
+Upgrade Jitsi lib (and so webrtc) from Jitsi android-sdk-3.1.0 to android-sdk-3.10.0
\ No newline at end of file
diff --git a/changelog.d/4507.misc b/changelog.d/4507.misc
new file mode 100644
index 0000000000..a1f231c82c
--- /dev/null
+++ b/changelog.d/4507.misc
@@ -0,0 +1 @@
+Improve crypto logs to help debug decryption failures
\ No newline at end of file
diff --git a/changelog.d/4515.misc b/changelog.d/4515.misc
new file mode 100644
index 0000000000..f47ace25d4
--- /dev/null
+++ b/changelog.d/4515.misc
@@ -0,0 +1 @@
+Voice recording mic button refactor with small animation tweaks in preparation for voice drafts
\ No newline at end of file
diff --git a/changelog.d/4520.bugfix b/changelog.d/4520.bugfix
new file mode 100644
index 0000000000..58314025f1
--- /dev/null
+++ b/changelog.d/4520.bugfix
@@ -0,0 +1 @@
+Fix a crash when displaying the bootstrap bottom sheet
\ No newline at end of file
diff --git a/changelog.d/4539.bugfix b/changelog.d/4539.bugfix
new file mode 100644
index 0000000000..0e133cbadc
--- /dev/null
+++ b/changelog.d/4539.bugfix
@@ -0,0 +1 @@
+Remove duplicated settings declaration
diff --git a/changelog.d/4552.bugfix b/changelog.d/4552.bugfix
new file mode 100644
index 0000000000..188e5fb1f6
--- /dev/null
+++ b/changelog.d/4552.bugfix
@@ -0,0 +1 @@
+Fixes .ogg files failing to upload to rooms
\ No newline at end of file
diff --git a/dependencies.gradle b/dependencies.gradle
index 85f00f95ea..1a04fe07ea 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -11,7 +11,7 @@ def gradle = "7.0.3"
// Ref: https://kotlinlang.org/releases.html
def kotlin = "1.5.31"
def kotlinCoroutines = "1.5.2"
-def dagger = "2.40.1"
+def dagger = "2.40.3"
def retrofit = "2.9.0"
def arrow = "0.8.2"
def markwon = "4.6.2"
@@ -26,7 +26,7 @@ def jjwt = "0.11.2"
def vanniktechEmoji = "0.8.0"
// Testing
-def mockk = "1.12.0"
+def mockk = "1.12.1"
def espresso = "3.4.0"
def androidxTest = "1.4.0"
@@ -45,13 +45,13 @@ ext.libs = [
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
],
androidx : [
- 'appCompat' : "androidx.appcompat:appcompat:1.3.1",
+ 'appCompat' : "androidx.appcompat:appcompat:1.4.0",
'core' : "androidx.core:core-ktx:1.7.0",
'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1",
'exifinterface' : "androidx.exifinterface:exifinterface:1.3.3",
- 'fragmentKtx' : "androidx.fragment:fragment-ktx:1.3.6",
- 'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.1",
- 'work' : "androidx.work:work-runtime-ktx:2.7.0",
+ 'fragmentKtx' : "androidx.fragment:fragment-ktx:1.4.0",
+ 'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.2",
+ 'work' : "androidx.work:work-runtime-ktx:2.7.1",
'autoFill' : "androidx.autofill:autofill:1.1.0",
'preferenceKtx' : "androidx.preference:preference-ktx:1.1.1",
'junit' : "androidx.test.ext:junit:1.1.3",
diff --git a/docs/jitsi.md b/docs/jitsi.md
index 389e7d71ec..55cedaedb1 100644
--- a/docs/jitsi.md
+++ b/docs/jitsi.md
@@ -18,7 +18,7 @@ The generated maven repository is then host in the project https://github.com/ve
Update the script `./tools/jitsi/build_jisti_libs.sh` with the tag of the project `https://github.com/jitsi/jitsi-meet`.
-Currently we are building the version with the tag `android-sdk-3.1.0`.
+Currently we are building the version with the tag `android-sdk-3.10.0`.
### Run the build script
@@ -35,7 +35,7 @@ It will build the Jitsi Meet Android library and put every generated files in th
- Update the file `./build.gradle` to use the previously created local Maven repository. Currently we have this line:
```groovy
-url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-3.1.0"
+url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-3.10.0"
```
You can uncomment and update the line starting with `// url "file://...` and comment the line starting with `url`, to test the library using the locally generated Maven repository.
@@ -43,13 +43,13 @@ You can uncomment and update the line starting with `// url "file://...` and com
- Update the dependency of the Jitsi Meet library in the file `./vector/build.gradle`. Currently we have this line:
```groovy
-implementation('org.jitsi.react:jitsi-meet-sdk:3.1.0')
+implementation('org.jitsi.react:jitsi-meet-sdk:3.10.0')
```
- Update the dependency of the WebRTC library in the file `./vector/build.gradle`. Currently we have this line:
```groovy
-implementation('com.facebook.react:react-native-webrtc:1.87.3-jitsi-6624067@aar')
+implementation('com.facebook.react:react-native-webrtc:1.92.1-jitsi-9093212@aar')
```
- Perform a gradle sync and build the project
@@ -74,7 +74,7 @@ If all the tests are passed, you can export the generated Jitsi library to our M
- Update the file `./build.gradle` to use the previously created Maven repository. Currently we have this line:
```groovy
-url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-3.1.0"
+url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-3.10.0"
```
- Build the project and perform the sanity tests again.
diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40103050.txt b/fastlane/metadata/android/cs-CZ/changelogs/40103050.txt
new file mode 100644
index 0000000000..6aee9110ef
--- /dev/null
+++ b/fastlane/metadata/android/cs-CZ/changelogs/40103050.txt
@@ -0,0 +1,2 @@
+Hlavní změny v této verzi: Přidání podpory přítomnosti pro místnost s přímými zprávami (poznámka: přítomnost je na matrix.org zakázána). Opět přidána podpora Android Auto.
+Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.3.5
diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40103060.txt b/fastlane/metadata/android/cs-CZ/changelogs/40103060.txt
new file mode 100644
index 0000000000..ef29377a46
--- /dev/null
+++ b/fastlane/metadata/android/cs-CZ/changelogs/40103060.txt
@@ -0,0 +1,2 @@
+Hlavní změny v této verzi: Přidání podpory přítomnosti pro místnost s přímými zprávami (poznámka: přítomnost je na matrix.org zakázána). Opět přidána podpora Android Auto.
+Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.3.6
diff --git a/fastlane/metadata/android/en-US/changelogs/40103070.txt b/fastlane/metadata/android/en-US/changelogs/40103070.txt
index ad225133c2..9a7030728a 100644
--- a/fastlane/metadata/android/en-US/changelogs/40103070.txt
+++ b/fastlane/metadata/android/en-US/changelogs/40103070.txt
@@ -1,2 +1,2 @@
Main changes in this version: Bug fixes mainly regarding the notifications.
-Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.7
\ No newline at end of file
+Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/40103080.txt b/fastlane/metadata/android/en-US/changelogs/40103080.txt
new file mode 100644
index 0000000000..fc00c5da9e
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/40103080.txt
@@ -0,0 +1,2 @@
+Main changes in this version: Bug fixes!
+Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.8
\ No newline at end of file
diff --git a/fastlane/metadata/android/et/changelogs/40103050.txt b/fastlane/metadata/android/et/changelogs/40103050.txt
new file mode 100644
index 0000000000..c68db70b6a
--- /dev/null
+++ b/fastlane/metadata/android/et/changelogs/40103050.txt
@@ -0,0 +1,2 @@
+Põhilised muutused selles versioonis: Lisasime otsevestlustele kasutaja võrguolekute toe (matrix.org puhul on välja lülitatud) ja uuesti lisasime Android Auto toe.
+Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.3.5
diff --git a/fastlane/metadata/android/et/changelogs/40103060.txt b/fastlane/metadata/android/et/changelogs/40103060.txt
new file mode 100644
index 0000000000..d1c44870fa
--- /dev/null
+++ b/fastlane/metadata/android/et/changelogs/40103060.txt
@@ -0,0 +1,2 @@
+Põhilised muutused selles versioonis: Lisasime otsevestlustele kasutaja võrguolekute toe (matrix.org puhul on välja lülitatud) ja uuesti lisasime Android Auto toe.
+Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.3.6
diff --git a/fastlane/metadata/android/fr-FR/changelogs/40103050.txt b/fastlane/metadata/android/fr-FR/changelogs/40103050.txt
new file mode 100644
index 0000000000..08143ead2f
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/40103050.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : ajout du support pour les indicateurs de présence, dans les conversations privées (attention : les indicateurs de présence sont désactivés sur matrix.org). Réactivation de la prise en charge de Android Auto.
+Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.3.5
diff --git a/fastlane/metadata/android/fr-FR/changelogs/40103060.txt b/fastlane/metadata/android/fr-FR/changelogs/40103060.txt
new file mode 100644
index 0000000000..0187c4bf88
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/40103060.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : ajout du support pour les indicateurs de présence, dans les conversations privées (attention : les indicateurs de présence sont désactivés sur matrix.org). Réactivation de la prise en charge de Android Auto.
+Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.3.6
diff --git a/fastlane/metadata/android/fy/short_description.txt b/fastlane/metadata/android/fy/short_description.txt
index ddc559b59c..f0f44ad06c 100644
--- a/fastlane/metadata/android/fy/short_description.txt
+++ b/fastlane/metadata/android/fy/short_description.txt
@@ -1 +1 @@
-Groepsberjochtetsjinst - fersifere berjochten, groeps petearen en fideo skilje
+Groepsberjochtetsjinst - fersifere berjochten, groepspetearen en fideobelje
diff --git a/fastlane/metadata/android/fy/title.txt b/fastlane/metadata/android/fy/title.txt
index c4b5b596fc..0c77d7d613 100644
--- a/fastlane/metadata/android/fy/title.txt
+++ b/fastlane/metadata/android/fy/title.txt
@@ -1 +1 @@
-Element - Feilige Berjochtetsjinst
+Element - Feilige berjochtetsjinst
diff --git a/fastlane/metadata/android/hu-HU/changelogs/40103050.txt b/fastlane/metadata/android/hu-HU/changelogs/40103050.txt
new file mode 100644
index 0000000000..e46bf39f83
--- /dev/null
+++ b/fastlane/metadata/android/hu-HU/changelogs/40103050.txt
@@ -0,0 +1,2 @@
+Fő változás ebben a verzióban: Állapot állítási lehetőség közvetlen beszélgetéseknél (megj.: a matrix.org-on az állapot jelzés ki van kapcsolva). Újra elérhető az Android Auto.
+Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.3.5
diff --git a/fastlane/metadata/android/hu-HU/changelogs/40103060.txt b/fastlane/metadata/android/hu-HU/changelogs/40103060.txt
new file mode 100644
index 0000000000..1fc6ce5b8a
--- /dev/null
+++ b/fastlane/metadata/android/hu-HU/changelogs/40103060.txt
@@ -0,0 +1,2 @@
+Fő változás ebben a verzióban: Állapot állítási lehetőség közvetlen beszélgetéseknél (megj.: a matrix.org-on az állapot jelzés ki van kapcsolva). Újra elérhető az Android Auto.
+Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.3.6
diff --git a/fastlane/metadata/android/id/changelogs/40103040.txt b/fastlane/metadata/android/id/changelogs/40103040.txt
index 60e2e3a4de..0641f72ffd 100644
--- a/fastlane/metadata/android/id/changelogs/40103040.txt
+++ b/fastlane/metadata/android/id/changelogs/40103040.txt
@@ -1,2 +1,2 @@
-Perubahan utama di versi ini: Menambahkan dukungan presensi, untuk ruangan Pesan Langsung (diingat bahwa presensi dinonaktifkan di matrix.org). Menambahkan lagi dukungan Android Auto.
+Perubahan utama di versi ini: Tambahkan dukungan presensi, untuk ruangan Pesan Langsung (diingat bahwa presensi dinonaktifkan di matrix.org). Tambahkan lagi dukungan Android Auto.
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.4
diff --git a/fastlane/metadata/android/id/changelogs/40103050.txt b/fastlane/metadata/android/id/changelogs/40103050.txt
new file mode 100644
index 0000000000..ec7c9423bf
--- /dev/null
+++ b/fastlane/metadata/android/id/changelogs/40103050.txt
@@ -0,0 +1,2 @@
+Perubahan utama dalam versi ini: Tambahkan dukungan presensi, untuk ruangan Pesan Langsung (diingat bahwa presensi dinonaktifkan di matrix.org). Tambahkan lagi dukungan Android Auto.
+Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.5
diff --git a/fastlane/metadata/android/id/changelogs/40103060.txt b/fastlane/metadata/android/id/changelogs/40103060.txt
new file mode 100644
index 0000000000..4265699d2f
--- /dev/null
+++ b/fastlane/metadata/android/id/changelogs/40103060.txt
@@ -0,0 +1,2 @@
+Perubahan utama dalam versi ini: Tambahkan dukungan presensi, untuk ruangan Pesan Langsung (catatan: presensi dinonaktifkan di matrix.org). Tambahkan lagi dukungan Android Auto.
+Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.6
diff --git a/fastlane/metadata/android/id/full_description.txt b/fastlane/metadata/android/id/full_description.txt
index dfa9c8c826..d28ae8b004 100644
--- a/fastlane/metadata/android/id/full_description.txt
+++ b/fastlane/metadata/android/id/full_description.txt
@@ -1,42 +1,42 @@
-Element adalah perpesanan yang aman dan aplikasi kolaborasi tim produktivitas yang ideal untuk obrolan grup saat bekerja jarak jauh. Aplikasi obrolan ini menggunakan enkripsi ujung-ke-ujung untuk memberikan konferensi video, berbagi file, dan panggilan suara.
+Element adalah perpesanan yang aman dan aplikasi kolaborasi tim produktivitas yang ideal untuk obrolan grup saat bekerja jarak jauh. Aplikasi perpesanan ini menggunakan enkripsi ujung-ke-ujung untuk memberikan konferensi video, pembagian file, dan panggilan suara yang aman.
-Fitur Element termasuk:
+Fitur Element termasuk
- Alat komunikasi online yang canggih
-- Pesan terenkripsi sepenuhnya untuk memungkinkan komunikasi perusahaan yang lebih aman, bahkan untuk pekerja jarak jauh
-- Obrolan terdesentralisasi berdasarkan framework sumber-terbuka Matrix
-- Berbagi file dengan aman dengan data terenkripsi saat mengelola proyek
-- Obrolan video dengan VoIP dan berbagi layar
+- Pesan-pesan yang dienkripsi sepenuhnya untuk memungkinkan komunikasi perusahaan yang lebih aman, bahkan untuk pekerja jarak jauh
+- Obrolan terdesentralisasi berdasarkan kerangka Matrix yang sumber terbuka
+- Pembagian file aman dengan data terenkripsi saat mengelola proyek
+- Obrolan video dengan VoIP dan pembagian layar
- Integrasi yang mudah dengan alat kolaborasi online favorit Anda, alat manajemen proyek, layanan VoIP dan aplikasi perpesanan tim lainnya
-Element benar-benar berbeda dari aplikasi perpesanan dan kolaborasi lainnya. Element beroperasi pada Matrix, jaringan terbuka untuk pengiriman pesan yang aman dan komunikasi terdesentralisasi. Matrix memungkinkan hosting sendiri untuk memberi pengguna kepemilikan maksimum dan kontrol data dan pesan mereka.
+Element benar-benar berbeda dari aplikasi perpesanan dan aplikasi kolaborasi lainnya. Element beroperasi pada Matrix, jaringan terbuka untuk pengiriman pesan yang aman dan komunikasi terdesentralisasi.
-Pesan privasi dan terenkripsi
-Element melindungi Anda dari iklan yang tidak diinginkan, penambangan data dan taman berdinding. Element juga mengamankan semua data Anda, komunikasi video dan suara satu-ke-satu melalui enkripsi ujung-ke-ujung dan verifikasi perangkat yang ditandatangani secara silang.
+Perpesanan dengan privasi dan enkripsi
+Element melindungi Anda dari iklan yang tidak diinginkan, penambangan data dan taman berdinding. Element juga mengamankan semua data Anda, komunikasi video dan suara satu-ke-satu dengan enkripsi ujung-ke-ujung dan verifikasi perangkat menggunakan penandatanganan silang.
-Element memberi Anda kendali atas privasi Anda sambil memungkinkan Anda untuk berkomunikasi dengan aman dengan siapa pun di jaringan Matrix, atau alat kolaborasi bisnis lainnya dengan mengintegrasikan dengan aplikasi seperti Slack.
+Element memberikan Anda kendali atas privasi Anda sambil memungkinkan Anda untuk berkomunikasi dengan siapa saja secara aman di jaringan Matrix, atau alat kolaborasi bisnis lainnya dengan mengintegrasikan aplikasi-aplikasi seperti Slack.
Element dapat dihost sendiri
-Untuk memungkinkan lebih banyak kendali atas data dan percakapan sensitif Anda, Element bisa dihost sendiri atau Anda dapat memilih host berbasis Matrix - standar untuk komunikasi terdesentralisasi sumber-terbuka. Element memberi Anda privasi, kepatuhan keamanan, dan fleksibilitas integrasi.
+Untuk memungkinkan lebih banyak kendali atas data dan pesan-pesan sensitif Anda, Element dapat dihost sendiri atau Anda dapat memilih host berbasis Matrix, standar untuk komunikasi terdesentralisasi sumber terbuka. Element memberi Anda privasi, kepatuhan keamanan, dan fleksibilitas integrasi.
Miliki data Anda
-Anda memutuskan di mana menyimpan data dan pesan Anda. Tanpa risiko penambangan data atau akses dari pihak ketiga.
+Anda memutuskan di mana untuk menyimpan data dan pesan-pesan Anda, tanpa risiko penambangan data atau akses dari pihak ketiga.
Element menempatkan Anda dalam kendali dengan cara yang berbeda:
1. Dapatkan akun gratis pada server publik matrix.org yang dihost oleh pengembang Matrix, atau memilih dari ribuan server publik yang dihost oleh sukarelawan
2. Host sendiri akun Anda dengan menjalankan server pada infrastruktur IT Anda sendiri
-3. Daftar untuk akun di server khusus dengan hanya berlangganan platform hosting Element Matrix Services
+3. Daftar untuk akun di server khusus dengan berlangganan platform hosting Layanan Matrix Element
Pesan terbuka dan kolaborasi
-Anda dapat mengobrol dengan siapa saja di jaringan Matrix, apakah mereka menggunakan Element, aplikasi Matrix lain atau bahkan jika mereka menggunakan aplikasi perpesanan yang berbeda.
+Anda dapat mengobrol dengan siapa saja di jaringan Matrix, jika mereka menggunakan Element, aplikasi Matrix lain atau bahkan menggunakan aplikasi perpesanan yang berbeda.
Sangat aman
-Enkripsi ujung-ke-ujung yang nyata (hanya mereka yang dalam percakapan dapat mendekripsi pesan), dan verifikasi perangkat menggunakan penandatanganan-silang.
+Enkripsi ujung-ke-ujung yang nyata (hanya mereka yang dalam obrolan dapat mendekripsi pesan), dan verifikasi perangkat menggunakan penandatanganan silang.
Komunikasi dan integrasi lengkap
-Perpesanan, panggilan suara dan video, berbagi file, berbagi layar dan banyak integrasi, bot dan widget. Buat ruangan, komunitas, tetap terhubung dan selesaikan hal-hal.
+Perpesanan, panggilan suara dan video, pembagian file, pembagian layar dan banyak integrasi bot dan widget. Buat ruangan dan komunitas, tetap terhubung dan selesaikan hal-hal penting.
Ambil di mana Anda tinggalkan
-Tetap terhubung di mana pun Anda berada dengan riwayat pesan yang sepenuhnya disinkronkan di semua perangkat Anda dan di web di https://app.element.io
+Tetap terhubung di mana Anda berada, dengan riwayat pesan yang disinkronkan di semua perangkat Anda dan web di https://app.element.io
-Open source
-Element Android adalah proyek sumber terbuka, di-host oleh GitHub. Silakan melaporkan bug dan/atau membuat kontribusi ke pengembangannya di https://github.com/vector-im/element-android
+Sumber terbuka
+Element Android adalah proyek sumber terbuka, dihost oleh GitHub. Silakan laporkan masalah yang Anda temukan, atau membuat kontribusi ke pengembangannya di https://github.com/vector-im/element-android
diff --git a/fastlane/metadata/android/id/short_description.txt b/fastlane/metadata/android/id/short_description.txt
index e6c3a2f7a9..1cd770dd73 100644
--- a/fastlane/metadata/android/id/short_description.txt
+++ b/fastlane/metadata/android/id/short_description.txt
@@ -1 +1 @@
-Perpesanan grup - pesan terenkripsi, panggilan grup dan video
+Perpesanan grup - perpesanan, panggilan suara dan video grup terenkripsi
diff --git a/fastlane/metadata/android/it-IT/changelogs/40103050.txt b/fastlane/metadata/android/it-IT/changelogs/40103050.txt
new file mode 100644
index 0000000000..2762949682
--- /dev/null
+++ b/fastlane/metadata/android/it-IT/changelogs/40103050.txt
@@ -0,0 +1,2 @@
+Modifiche principali in questa versione: aggiunto supporto alla presenza per messaggi diretti (nota: la presenza è disattivata su matrix.org). Aggiunto di nuovo il supporto ad Android Auto.
+Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.3.5
diff --git a/fastlane/metadata/android/it-IT/changelogs/40103060.txt b/fastlane/metadata/android/it-IT/changelogs/40103060.txt
new file mode 100644
index 0000000000..f241fa9e57
--- /dev/null
+++ b/fastlane/metadata/android/it-IT/changelogs/40103060.txt
@@ -0,0 +1,2 @@
+Modifiche principali in questa versione: aggiunto supporto alla presenza per i messaggi diretti (nota: la presenza è disattivata su matrix.org). Aggiunto di nuovo il supporto a Android Auto.
+Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.3.6
diff --git a/fastlane/metadata/android/pt-BR/changelogs/40103050.txt b/fastlane/metadata/android/pt-BR/changelogs/40103050.txt
new file mode 100644
index 0000000000..e565d269ed
--- /dev/null
+++ b/fastlane/metadata/android/pt-BR/changelogs/40103050.txt
@@ -0,0 +1,2 @@
+Principais mudanças nesta versão: Adicionar suporte a Presença, para sala de Mensagem Direta (nota: presença está desabilitada em matrix.org). Adicionar de novo suporte a Android Auto.
+Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.3.5
diff --git a/fastlane/metadata/android/pt-BR/changelogs/40103060.txt b/fastlane/metadata/android/pt-BR/changelogs/40103060.txt
new file mode 100644
index 0000000000..b246759d26
--- /dev/null
+++ b/fastlane/metadata/android/pt-BR/changelogs/40103060.txt
@@ -0,0 +1,2 @@
+Principais mudanças nesta versão: Adicionar suporte a Presença, para sala de Mensagem Direta (nota: presença está desabilitada em matrix.org). Adicionar de novo suporte a Android Auto.
+Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.3.6
diff --git a/fastlane/metadata/android/sq/changelogs/40103040.txt b/fastlane/metadata/android/sq/changelogs/40103040.txt
index 7f37e82801..6ad044b6a4 100644
--- a/fastlane/metadata/android/sq/changelogs/40103040.txt
+++ b/fastlane/metadata/android/sq/changelogs/40103040.txt
@@ -1,2 +1,2 @@
-Ndryshimet kryesore në këtë version: Shtim mbulimi për Prani, për dhomë Mesazh i Drejtpërdrejtë (shënim: në matrix.org prania është e çaktivizuar. Shtim sërish i mbulimit për Android Auto.
+Ndryshimet kryesore në këtë version: Shtim mbulimi për Prani, për dhomë Mesazh i Drejtpërdrejtë (shënim: në matrix.org prania është e çaktivizuar). Shtim sërish i mbulimit për Android Auto.
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.3.4
diff --git a/fastlane/metadata/android/sq/changelogs/40103050.txt b/fastlane/metadata/android/sq/changelogs/40103050.txt
new file mode 100644
index 0000000000..bb609da987
--- /dev/null
+++ b/fastlane/metadata/android/sq/changelogs/40103050.txt
@@ -0,0 +1,2 @@
+Ndryshimet kryesore në këtë version: Shtim mbulimi për Prani, për dhomën Mesazh i Drejtpërdrejtë (shënim: prania është e çaktivizuar në matrix.org). Shtim sërish i mbulimit për Android Auto.
+Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.3.5
diff --git a/fastlane/metadata/android/sq/changelogs/40103060.txt b/fastlane/metadata/android/sq/changelogs/40103060.txt
new file mode 100644
index 0000000000..96afd47a5d
--- /dev/null
+++ b/fastlane/metadata/android/sq/changelogs/40103060.txt
@@ -0,0 +1,2 @@
+Ndryshimet kryesore në këtë version: Shtim mbulimi për Prani, për dhomën Mesazh i Drejtpërdrejtë (shënim: prania është e çaktivizuar në matrix.org). Shtim sërish i mbulimit për Android Auto.
+Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.3.6
diff --git a/fastlane/metadata/android/sv-SE/changelogs/40103050.txt b/fastlane/metadata/android/sv-SE/changelogs/40103050.txt
new file mode 100644
index 0000000000..57ee7189e3
--- /dev/null
+++ b/fastlane/metadata/android/sv-SE/changelogs/40103050.txt
@@ -0,0 +1,2 @@
+Huvudsakliga ändringar i den här versionen: Lägg till närvarostöd för direktmeddelanden (obs: närvaro är inaktiverat på matrix.org). Lägg till stöd för Android Auto igen.
+Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.3.5
diff --git a/fastlane/metadata/android/sv-SE/changelogs/40103060.txt b/fastlane/metadata/android/sv-SE/changelogs/40103060.txt
new file mode 100644
index 0000000000..bac3775a2a
--- /dev/null
+++ b/fastlane/metadata/android/sv-SE/changelogs/40103060.txt
@@ -0,0 +1,2 @@
+Huvudsakliga ändringar i den här versionen: Lägg till närvarostöd för direktmeddelanden (obs: närvaro är inaktiverat på matrix.org). Lägg till stöd för Android Auto igen.
+Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.3.6
diff --git a/fastlane/metadata/android/uk/changelogs/40103040.txt b/fastlane/metadata/android/uk/changelogs/40103040.txt
index 3e65e0bc07..b6d237241b 100644
--- a/fastlane/metadata/android/uk/changelogs/40103040.txt
+++ b/fastlane/metadata/android/uk/changelogs/40103040.txt
@@ -1,2 +1,2 @@
-Основні зміни в цій версії: Додано підтримку присутності для кімнати особистих повідомлень (примітка: присутність вимкнено на matrix.org. Знову додано підтримку Android Auto.
+Основні зміни в цій версії: Додано підтримку присутності для кімнати особистих повідомлень (примітка: присутність вимкнено на matrix.org). Знову додано підтримку Android Auto.
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.3.4
diff --git a/fastlane/metadata/android/uk/changelogs/40103050.txt b/fastlane/metadata/android/uk/changelogs/40103050.txt
new file mode 100644
index 0000000000..846d1a2d84
--- /dev/null
+++ b/fastlane/metadata/android/uk/changelogs/40103050.txt
@@ -0,0 +1,2 @@
+Основні зміни у цій версії: Додано підтримку присутності для кімнати особистих повідомлень (примітка: присутність вимкнена на matrix.org). Знову додано підтримку Android Auto.
+Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.3.5
diff --git a/fastlane/metadata/android/uk/changelogs/40103060.txt b/fastlane/metadata/android/uk/changelogs/40103060.txt
new file mode 100644
index 0000000000..a1eec4d4de
--- /dev/null
+++ b/fastlane/metadata/android/uk/changelogs/40103060.txt
@@ -0,0 +1,2 @@
+Основні зміни у цій версії: Додано підтримку присутності для кімнати особистих повідомлень (примітка: присутність вимкнена на matrix.org). Знову додано підтримку Android Auto.
+Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.3.6
diff --git a/fastlane/metadata/android/zh-CN/changelogs/40103040.txt b/fastlane/metadata/android/zh-CN/changelogs/40103040.txt
index 0c3d4d57c3..c879c3d036 100644
--- a/fastlane/metadata/android/zh-CN/changelogs/40103040.txt
+++ b/fastlane/metadata/android/zh-CN/changelogs/40103040.txt
@@ -1,2 +1,2 @@
-此版本主要变化:为 Direct Message 聊天室添加 Presence 支持 (注意:Presence 在matrix.org 上是禁用的。再次添加 Android Auto 支持。
+此版本主要变化:为 Direct Message 聊天室添加 Presence 支持 (注意:presence 在 matrix.org 上是禁用的)。再次添加 Android Auto 支持。
完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.3.4
diff --git a/fastlane/metadata/android/zh-CN/changelogs/40103050.txt b/fastlane/metadata/android/zh-CN/changelogs/40103050.txt
new file mode 100644
index 0000000000..7343ae0b9f
--- /dev/null
+++ b/fastlane/metadata/android/zh-CN/changelogs/40103050.txt
@@ -0,0 +1,2 @@
+此版本的主要变化:为私信聊天室添加 Presence 支持 (注意:在 matrix.org 上 Presence 是禁用的)。再次添加 Android Auto 支持。
+完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.3.5
diff --git a/fastlane/metadata/android/zh-CN/changelogs/40103060.txt b/fastlane/metadata/android/zh-CN/changelogs/40103060.txt
new file mode 100644
index 0000000000..8322539927
--- /dev/null
+++ b/fastlane/metadata/android/zh-CN/changelogs/40103060.txt
@@ -0,0 +1,2 @@
+此版本的主要变化:为私信聊天室添加 Presence 支持(注意:在 matrix.org 上 Presence 是禁用的)。再次添加 Android Auto 支持。
+完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.3.6
diff --git a/fastlane/metadata/android/zh-TW/changelogs/40103050.txt b/fastlane/metadata/android/zh-TW/changelogs/40103050.txt
new file mode 100644
index 0000000000..659be479f5
--- /dev/null
+++ b/fastlane/metadata/android/zh-TW/changelogs/40103050.txt
@@ -0,0 +1,2 @@
+此版本中的主要變動:為直接訊息聊天室新增 Presence 支援(請注意:此功能在 matrix.org 上停用)。加回 Android Auto 支援。
+完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.3.5
diff --git a/fastlane/metadata/android/zh-TW/changelogs/40103060.txt b/fastlane/metadata/android/zh-TW/changelogs/40103060.txt
new file mode 100644
index 0000000000..e1223a40e5
--- /dev/null
+++ b/fastlane/metadata/android/zh-TW/changelogs/40103060.txt
@@ -0,0 +1,2 @@
+此版本中的主要變動:為直接訊息聊天室新增 Presence 支援(請注意:此功能在 matrix.org 上停用)。加回 Android Auto 支援。
+完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.3.6
diff --git a/library/ui-styles/src/debug/res/layout/debug_social_login.xml b/library/ui-styles/src/debug/res/layout/debug_social_login.xml
new file mode 100644
index 0000000000..895ecddad4
--- /dev/null
+++ b/library/ui-styles/src/debug/res/layout/debug_social_login.xml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/ui-styles/src/main/res/values/styles_social_login.xml b/library/ui-styles/src/main/res/values/styles_social_login.xml
index b527d5bfdc..f601f36480 100644
--- a/library/ui-styles/src/main/res/values/styles_social_login.xml
+++ b/library/ui-styles/src/main/res/values/styles_social_login.xml
@@ -9,7 +9,7 @@
start14spcenter
- 2dp
+ 4dp38dpfalse
diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml
index 4d525f7eee..b828855721 100644
--- a/library/ui-styles/src/main/res/values/theme_dark.xml
+++ b/library/ui-styles/src/main/res/values/theme_dark.xml
@@ -129,7 +129,9 @@
@transition/image_preview_transition@transition/image_preview_transition
- @style/Widget.Vector.Button.Outlined.SocialLogin.Google.Dark
+
+ @style/Widget.Vector.Button.Outlined.SocialLogin.Google.Light@style/Widget.Vector.Button.Outlined.SocialLogin.Github.Dark@style/Widget.Vector.Button.Outlined.SocialLogin.Facebook.Dark@style/Widget.Vector.Button.Outlined.SocialLogin.Twitter.Dark
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 94fc221cf1..7fd871fdcc 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -31,7 +31,7 @@ android {
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'
- buildConfigField "String", "SDK_VERSION", "\"1.3.8\""
+ buildConfigField "String", "SDK_VERSION", "\"1.3.9\""
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
@@ -115,7 +115,7 @@ dependencies {
implementation libs.squareup.retrofit
implementation libs.squareup.retrofitMoshi
- implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.2"))
+ implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.3"))
implementation 'com.squareup.okhttp3:okhttp'
implementation 'com.squareup.okhttp3:logging-interceptor'
implementation 'com.squareup.okhttp3:okhttp-urlconnection'
@@ -158,10 +158,10 @@ dependencies {
implementation libs.apache.commonsImaging
// Phone number https://github.com/google/libphonenumber
- implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.37'
+ implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.38'
testImplementation libs.tests.junit
- testImplementation 'org.robolectric:robolectric:4.7'
+ testImplementation 'org.robolectric:robolectric:4.7.2'
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
testImplementation libs.mockk.mockk
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixUrls.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixUrls.kt
index dc4e0f152d..4a41eaec4a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixUrls.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixUrls.kt
@@ -20,7 +20,18 @@ package org.matrix.android.sdk.api
* This class contains pattern to match Matrix Url, aka mxc urls
*/
object MatrixUrls {
+ /**
+ * "mxc" scheme, including "://". So "mxc://"
+ */
const val MATRIX_CONTENT_URI_SCHEME = "mxc://"
+ /**
+ * Return true if the String starts with "mxc://"
+ */
fun String.isMxcUrl() = startsWith(MATRIX_CONTENT_URI_SCHEME)
+
+ /**
+ * Remove the "mxc://" prefix. No op if the String is not a Mxc URL
+ */
+ fun String.removeMxcPrefix() = removePrefix(MATRIX_CONTENT_URI_SCHEME)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt
index b2035bb2eb..13a26c89c1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt
@@ -18,6 +18,8 @@ package org.matrix.android.sdk.api.failure
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.session.contentscanner.ContentScannerError
+import org.matrix.android.sdk.api.session.contentscanner.ScanFailure
import org.matrix.android.sdk.internal.di.MoshiProvider
import java.io.IOException
import javax.net.ssl.HttpsURLConnection
@@ -100,3 +102,19 @@ fun Throwable.isRegistrationAvailabilityError(): Boolean {
error.code == MatrixError.M_INVALID_USERNAME ||
error.code == MatrixError.M_EXCLUSIVE)
}
+
+/**
+ * Try to convert to a ScanFailure. Return null in the cases it's not possible
+ */
+fun Throwable.toScanFailure(): ScanFailure? {
+ return if (this is Failure.OtherServerError) {
+ tryOrNull {
+ MoshiProvider.providesMoshi()
+ .adapter(ContentScannerError::class.java)
+ .fromJson(errorBody)
+ }
+ ?.let { ScanFailure(it, httpCode, this) }
+ } else {
+ null
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt
index 0d204edcee..44ac439d7b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt
@@ -26,6 +26,7 @@ open class LoggerTag(_value: String, parentTag: LoggerTag? = null) {
object SYNC : LoggerTag("SYNC")
object VOIP : LoggerTag("VOIP")
+ object CRYPTO : LoggerTag("CRYPTO")
val value: String = if (parentTag == null) {
_value
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt
index dfe43aed6f..3f817ec4d2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt
@@ -31,6 +31,7 @@ import org.matrix.android.sdk.api.session.cache.CacheService
import org.matrix.android.sdk.api.session.call.CallSignalingService
import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
+import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.events.EventService
import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
@@ -192,6 +193,11 @@ interface Session :
*/
fun cryptoService(): CryptoService
+ /**
+ * Returns the ContentScannerService associated with the session
+ */
+ fun contentScannerService(): ContentScannerService
+
/**
* Returns the identity service associated with the session
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt
index 7ee26de8db..a3d8e83740 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt
@@ -44,7 +44,8 @@ data class ContentAttachmentData(
FILE,
IMAGE,
AUDIO,
- VIDEO
+ VIDEO,
+ VOICE_MESSAGE
}
fun getSafeMimeType() = mimeType?.normalizeMimeType()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt
index 36c471bb2b..3dd096e144 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt
@@ -16,6 +16,8 @@
package org.matrix.android.sdk.api.session.content
+import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
+
/**
* This interface defines methods for accessing content from the current session.
*/
@@ -39,6 +41,15 @@ interface ContentUrlResolver {
*/
fun resolveFullSize(contentUrl: String?): String?
+ /**
+ * Get the ResolvedMethod to download a URL
+ *
+ * @param contentUrl the Matrix media content URI (in the form of "mxc://...").
+ * @param elementToDecrypt Encryption data may be required if you use a content scanner
+ * @return the Method to access resource, or null if invalid
+ */
+ fun resolveForDownload(contentUrl: String?, elementToDecrypt: ElementToDecrypt? = null): ResolvedMethod?
+
/**
* Get the actual URL for accessing the thumbnail image of a given Matrix media content URI.
*
@@ -49,4 +60,9 @@ interface ContentUrlResolver {
* @return the URL to access the described resource, or null if the url is invalid.
*/
fun resolveThumbnail(contentUrl: String?, width: Int, height: Int, method: ThumbnailMethod): String?
+
+ sealed class ResolvedMethod {
+ data class GET(val url: String) : ResolvedMethod()
+ data class POST(val url: String, val jsonBody: String) : ResolvedMethod()
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerError.kt
new file mode 100644
index 0000000000..cef5d41f2c
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerError.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.contentscanner
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class ContentScannerError(
+ @Json(name = "info") val info: String? = null,
+ @Json(name = "reason") val reason: String? = null
+) {
+ companion object {
+ // 502 The server failed to request media from the media repo.
+ const val REASON_MCS_MEDIA_REQUEST_FAILED = "MCS_MEDIA_REQUEST_FAILED"
+
+ /* 400 The server failed to decrypt the encrypted media downloaded from the media repo.*/
+ const val REASON_MCS_MEDIA_FAILED_TO_DECRYPT = "MCS_MEDIA_FAILED_TO_DECRYPT"
+
+ /* 403 The server scanned the downloaded media but the antivirus script returned a non-zero exit code.*/
+ const val REASON_MCS_MEDIA_NOT_CLEAN = "MCS_MEDIA_NOT_CLEAN"
+
+ /* 403 The provided encrypted_body could not be decrypted. The client should request the public key of the server and then retry (once).*/
+ const val REASON_MCS_BAD_DECRYPTION = "MCS_BAD_DECRYPTION"
+
+ /* 400 The request body contains malformed JSON.*/
+ const val REASON_MCS_MALFORMED_JSON = "MCS_MALFORMED_JSON"
+ }
+}
+
+class ScanFailure(val error: ContentScannerError, val httpCode: Int, cause: Throwable? = null) : Throwable(cause = cause)
+
+// For Glide, which deals with Exception and not with Throwable
+fun ScanFailure.toException() = Exception(this)
+fun Throwable.toScanFailure() = this.cause as? ScanFailure
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerService.kt
new file mode 100644
index 0000000000..1dd7bab01c
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerService.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.contentscanner
+
+import androidx.lifecycle.LiveData
+import org.matrix.android.sdk.api.util.Optional
+import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
+
+interface ContentScannerService {
+
+ val serverPublicKey: String?
+
+ fun getContentScannerServer(): String?
+ fun setScannerUrl(url: String?)
+ fun enableScanner(enabled: Boolean)
+ fun isScannerEnabled(): Boolean
+ fun getLiveStatusForFile(mxcUrl: String, fetchIfNeeded: Boolean = true, fileInfo: ElementToDecrypt? = null): LiveData>
+ fun getCachedScanResultForFile(mxcUrl: String): ScanStatusInfo?
+
+ /**
+ * Get the current public curve25519 key that the AV server is advertising.
+ * @param callback on success callback containing the server public key
+ */
+ suspend fun getServerPublicKey(forceDownload: Boolean = false): String?
+ suspend fun getScanResultForAttachment(mxcUrl: String, fileInfo: ElementToDecrypt? = null): ScanStatusInfo
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ScanState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ScanState.kt
new file mode 100644
index 0000000000..da209080ac
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ScanState.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.contentscanner
+
+enum class ScanState {
+ TRUSTED,
+ INFECTED,
+ UNKNOWN,
+ IN_PROGRESS
+}
+
+data class ScanStatusInfo(
+ val state: ScanState,
+ val scanDateTimestamp: Long?,
+ val humanReadableMessage: String?
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt
index 7d827f871b..f67efc50ba 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt
@@ -28,6 +28,10 @@ object RelationType {
/** Lets you define an event which references an existing event.*/
const val REFERENCE = "m.reference"
+ /** Lets you define an thread event that belongs to another existing event.*/
+// const val THREAD = "m.thread" // m.thread is not yet released in the backend
+ const val THREAD = "io.element.thread" // io.element.thread will be replaced by m.thread when it is released
+
/** Lets you define an event which adds a response to an existing event.*/
const val RESPONSE = "org.matrix.response"
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt
index 9471b3dbcb..073f5c0613 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt
@@ -23,15 +23,15 @@ package org.matrix.android.sdk.api.session.room.send
* EDIT: draft of an edition of a message
* REPLY: draft of a reply of another message
*/
-sealed class UserDraft(open val text: String) {
- data class REGULAR(override val text: String) : UserDraft(text)
- data class QUOTE(val linkedEventId: String, override val text: String) : UserDraft(text)
- data class EDIT(val linkedEventId: String, override val text: String) : UserDraft(text)
- data class REPLY(val linkedEventId: String, override val text: String) : UserDraft(text)
+sealed interface UserDraft {
+ data class Regular(val text: String) : UserDraft
+ data class Quote(val linkedEventId: String, val text: String) : UserDraft
+ data class Edit(val linkedEventId: String, val text: String) : UserDraft
+ data class Reply(val linkedEventId: String, val text: String) : UserDraft
fun isValid(): Boolean {
return when (this) {
- is REGULAR -> text.isNotBlank()
+ is Regular -> text.isNotBlank()
else -> true
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
index b9c7c812ae..a7455a349d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
@@ -28,8 +28,10 @@ import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
+import org.matrix.android.sdk.api.util.ContentUtils
import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromReply
/**
@@ -135,20 +137,6 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? {
}
}
-/**
- * Get last Message body, after a possible edition
- */
-fun TimelineEvent.getLastMessageBody(): String? {
- val lastMessageContent = getLastMessageContent()
-
- if (lastMessageContent != null) {
- return lastMessageContent.newContent?.toModel()?.body
- ?: lastMessageContent.body
- }
-
- return null
-}
-
/**
* Returns true if it's a reply
*/
@@ -160,11 +148,25 @@ fun TimelineEvent.isEdition(): Boolean {
return root.isEdition()
}
-fun TimelineEvent.getTextEditableContent(): String? {
- val lastContent = getLastMessageContent()
+/**
+ * Get the latest message body, after a possible edition, stripping the reply prefix if necessary
+ */
+fun TimelineEvent.getTextEditableContent(): String {
+ val lastContentBody = getLastMessageContent()?.body ?: return ""
return if (isReply()) {
- return extractUsefulTextFromReply(lastContent?.body ?: "")
+ extractUsefulTextFromReply(lastContentBody)
} else {
- lastContent?.body ?: ""
+ lastContentBody
}
}
+
+/**
+ * Get the latest displayable content.
+ * Will take care to hide spoiler text
+ */
+fun MessageContent.getTextDisplayableContent(): String {
+ return newContent?.toModel()?.matrixFormattedBody?.let { ContentUtils.formatSpoilerTextFromHtml(it) }
+ ?: newContent?.toModel()?.body
+ ?: (this as MessageTextContent?)?.matrixFormattedBody?.let { ContentUtils.formatSpoilerTextFromHtml(it) }
+ ?: body
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/ContentUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/ContentUtils.kt
index 1a00b85ff4..e453cb2df5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/ContentUtils.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/ContentUtils.kt
@@ -15,6 +15,8 @@
*/
package org.matrix.android.sdk.api.util
+import org.matrix.android.sdk.internal.util.unescapeHtml
+
object ContentUtils {
fun extractUsefulTextFromReply(repliedBody: String): String {
val lines = repliedBody.lines()
@@ -44,4 +46,15 @@ object ContentUtils {
}
return repliedBody
}
+
+ @Suppress("RegExpRedundantEscape")
+ fun formatSpoilerTextFromHtml(formattedBody: String): String {
+ // var reason = "",
+ // can capture the spoiler reason for better formatting? ex. { reason = it.value; ">"}
+ return formattedBody.replace("(?<=".toRegex(), ">")
+ .replace("(?<=).+?(?=)".toRegex()) { SPOILER_CHAR.repeat(it.value.length) }
+ .unescapeHtml()
+ }
+
+ private const val SPOILER_CHAR = "█"
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt
index b72cff3cf1..0583951138 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt
@@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistration
import org.matrix.android.sdk.internal.auth.registration.RegisterAddThreePidTask
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.content.DefaultContentUrlResolver
+import org.matrix.android.sdk.internal.session.contentscanner.DisabledContentScannerService
internal class DefaultLoginWizard(
private val authAPI: AuthAPI,
@@ -44,7 +45,7 @@ internal class DefaultLoginWizard(
private val getProfileTask: GetProfileTask = DefaultGetProfileTask(
authAPI,
- DefaultContentUrlResolver(pendingSessionData.homeServerConnectionConfig)
+ DefaultContentUrlResolver(pendingSessionData.homeServerConnectionConfig, DisabledContentScannerService())
)
override suspend fun getProfileInfo(matrixId: String): LoginProfileInfo {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
index 7b96148e2e..18f344882c 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
@@ -36,6 +36,7 @@ import org.matrix.android.sdk.api.crypto.MXCryptoConfig
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.listeners.ProgressListener
+import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
@@ -110,6 +111,9 @@ import kotlin.math.max
* CryptoService maintains all necessary keys and their sharing with other devices required for the crypto.
* Specially, it tracks all room membership changes events in order to do keys updates.
*/
+
+private val loggerTag = LoggerTag("DefaultCryptoService", LoggerTag.CRYPTO)
+
@SessionScope
internal class DefaultCryptoService @Inject constructor(
// Olm Manager
@@ -346,7 +350,7 @@ internal class DefaultCryptoService @Inject constructor(
deviceListManager.startTrackingDeviceList(listOf(userId))
deviceListManager.refreshOutdatedDeviceLists()
} catch (failure: Throwable) {
- Timber.e(failure, "## CRYPTO onSyncWillProcess ")
+ Timber.tag(loggerTag.value).e(failure, "onSyncWillProcess ")
}
}
}
@@ -379,7 +383,7 @@ internal class DefaultCryptoService @Inject constructor(
{
isStarting.set(false)
isStarted.set(false)
- Timber.e(it, "Start failed")
+ Timber.tag(loggerTag.value).e(it, "Start failed")
}
)
}
@@ -551,14 +555,14 @@ internal class DefaultCryptoService @Inject constructor(
val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId)
if (!existingAlgorithm.isNullOrEmpty() && existingAlgorithm != algorithm) {
- Timber.e("## CRYPTO | setEncryptionInRoom() : Ignoring m.room.encryption event which requests a change of config in $roomId")
+ Timber.tag(loggerTag.value).e("setEncryptionInRoom() : Ignoring m.room.encryption event which requests a change of config in $roomId")
return false
}
val encryptingClass = MXCryptoAlgorithms.hasEncryptorClassForAlgorithm(algorithm)
if (!encryptingClass) {
- Timber.e("## CRYPTO | setEncryptionInRoom() : Unable to encrypt room $roomId with $algorithm")
+ Timber.tag(loggerTag.value).e("setEncryptionInRoom() : Unable to encrypt room $roomId with $algorithm")
return false
}
@@ -577,7 +581,7 @@ internal class DefaultCryptoService @Inject constructor(
// e2e rooms with them, so there is room for optimisation here, but for now
// we just invalidate everyone in the room.
if (null == existingAlgorithm) {
- Timber.v("Enabling encryption in $roomId for the first time; invalidating device lists for all users therein")
+ Timber.tag(loggerTag.value).d("Enabling encryption in $roomId for the first time; invalidating device lists for all users therein")
val userIds = ArrayList(membersId)
@@ -655,17 +659,17 @@ internal class DefaultCryptoService @Inject constructor(
val safeAlgorithm = alg
if (safeAlgorithm != null) {
val t0 = System.currentTimeMillis()
- Timber.v("## CRYPTO | encryptEventContent() starts")
+ Timber.tag(loggerTag.value).v("encryptEventContent() starts")
runCatching {
val content = safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
- Timber.v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
+ Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
MXEncryptEventContentResult(content, EventType.ENCRYPTED)
}.foldToCallback(callback)
} else {
val algorithm = getEncryptionAlgorithm(roomId)
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON,
algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON)
- Timber.e("## CRYPTO | encryptEventContent() : $reason")
+ Timber.tag(loggerTag.value).e("encryptEventContent() : failed $reason")
callback.onFailure(Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason)))
}
}
@@ -677,7 +681,7 @@ internal class DefaultCryptoService @Inject constructor(
if (roomEncryptor is IMXGroupEncryption) {
roomEncryptor.discardSessionKey()
} else {
- Timber.e("## CRYPTO | discardOutboundSession() for:$roomId: Unable to handle IMXGroupEncryption")
+ Timber.tag(loggerTag.value).e("discardOutboundSession() for:$roomId: Unable to handle IMXGroupEncryption")
}
}
}
@@ -768,14 +772,14 @@ internal class DefaultCryptoService @Inject constructor(
*/
private fun onRoomKeyEvent(event: Event) {
val roomKeyContent = event.getClearContent().toModel() ?: return
- Timber.i("## CRYPTO | onRoomKeyEvent() from: ${event.senderId} type<${event.getClearType()}> , sessionId<${roomKeyContent.sessionId}>")
+ Timber.tag(loggerTag.value).i("onRoomKeyEvent() from: ${event.senderId} type<${event.getClearType()}> , sessionId<${roomKeyContent.sessionId}>")
if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.algorithm.isNullOrEmpty()) {
- Timber.e("## CRYPTO | onRoomKeyEvent() : missing fields")
+ Timber.tag(loggerTag.value).e("onRoomKeyEvent() : missing fields")
return
}
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(roomKeyContent.roomId, roomKeyContent.algorithm)
if (alg == null) {
- Timber.e("## CRYPTO | GOSSIP onRoomKeyEvent() : Unable to handle keys for ${roomKeyContent.algorithm}")
+ Timber.tag(loggerTag.value).e("GOSSIP onRoomKeyEvent() : Unable to handle keys for ${roomKeyContent.algorithm}")
return
}
alg.onRoomKeyEvent(event, keysBackupService)
@@ -783,29 +787,29 @@ internal class DefaultCryptoService @Inject constructor(
private fun onKeyWithHeldReceived(event: Event) {
val withHeldContent = event.getClearContent().toModel() ?: return Unit.also {
- Timber.i("## CRYPTO | Malformed onKeyWithHeldReceived() : missing fields")
+ Timber.tag(loggerTag.value).i("Malformed onKeyWithHeldReceived() : missing fields")
}
- Timber.i("## CRYPTO | onKeyWithHeldReceived() received from:${event.senderId}, content <$withHeldContent>")
+ Timber.tag(loggerTag.value).i("onKeyWithHeldReceived() received from:${event.senderId}, content <$withHeldContent>")
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(withHeldContent.roomId, withHeldContent.algorithm)
if (alg is IMXWithHeldExtension) {
alg.onRoomKeyWithHeldEvent(withHeldContent)
} else {
- Timber.e("## CRYPTO | onKeyWithHeldReceived() from:${event.senderId}: Unable to handle WithHeldContent for ${withHeldContent.algorithm}")
+ Timber.tag(loggerTag.value).e("onKeyWithHeldReceived() from:${event.senderId}: Unable to handle WithHeldContent for ${withHeldContent.algorithm}")
return
}
}
private fun onSecretSendReceived(event: Event) {
- Timber.i("## CRYPTO | GOSSIP onSecretSend() from ${event.senderId} : onSecretSendReceived ${event.content?.get("sender_key")}")
+ Timber.tag(loggerTag.value).i("GOSSIP onSecretSend() from ${event.senderId} : onSecretSendReceived ${event.content?.get("sender_key")}")
if (!event.isEncrypted()) {
// secret send messages must be encrypted
- Timber.e("## CRYPTO | GOSSIP onSecretSend() :Received unencrypted secret send event")
+ Timber.tag(loggerTag.value).e("GOSSIP onSecretSend() :Received unencrypted secret send event")
return
}
// Was that sent by us?
if (event.senderId != userId) {
- Timber.e("## CRYPTO | GOSSIP onSecretSend() : Ignore secret from other user ${event.senderId}")
+ Timber.tag(loggerTag.value).e("GOSSIP onSecretSend() : Ignore secret from other user ${event.senderId}")
return
}
@@ -815,13 +819,13 @@ internal class DefaultCryptoService @Inject constructor(
.getOutgoingSecretKeyRequests().firstOrNull { it.requestId == secretContent.requestId }
if (existingRequest == null) {
- Timber.i("## CRYPTO | GOSSIP onSecretSend() : Ignore secret that was not requested: ${secretContent.requestId}")
+ Timber.tag(loggerTag.value).i("GOSSIP onSecretSend() : Ignore secret that was not requested: ${secretContent.requestId}")
return
}
if (!handleSDKLevelGossip(existingRequest.secretName, secretContent.secretValue)) {
// TODO Ask to application layer?
- Timber.v("## CRYPTO | onSecretSend() : secret not handled by SDK")
+ Timber.tag(loggerTag.value).v("onSecretSend() : secret not handled by SDK")
}
}
@@ -858,7 +862,7 @@ internal class DefaultCryptoService @Inject constructor(
private fun onRoomEncryptionEvent(roomId: String, event: Event) {
if (!event.isStateEvent()) {
// Ignore
- Timber.w("Invalid encryption event")
+ Timber.tag(loggerTag.value).w("Invalid encryption event")
return
}
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
@@ -912,7 +916,7 @@ internal class DefaultCryptoService @Inject constructor(
*/
private suspend fun uploadDeviceKeys() {
if (cryptoStore.areDeviceKeysUploaded()) {
- Timber.d("Keys already uploaded, nothing to do")
+ Timber.tag(loggerTag.value).d("Keys already uploaded, nothing to do")
return
}
// Prepare the device keys data to send
@@ -971,13 +975,13 @@ internal class DefaultCryptoService @Inject constructor(
password: String,
progressListener: ProgressListener?): ImportRoomKeysResult {
return withContext(coroutineDispatchers.crypto) {
- Timber.v("## CRYPTO | importRoomKeys starts")
+ Timber.tag(loggerTag.value).v("importRoomKeys starts")
val t0 = System.currentTimeMillis()
val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password)
val t1 = System.currentTimeMillis()
- Timber.v("## CRYPTO | importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms")
+ Timber.tag(loggerTag.value).v("importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms")
val importedSessions = MoshiProvider.providesMoshi()
.adapter>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java))
@@ -985,7 +989,7 @@ internal class DefaultCryptoService @Inject constructor(
val t2 = System.currentTimeMillis()
- Timber.v("## CRYPTO | importRoomKeys : JSON parsing ${t2 - t1} ms")
+ Timber.tag(loggerTag.value).v("importRoomKeys : JSON parsing ${t2 - t1} ms")
if (importedSessions == null) {
throw Exception("Error")
@@ -1122,7 +1126,7 @@ internal class DefaultCryptoService @Inject constructor(
*/
override fun reRequestRoomKeyForEvent(event: Event) {
val wireContent = event.content.toModel() ?: return Unit.also {
- Timber.e("## CRYPTO | reRequestRoomKeyForEvent Failed to re-request key, null content")
+ Timber.tag(loggerTag.value).e("reRequestRoomKeyForEvent Failed to re-request key, null content")
}
val requestBody = RoomKeyRequestBody(
@@ -1137,7 +1141,7 @@ internal class DefaultCryptoService @Inject constructor(
override fun requestRoomKeyForEvent(event: Event) {
val wireContent = event.content.toModel() ?: return Unit.also {
- Timber.e("## CRYPTO | requestRoomKeyForEvent Failed to request key, null content eventId: ${event.eventId}")
+ Timber.tag(loggerTag.value).e("requestRoomKeyForEvent Failed to request key, null content eventId: ${event.eventId}")
}
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
@@ -1148,7 +1152,7 @@ internal class DefaultCryptoService @Inject constructor(
roomDecryptorProvider
.getOrCreateRoomDecryptor(event.roomId, wireContent.algorithm)
?.requestKeysForEvent(event, false) ?: run {
- Timber.v("## CRYPTO | requestRoomKeyForEvent() : No room decryptor for roomId:${event.roomId} algorithm:${wireContent.algorithm}")
+ Timber.tag(loggerTag.value).v("requestRoomKeyForEvent() : No room decryptor for roomId:${event.roomId} algorithm:${wireContent.algorithm}")
}
}
}
@@ -1287,12 +1291,12 @@ internal class DefaultCryptoService @Inject constructor(
override fun prepareToEncrypt(roomId: String, callback: MatrixCallback) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
- Timber.d("## CRYPTO | prepareToEncrypt() : Check room members up to date")
+ Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date")
// Ensure to load all room members
try {
loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId))
} catch (failure: Throwable) {
- Timber.e("## CRYPTO | prepareToEncrypt() : Failed to load room members")
+ Timber.tag(loggerTag.value).e("prepareToEncrypt() : Failed to load room members")
callback.onFailure(failure)
return@launch
}
@@ -1305,7 +1309,7 @@ internal class DefaultCryptoService @Inject constructor(
if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON)
- Timber.e("## CRYPTO | prepareToEncrypt() : $reason")
+ Timber.tag(loggerTag.value).e("prepareToEncrypt() : $reason")
callback.onFailure(IllegalArgumentException("Missing algorithm"))
return@launch
}
@@ -1315,7 +1319,7 @@ internal class DefaultCryptoService @Inject constructor(
}.fold(
{ callback.onSuccess(Unit) },
{
- Timber.e("## CRYPTO | prepareToEncrypt() failed.")
+ Timber.tag(loggerTag.value).e(it, "prepareToEncrypt() failed.")
callback.onFailure(it)
}
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt
index 3979ff1fb4..ab2ed04dfb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt
@@ -16,17 +16,21 @@
package org.matrix.android.sdk.internal.crypto.actions
+import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.MXKey
import org.matrix.android.sdk.internal.crypto.model.MXOlmSessionResult
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
+import org.matrix.android.sdk.internal.crypto.model.toDebugString
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
import timber.log.Timber
import javax.inject.Inject
private const val ONE_TIME_KEYS_RETRY_COUNT = 3
+private val loggerTag = LoggerTag("EnsureOlmSessionsForDevicesAction", LoggerTag.CRYPTO)
+
internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
private val olmDevice: MXOlmDevice,
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) {
@@ -36,15 +40,22 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
val results = MXUsersDevicesMap()
- for ((userId, deviceInfos) in devicesByUser) {
- for (deviceInfo in deviceInfos) {
+ for ((userId, deviceList) in devicesByUser) {
+ for (deviceInfo in deviceList) {
val deviceId = deviceInfo.deviceId
val key = deviceInfo.identityKey()
+ if (key == null) {
+ Timber.w("## CRYPTO | Ignoring device (${deviceInfo.userId}|$deviceId) without identity key")
+ continue
+ }
- val sessionId = olmDevice.getSessionId(key!!)
+ val sessionId = olmDevice.getSessionId(key)
if (sessionId.isNullOrEmpty() || force) {
+ Timber.tag(loggerTag.value).d("Found no existing olm session (${deviceInfo.userId}|$deviceId) (force=$force)")
devicesWithoutSession.add(deviceInfo)
+ } else {
+ Timber.tag(loggerTag.value).d("using olm session $sessionId for (${deviceInfo.userId}|$deviceId)")
}
val olmSessionResult = MXOlmSessionResult(deviceInfo, sessionId)
@@ -52,6 +63,8 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
}
}
+ Timber.tag(loggerTag.value).d("Devices without olm session (count:${devicesWithoutSession.size}) :" +
+ " ${devicesWithoutSession.joinToString { "${it.userId}|${it.deviceId}" }}")
if (devicesWithoutSession.size == 0) {
return results
}
@@ -71,11 +84,11 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
//
// That should eventually resolve itself, but it's poor form.
- Timber.i("## CRYPTO | claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim")
+ Timber.tag(loggerTag.value).i("claimOneTimeKeysForUsersDevices() : ${usersDevicesToClaim.toDebugString()}")
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
val oneTimeKeys = oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, remainingRetry = ONE_TIME_KEYS_RETRY_COUNT)
- Timber.v("## CRYPTO | claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $oneTimeKeys")
+ Timber.tag(loggerTag.value).v("claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $oneTimeKeys")
for ((userId, deviceInfos) in devicesByUser) {
for (deviceInfo in deviceInfos) {
var oneTimeKey: MXKey? = null
@@ -83,7 +96,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
if (null != deviceIds) {
for (deviceId in deviceIds) {
val olmSessionResult = results.getObject(userId, deviceId)
- if (olmSessionResult!!.sessionId != null && !force) {
+ if (olmSessionResult?.sessionId != null && !force) {
// We already have a result for this device
continue
}
@@ -92,12 +105,11 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
oneTimeKey = key
}
if (oneTimeKey == null) {
- Timber.w("## CRYPTO | ensureOlmSessionsForDevices() : No one-time keys " + oneTimeKeyAlgorithm +
- " for device " + userId + " : " + deviceId)
+ Timber.tag(loggerTag.value).d("No one time key for $userId|$deviceId")
continue
}
// Update the result for this device in results
- olmSessionResult.sessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo)
+ olmSessionResult?.sessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo)
}
}
}
@@ -112,31 +124,36 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
val signKeyId = "ed25519:$deviceId"
val signature = oneTimeKey.signatureForUserId(userId, signKeyId)
- if (!signature.isNullOrEmpty() && !deviceInfo.fingerprint().isNullOrEmpty()) {
+ val fingerprint = deviceInfo.fingerprint()
+ if (!signature.isNullOrEmpty() && !fingerprint.isNullOrEmpty()) {
var isVerified = false
var errorMessage: String? = null
try {
- olmDevice.verifySignature(deviceInfo.fingerprint()!!, oneTimeKey.signalableJSONDictionary(), signature)
+ olmDevice.verifySignature(fingerprint, oneTimeKey.signalableJSONDictionary(), signature)
isVerified = true
} catch (e: Exception) {
+ Timber.tag(loggerTag.value).d(e, "verifyKeyAndStartSession() : Verify error for otk: ${oneTimeKey.signalableJSONDictionary()}," +
+ " signature:$signature fingerprint:$fingerprint")
+ Timber.tag(loggerTag.value).e("verifyKeyAndStartSession() : Verify error for ${deviceInfo.userId}|${deviceInfo.deviceId} " +
+ " - signable json ${oneTimeKey.signalableJSONDictionary()}")
errorMessage = e.message
}
// Check one-time key signature
if (isVerified) {
- sessionId = olmDevice.createOutboundSession(deviceInfo.identityKey()!!, oneTimeKey.value)
+ sessionId = deviceInfo.identityKey()?.let { identityKey ->
+ olmDevice.createOutboundSession(identityKey, oneTimeKey.value)
+ }
- if (!sessionId.isNullOrEmpty()) {
- Timber.v("## CRYPTO | verifyKeyAndStartSession() : Started new sessionid " + sessionId +
- " for device " + deviceInfo + "(theirOneTimeKey: " + oneTimeKey.value + ")")
- } else {
+ if (sessionId.isNullOrEmpty()) {
// Possibly a bad key
- Timber.e("## CRYPTO | verifyKeyAndStartSession() : Error starting session with device $userId:$deviceId")
+ Timber.tag(loggerTag.value).e("verifyKeyAndStartSession() : Error starting session with device $userId:$deviceId")
+ } else {
+ Timber.tag(loggerTag.value).d("verifyKeyAndStartSession() : Started new sessionId $sessionId for device $userId:$deviceId")
}
} else {
- Timber.e("## CRYPTO | verifyKeyAndStartSession() : Unable to verify signature on one-time key for device " + userId +
- ":" + deviceId + " Error " + errorMessage)
+ Timber.tag(loggerTag.value).e("verifyKeyAndStartSession() : Unable to verify otk signature for $userId:$deviceId: $errorMessage")
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
index d7411ad0be..8bbc71543c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
+import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
@@ -44,6 +45,8 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
import timber.log.Timber
+private val loggerTag = LoggerTag("MXMegolmDecryption", LoggerTag.CRYPTO)
+
internal class MXMegolmDecryption(private val userId: String,
private val olmDevice: MXOlmDevice,
private val deviceListManager: DeviceListManager,
@@ -74,7 +77,7 @@ internal class MXMegolmDecryption(private val userId: String,
@Throws(MXCryptoError::class)
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult {
- Timber.v("## CRYPTO | decryptEvent ${event.eventId}, requestKeysOnFail:$requestKeysOnFail")
+ Timber.tag(loggerTag.value).v("decryptEvent ${event.eventId}, requestKeysOnFail:$requestKeysOnFail")
if (event.roomId.isNullOrBlank()) {
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
}
@@ -230,7 +233,7 @@ internal class MXMegolmDecryption(private val userId: String,
* @param event the key event.
*/
override fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {
- Timber.v("## CRYPTO | onRoomKeyEvent()")
+ Timber.tag(loggerTag.value).v("onRoomKeyEvent()")
var exportFormat = false
val roomKeyContent = event.getClearContent().toModel() ?: return
@@ -239,11 +242,11 @@ internal class MXMegolmDecryption(private val userId: String,
val forwardingCurve25519KeyChain: MutableList = ArrayList()
if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.sessionId.isNullOrEmpty() || roomKeyContent.sessionKey.isNullOrEmpty()) {
- Timber.e("## CRYPTO | onRoomKeyEvent() : Key event is missing fields")
+ Timber.tag(loggerTag.value).e("onRoomKeyEvent() : Key event is missing fields")
return
}
if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
- Timber.i("## CRYPTO | onRoomKeyEvent(), forward adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
+ Timber.tag(loggerTag.value).i("onRoomKeyEvent(), forward adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
val forwardedRoomKeyContent = event.getClearContent().toModel()
?: return
@@ -252,7 +255,7 @@ internal class MXMegolmDecryption(private val userId: String,
}
if (senderKey == null) {
- Timber.e("## CRYPTO | onRoomKeyEvent() : event is missing sender_key field")
+ Timber.tag(loggerTag.value).e("onRoomKeyEvent() : event is missing sender_key field")
return
}
@@ -261,20 +264,20 @@ internal class MXMegolmDecryption(private val userId: String,
exportFormat = true
senderKey = forwardedRoomKeyContent.senderKey
if (null == senderKey) {
- Timber.e("## CRYPTO | onRoomKeyEvent() : forwarded_room_key event is missing sender_key field")
+ Timber.tag(loggerTag.value).e("onRoomKeyEvent() : forwarded_room_key event is missing sender_key field")
return
}
if (null == forwardedRoomKeyContent.senderClaimedEd25519Key) {
- Timber.e("## CRYPTO | forwarded_room_key_event is missing sender_claimed_ed25519_key field")
+ Timber.tag(loggerTag.value).e("forwarded_room_key_event is missing sender_claimed_ed25519_key field")
return
}
keysClaimed["ed25519"] = forwardedRoomKeyContent.senderClaimedEd25519Key
} else {
- Timber.i("## CRYPTO | onRoomKeyEvent(), Adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
+ Timber.tag(loggerTag.value).i("onRoomKeyEvent(), Adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
if (null == senderKey) {
- Timber.e("## onRoomKeyEvent() : key event has no sender key (not encrypted?)")
+ Timber.tag(loggerTag.value).e("## onRoomKeyEvent() : key event has no sender key (not encrypted?)")
return
}
@@ -282,7 +285,7 @@ internal class MXMegolmDecryption(private val userId: String,
keysClaimed = event.getKeysClaimed().toMutableMap()
}
- Timber.i("## CRYPTO | onRoomKeyEvent addInboundGroupSession ${roomKeyContent.sessionId}")
+ Timber.tag(loggerTag.value).i("onRoomKeyEvent addInboundGroupSession ${roomKeyContent.sessionId}")
val added = olmDevice.addInboundGroupSession(roomKeyContent.sessionId,
roomKeyContent.sessionKey,
roomKeyContent.roomId,
@@ -314,7 +317,7 @@ internal class MXMegolmDecryption(private val userId: String,
* @param sessionId the session id
*/
override fun onNewSession(senderKey: String, sessionId: String) {
- Timber.v(" CRYPTO | ON NEW SESSION $sessionId - $senderKey")
+ Timber.tag(loggerTag.value).v("ON NEW SESSION $sessionId - $senderKey")
newSessionListener?.onNewSession(null, senderKey, sessionId)
}
@@ -346,10 +349,10 @@ internal class MXMegolmDecryption(private val userId: String,
if (olmSessionResult?.sessionId == null) {
// no session with this device, probably because there
// were no one-time keys.
- Timber.e("no session with this device $deviceId, probably because there were no one-time keys.")
+ Timber.tag(loggerTag.value).e("no session with this device $deviceId, probably because there were no one-time keys.")
return@mapCatching
}
- Timber.i("## CRYPTO | shareKeysWithDevice() : sharing session ${body.sessionId} with device $userId:$deviceId")
+ Timber.tag(loggerTag.value).i("shareKeysWithDevice() : sharing session ${body.sessionId} with device $userId:$deviceId")
val payloadJson = mutableMapOf("type" to EventType.FORWARDED_ROOM_KEY)
runCatching { olmDevice.getInboundGroupSession(body.sessionId, body.senderKey, body.roomId) }
@@ -360,7 +363,7 @@ internal class MXMegolmDecryption(private val userId: String,
},
{
// TODO
- Timber.e(it, "## CRYPTO | shareKeysWithDevice: failed to get session for request $body")
+ Timber.tag(loggerTag.value).e(it, "shareKeysWithDevice: failed to get session for request $body")
}
)
@@ -368,12 +371,12 @@ internal class MXMegolmDecryption(private val userId: String,
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap()
sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
- Timber.i("## CRYPTO | shareKeysWithDevice() : sending ${body.sessionId} to $userId:$deviceId")
+ Timber.tag(loggerTag.value).i("shareKeysWithDevice() : sending ${body.sessionId} to $userId:$deviceId")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
try {
sendToDeviceTask.execute(sendToDeviceParams)
} catch (failure: Throwable) {
- Timber.e(failure, "## CRYPTO | shareKeysWithDevice() : Failed to send ${body.sessionId} to $userId:$deviceId")
+ Timber.tag(loggerTag.value).e(failure, "shareKeysWithDevice() : Failed to send ${body.sessionId} to $userId:$deviceId")
}
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
index 031bb4e194..389036a1f8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
+import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.Event
@@ -36,6 +37,8 @@ import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
import org.matrix.android.sdk.internal.crypto.model.forEach
+import org.matrix.android.sdk.internal.crypto.model.toDebugCount
+import org.matrix.android.sdk.internal.crypto.model.toDebugString
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
@@ -43,6 +46,8 @@ import org.matrix.android.sdk.internal.util.JsonCanonicalizer
import org.matrix.android.sdk.internal.util.convertToUTF8
import timber.log.Timber
+private val loggerTag = LoggerTag("MXMegolmEncryption", LoggerTag.CRYPTO)
+
internal class MXMegolmEncryption(
// The id of the room we will be sending to.
private val roomId: String,
@@ -51,8 +56,8 @@ internal class MXMegolmEncryption(
private val cryptoStore: IMXCryptoStore,
private val deviceListManager: DeviceListManager,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
- private val userId: String,
- private val deviceId: String,
+ private val myUserId: String,
+ private val myDeviceId: String,
private val sendToDeviceTask: SendToDeviceTask,
private val messageEncrypter: MessageEncrypter,
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
@@ -80,9 +85,10 @@ internal class MXMegolmEncryption(
eventType: String,
userIds: List): Content {
val ts = System.currentTimeMillis()
- Timber.v("## CRYPTO | encryptEventContent : getDevicesInRoom")
+ Timber.tag(loggerTag.value).v("encryptEventContent : getDevicesInRoom")
val devices = getDevicesInRoom(userIds)
- Timber.v("## CRYPTO | encryptEventContent ${System.currentTimeMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.map}")
+ Timber.tag(loggerTag.value).d("encrypt event in room=$roomId - devices count in room ${devices.allowedDevices.toDebugCount()}")
+ Timber.tag(loggerTag.value).v("encryptEventContent ${System.currentTimeMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.map}")
val outboundSession = ensureOutboundSession(devices.allowedDevices)
return encryptContent(outboundSession, eventType, eventContent)
@@ -91,7 +97,7 @@ internal class MXMegolmEncryption(
// annoyingly we have to serialize again the saved outbound session to store message index :/
// if not we would see duplicate message index errors
olmDevice.storeOutboundGroupSessionForRoom(roomId, outboundSession.sessionId)
- Timber.v("## CRYPTO | encryptEventContent: Finished in ${System.currentTimeMillis() - ts} millis")
+ Timber.tag(loggerTag.value).d("encrypt event in room=$roomId Finished in ${System.currentTimeMillis() - ts} millis")
}
}
@@ -118,13 +124,13 @@ internal class MXMegolmEncryption(
override suspend fun preshareKey(userIds: List) {
val ts = System.currentTimeMillis()
- Timber.v("## CRYPTO | preshareKey : getDevicesInRoom")
+ Timber.tag(loggerTag.value).d("preshareKey started in $roomId ...")
val devices = getDevicesInRoom(userIds)
val outboundSession = ensureOutboundSession(devices.allowedDevices)
notifyWithheldForSession(devices.withHeldDevices, outboundSession)
- Timber.v("## CRYPTO | preshareKey ${System.currentTimeMillis() - ts} millis")
+ Timber.tag(loggerTag.value).d("preshareKey in $roomId done in ${System.currentTimeMillis() - ts} millis")
}
/**
@@ -133,7 +139,7 @@ internal class MXMegolmEncryption(
* @return the session description
*/
private fun prepareNewSessionInRoom(): MXOutboundSessionInfo {
- Timber.v("## CRYPTO | prepareNewSessionInRoom() ")
+ Timber.tag(loggerTag.value).v("prepareNewSessionInRoom() ")
val sessionId = olmDevice.createOutboundGroupSessionForRoom(roomId)
val keysClaimedMap = HashMap()
@@ -153,13 +159,14 @@ internal class MXMegolmEncryption(
* @param devicesInRoom the devices list
*/
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap): MXOutboundSessionInfo {
- Timber.v("## CRYPTO | ensureOutboundSession start")
+ Timber.tag(loggerTag.value).v("ensureOutboundSession roomId:$roomId")
var session = outboundSession
if (session == null ||
// Need to make a brand new session?
session.needsRotation(sessionRotationPeriodMsgs, sessionRotationPeriodMs) ||
// Determine if we have shared with anyone we shouldn't have
session.sharedWithTooManyDevices(devicesInRoom)) {
+ Timber.tag(loggerTag.value).d("roomId:$roomId Starting new megolm session because we need to rotate.")
session = prepareNewSessionInRoom()
outboundSession = session
}
@@ -176,6 +183,8 @@ internal class MXMegolmEncryption(
}
}
}
+ val devicesCount = shareMap.entries.fold(0) { acc, new -> acc + new.value.size }
+ Timber.tag(loggerTag.value).d("roomId:$roomId found $devicesCount devices without megolm session(${session.sessionId})")
shareKey(safeSession, shareMap)
return safeSession
}
@@ -190,7 +199,7 @@ internal class MXMegolmEncryption(
devicesByUsers: Map>) {
// nothing to send, the task is done
if (devicesByUsers.isEmpty()) {
- Timber.v("## CRYPTO | shareKey() : nothing more to do")
+ Timber.tag(loggerTag.value).v("shareKey() : nothing more to do")
return
}
// reduce the map size to avoid request timeout when there are too many devices (Users size * devices per user)
@@ -203,7 +212,7 @@ internal class MXMegolmEncryption(
break
}
}
- Timber.v("## CRYPTO | shareKey() ; sessionId<${session.sessionId}> userId ${subMap.keys}")
+ Timber.tag(loggerTag.value).v("shareKey() ; sessionId<${session.sessionId}> userId ${subMap.keys}")
shareUserDevicesKey(session, subMap)
val remainingDevices = devicesByUsers - subMap.keys
shareKey(session, remainingDevices)
@@ -232,11 +241,11 @@ internal class MXMegolmEncryption(
payload["content"] = submap
var t0 = System.currentTimeMillis()
- Timber.v("## CRYPTO | shareUserDevicesKey() : starts")
+ Timber.tag(loggerTag.value).v("shareUserDevicesKey() : starts")
val results = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
- Timber.v(
- """## CRYPTO | shareUserDevicesKey(): ensureOlmSessionsForDevices succeeds after ${System.currentTimeMillis() - t0} ms"""
+ Timber.tag(loggerTag.value).v(
+ """shareUserDevicesKey(): ensureOlmSessionsForDevices succeeds after ${System.currentTimeMillis() - t0} ms"""
.trimMargin()
)
val contentMap = MXUsersDevicesMap()
@@ -254,10 +263,11 @@ internal class MXMegolmEncryption(
// MSC 2399
// send withheld m.no_olm: an olm session could not be established.
// This may happen, for example, if the sender was unable to obtain a one-time key from the recipient.
+ Timber.tag(loggerTag.value).v("shareUserDevicesKey() : No Olm Session for $userId:$deviceID mark for withheld")
noOlmToNotify.add(UserDevice(userId, deviceID))
continue
}
- Timber.i("## CRYPTO | shareUserDevicesKey() : Add to share keys contentMap for $userId:$deviceID")
+ Timber.tag(loggerTag.value).v("shareUserDevicesKey() : Add to share keys contentMap for $userId:$deviceID")
contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, listOf(sessionResult.deviceInfo)))
haveTargets = true
}
@@ -275,7 +285,7 @@ internal class MXMegolmEncryption(
gossipingEventBuffer.add(
Event(
type = EventType.ROOM_KEY,
- senderId = this.userId,
+ senderId = myUserId,
content = submap.apply {
this["session_key"] = ""
// we add a fake key for trail
@@ -289,17 +299,18 @@ internal class MXMegolmEncryption(
if (haveTargets) {
t0 = System.currentTimeMillis()
- Timber.i("## CRYPTO | shareUserDevicesKey() ${session.sessionId} : has target")
+ Timber.tag(loggerTag.value).i("shareUserDevicesKey() ${session.sessionId} : has target")
+ Timber.tag(loggerTag.value).d("sending to device room key for ${session.sessionId} to ${contentMap.toDebugString()}")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
try {
sendToDeviceTask.execute(sendToDeviceParams)
- Timber.i("## CRYPTO | shareUserDevicesKey() : sendToDevice succeeds after ${System.currentTimeMillis() - t0} ms")
+ Timber.tag(loggerTag.value).i("shareUserDevicesKey() : sendToDevice succeeds after ${System.currentTimeMillis() - t0} ms")
} catch (failure: Throwable) {
// What to do here...
- Timber.e("## CRYPTO | shareUserDevicesKey() : Failed to share session <${session.sessionId}> with $devicesByUser ")
+ Timber.tag(loggerTag.value).e("shareUserDevicesKey() : Failed to share session <${session.sessionId}> with $devicesByUser ")
}
} else {
- Timber.i("## CRYPTO | shareUserDevicesKey() : no need to sharekey")
+ Timber.tag(loggerTag.value).i("shareUserDevicesKey() : no need to share key")
}
if (noOlmToNotify.isNotEmpty()) {
@@ -317,7 +328,8 @@ internal class MXMegolmEncryption(
sessionId: String,
senderKey: String?,
code: WithHeldCode) {
- Timber.i("## CRYPTO | notifyKeyWithHeld() :sending withheld key for $targets session:$sessionId and code $code")
+ Timber.tag(loggerTag.value).d("notifyKeyWithHeld() :sending withheld for session:$sessionId and code $code to" +
+ " ${targets.joinToString { "${it.userId}|${it.deviceId}" }}")
val withHeldContent = RoomKeyWithHeldContent(
roomId = roomId,
senderKey = senderKey,
@@ -336,7 +348,7 @@ internal class MXMegolmEncryption(
try {
sendToDeviceTask.execute(params)
} catch (failure: Throwable) {
- Timber.e("## CRYPTO | notifyKeyWithHeld() : Failed to notify withheld key for $targets session: $sessionId ")
+ Timber.tag(loggerTag.value).e("notifyKeyWithHeld() : Failed to notify withheld key for $targets session: $sessionId ")
}
}
@@ -363,7 +375,7 @@ internal class MXMegolmEncryption(
// Include our device ID so that recipients can send us a
// m.new_device message if they don't have our session key.
- map["device_id"] = deviceId
+ map["device_id"] = myDeviceId
session.useCount++
return map
}
@@ -424,9 +436,9 @@ internal class MXMegolmEncryption(
userId: String,
deviceId: String,
senderKey: String): Boolean {
- Timber.i("## Crypto process reshareKey for $sessionId to $userId:$deviceId")
+ Timber.tag(loggerTag.value).i("process reshareKey for $sessionId to $userId:$deviceId")
val deviceInfo = cryptoStore.getUserDevice(userId, deviceId) ?: return false
- .also { Timber.w("## Crypto reshareKey: Device not found") }
+ .also { Timber.tag(loggerTag.value).w("reshareKey: Device not found") }
// Get the chain index of the key we previously sent this device
val wasSessionSharedWithUser = cryptoStore.getSharedSessionInfo(roomId, sessionId, deviceInfo)
@@ -434,13 +446,13 @@ internal class MXMegolmEncryption(
// This session was never shared with this user
// Send a room key with held
notifyKeyWithHeld(listOf(UserDevice(userId, deviceId)), sessionId, senderKey, WithHeldCode.UNAUTHORISED)
- Timber.w("## Crypto reshareKey: ERROR : Never shared megolm with this device")
+ Timber.tag(loggerTag.value).w("reshareKey: ERROR : Never shared megolm with this device")
return false
}
// if found chain index should not be null
val chainIndex = wasSessionSharedWithUser.chainIndex ?: return false
.also {
- Timber.w("## Crypto reshareKey: Null chain index")
+ Timber.tag(loggerTag.value).w("reshareKey: Null chain index")
}
val devicesByUser = mapOf(userId to listOf(deviceInfo))
@@ -449,10 +461,10 @@ internal class MXMegolmEncryption(
olmSessionResult?.sessionId // no session with this device, probably because there were no one-time keys.
// ensureOlmSessionsForDevicesAction has already done the logging, so just skip it.
?: return false.also {
- Timber.w("## Crypto reshareKey: no session with this device, probably because there were no one-time keys")
+ Timber.tag(loggerTag.value).w("reshareKey: no session with this device, probably because there were no one-time keys")
}
- Timber.i("[MXMegolmEncryption] reshareKey: sharing keys for session $senderKey|$sessionId:$chainIndex with device $userId:$deviceId")
+ Timber.tag(loggerTag.value).i(" reshareKey: sharing keys for session $senderKey|$sessionId:$chainIndex with device $userId:$deviceId")
val payloadJson = mutableMapOf("type" to EventType.FORWARDED_ROOM_KEY)
@@ -464,7 +476,7 @@ internal class MXMegolmEncryption(
},
{
// TODO
- Timber.e(it, "[MXMegolmEncryption] reshareKey: failed to get session $sessionId|$senderKey|$roomId")
+ Timber.tag(loggerTag.value).e(it, "reshareKey: failed to get session $sessionId|$senderKey|$roomId")
}
)
@@ -472,14 +484,14 @@ internal class MXMegolmEncryption(
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap()
sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
- Timber.i("## CRYPTO | reshareKey() : sending session $sessionId to $userId:$deviceId")
+ Timber.tag(loggerTag.value).i("reshareKey() : sending session $sessionId to $userId:$deviceId")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
return try {
sendToDeviceTask.execute(sendToDeviceParams)
- Timber.i("## CRYPTO reshareKey() : successfully send <$sessionId> to $userId:$deviceId")
+ Timber.tag(loggerTag.value).i("reshareKey() : successfully send <$sessionId> to $userId:$deviceId")
true
} catch (failure: Throwable) {
- Timber.e(failure, "## CRYPTO reshareKey() : fail to send <$sessionId> to $userId:$deviceId")
+ Timber.tag(loggerTag.value).e(failure, "reshareKey() : fail to send <$sessionId> to $userId:$deviceId")
false
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt
index 238d7eed88..136fdc05f5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt
@@ -52,8 +52,8 @@ internal class MXMegolmEncryptionFactory @Inject constructor(
cryptoStore = cryptoStore,
deviceListManager = deviceListManager,
ensureOlmSessionsForDevicesAction = ensureOlmSessionsForDevicesAction,
- userId = userId,
- deviceId = deviceId!!,
+ myUserId = userId,
+ myDeviceId = deviceId!!,
sendToDeviceTask = sendToDeviceTask,
messageEncrypter = messageEncrypter,
warnOnUnknownDevicesRepository = warnOnUnknownDevicesRepository,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt
index 9d7f2d9883..662541428e 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt
@@ -129,3 +129,11 @@ inline fun MXUsersDevicesMap.forEach(action: (String, String, T) -> Unit)
}
}
}
+
+internal fun MXUsersDevicesMap.toDebugString() =
+ map.entries.joinToString { "${it.key} [${it.value.keys.joinToString { it }}]" }
+
+internal fun MXUsersDevicesMap.toDebugCount() =
+ map.entries.fold(0) { acc, new ->
+ acc + new.value.keys.size
+ }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt
index e40db6af67..bdfe818c62 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt
@@ -23,6 +23,7 @@ import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
import org.matrix.android.sdk.internal.task.Task
+import timber.log.Timber
import javax.inject.Inject
internal interface SendEventTask : Task {
@@ -60,7 +61,9 @@ internal class DefaultSendEventTask @Inject constructor(
)
}
localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENT)
- return response.eventId
+ return response.eventId.also {
+ Timber.d("Event: $it just sent in ${params.event.roomId}")
+ }
} catch (e: Throwable) {
// localEchoRepository.updateSendState(params.event.eventId!!, SendState.UNDELIVERED)
throw e
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt
index 148f727ba7..426c52f9ef 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt
@@ -26,20 +26,20 @@ internal object DraftMapper {
fun map(entity: DraftEntity): UserDraft {
return when (entity.draftMode) {
- DraftEntity.MODE_REGULAR -> UserDraft.REGULAR(entity.content)
- DraftEntity.MODE_EDIT -> UserDraft.EDIT(entity.linkedEventId, entity.content)
- DraftEntity.MODE_QUOTE -> UserDraft.QUOTE(entity.linkedEventId, entity.content)
- DraftEntity.MODE_REPLY -> UserDraft.REPLY(entity.linkedEventId, entity.content)
+ DraftEntity.MODE_REGULAR -> UserDraft.Regular(entity.content)
+ DraftEntity.MODE_EDIT -> UserDraft.Edit(entity.linkedEventId, entity.content)
+ DraftEntity.MODE_QUOTE -> UserDraft.Quote(entity.linkedEventId, entity.content)
+ DraftEntity.MODE_REPLY -> UserDraft.Reply(entity.linkedEventId, entity.content)
else -> null
- } ?: UserDraft.REGULAR("")
+ } ?: UserDraft.Regular("")
}
fun map(domain: UserDraft): DraftEntity {
return when (domain) {
- is UserDraft.REGULAR -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_REGULAR, linkedEventId = "")
- is UserDraft.EDIT -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_EDIT, linkedEventId = domain.linkedEventId)
- is UserDraft.QUOTE -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_QUOTE, linkedEventId = domain.linkedEventId)
- is UserDraft.REPLY -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_REPLY, linkedEventId = domain.linkedEventId)
+ is UserDraft.Regular -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_REGULAR, linkedEventId = "")
+ is UserDraft.Edit -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_EDIT, linkedEventId = domain.linkedEventId)
+ is UserDraft.Quote -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_QUOTE, linkedEventId = domain.linkedEventId)
+ is UserDraft.Reply -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_REPLY, linkedEventId = domain.linkedEventId)
}
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt
index bcd30cb54b..836fc4efaf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.database.model
import io.realm.RealmObject
import io.realm.annotations.Index
import org.matrix.android.sdk.api.session.room.send.SendState
+import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
import org.matrix.android.sdk.internal.di.MoshiProvider
@@ -56,10 +57,10 @@ internal open class EventEntity(@Index var eventId: String = "",
companion object
- fun setDecryptionResult(result: MXEventDecryptionResult) {
+ fun setDecryptionResult(result: MXEventDecryptionResult, clearEvent: JsonDict? = null) {
assertIsManaged()
val decryptionResult = OlmDecryptionResult(
- payload = result.clearEvent,
+ payload = clearEvent ?: result.clearEvent,
senderKey = result.senderCurve25519Key,
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/DbQualifiers.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/DbQualifiers.kt
index 49e155add8..8b78cd0d42 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/DbQualifiers.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/DbQualifiers.kt
@@ -37,3 +37,7 @@ internal annotation class CryptoDatabase
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
internal annotation class IdentityDatabase
+
+@Qualifier
+@Retention(AnnotationRetention.RUNTIME)
+internal annotation class ContentScannerDatabase
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt
index 361a306d4f..1ab1042129 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt
@@ -38,6 +38,9 @@ internal object NetworkConstants {
// Integration
const val URI_INTEGRATION_MANAGER_PATH = "_matrix/integrations/v1/"
+ // Content scanner
+ const val URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE = "_matrix/media_proxy/unstable/"
+
// Federation
const val URI_FEDERATION_PATH = "_matrix/federation/v1/"
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
index 46c5967876..14dfc097cf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
@@ -23,8 +23,10 @@ import androidx.core.content.FileProvider
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.completeWith
import kotlinx.coroutines.withContext
+import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
+import okhttp3.RequestBody.Companion.toRequestBody
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
@@ -118,12 +120,24 @@ internal class DefaultFileService @Inject constructor(
val cachedFiles = getFiles(url, fileName, mimeType, elementToDecrypt != null)
if (!cachedFiles.file.exists()) {
- val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: throw IllegalArgumentException("url is null")
+ val resolvedUrl = contentUrlResolver.resolveForDownload(url, elementToDecrypt) ?: throw IllegalArgumentException("url is null")
- val request = Request.Builder()
- .url(resolvedUrl)
- .header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url)
- .build()
+ val request = when (resolvedUrl) {
+ is ContentUrlResolver.ResolvedMethod.GET -> {
+ Request.Builder()
+ .url(resolvedUrl.url)
+ .header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url)
+ .build()
+ }
+
+ is ContentUrlResolver.ResolvedMethod.POST -> {
+ Request.Builder()
+ .url(resolvedUrl.url)
+ .header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url)
+ .post(resolvedUrl.jsonBody.toRequestBody("application/json".toMediaType()))
+ .build()
+ }
+ }
val response = try {
okHttpClient.newCall(request).execute()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
index c52462612a..c07ff48cf4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
@@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.session.cache.CacheService
import org.matrix.android.sdk.api.session.call.CallSignalingService
import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
+import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.events.EventService
import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
@@ -124,6 +125,7 @@ internal class DefaultSession @Inject constructor(
private val _sharedSecretStorageService: Lazy,
private val accountService: Lazy,
private val eventService: Lazy,
+ private val contentScannerService: Lazy,
private val identityService: IdentityService,
private val integrationManagerService: IntegrationManagerService,
private val thirdPartyService: Lazy,
@@ -174,8 +176,8 @@ internal class DefaultSession @Inject constructor(
lifecycleObservers.forEach {
it.onSessionStarted(this)
}
- sessionListeners.dispatch { _, listener ->
- listener.onSessionStarted(this)
+ dispatchTo(sessionListeners) { session, listener ->
+ listener.onSessionStarted(session)
}
}
}
@@ -217,8 +219,8 @@ internal class DefaultSession @Inject constructor(
// timelineEventDecryptor.destroy()
uiHandler.post {
lifecycleObservers.forEach { it.onSessionStopped(this) }
- sessionListeners.dispatch { _, listener ->
- listener.onSessionStopped(this)
+ dispatchTo(sessionListeners) { session, listener ->
+ listener.onSessionStopped(session)
}
}
cryptoService.get().close()
@@ -249,8 +251,8 @@ internal class DefaultSession @Inject constructor(
lifecycleObservers.forEach {
it.onClearCache(this)
}
- sessionListeners.dispatch { _, listener ->
- listener.onClearCache(this)
+ dispatchTo(sessionListeners) { session, listener ->
+ listener.onClearCache(session)
}
}
withContext(NonCancellable) {
@@ -260,8 +262,8 @@ internal class DefaultSession @Inject constructor(
}
override fun onGlobalError(globalError: GlobalError) {
- sessionListeners.dispatch { _, listener ->
- listener.onGlobalError(this, globalError)
+ dispatchTo(sessionListeners) { session, listener ->
+ listener.onGlobalError(session, globalError)
}
}
@@ -275,6 +277,8 @@ internal class DefaultSession @Inject constructor(
override fun cryptoService(): CryptoService = cryptoService.get()
+ override fun contentScannerService(): ContentScannerService = contentScannerService.get()
+
override fun identityService() = identityService
override fun fileService(): FileService = defaultFileService.get()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
index bc8a707530..76e5d84e56 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
@@ -36,6 +36,7 @@ import org.matrix.android.sdk.internal.session.cache.CacheModule
import org.matrix.android.sdk.internal.session.call.CallModule
import org.matrix.android.sdk.internal.session.content.ContentModule
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
+import org.matrix.android.sdk.internal.session.contentscanner.ContentScannerModule
import org.matrix.android.sdk.internal.session.filter.FilterModule
import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker
import org.matrix.android.sdk.internal.session.group.GroupModule
@@ -94,6 +95,7 @@ import org.matrix.android.sdk.internal.util.system.SystemModule
AccountModule::class,
FederationModule::class,
CallModule::class,
+ ContentScannerModule::class,
SearchModule::class,
ThirdPartyModule::class,
SpaceModule::class,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt
index d5c661b1e4..756b9cef83 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt
@@ -18,15 +18,11 @@ package org.matrix.android.sdk.internal.session
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session
-import org.matrix.android.sdk.internal.SessionManager
-import org.matrix.android.sdk.internal.di.SessionId
import timber.log.Timber
import javax.inject.Inject
@SessionScope
-internal class SessionListeners @Inject constructor(
- @SessionId private val sessionId: String,
- private val sessionManager: SessionManager) {
+internal class SessionListeners @Inject constructor() {
private val listeners = mutableSetOf()
@@ -42,18 +38,19 @@ internal class SessionListeners @Inject constructor(
}
}
- fun dispatch(block: (Session, Session.Listener) -> Unit) {
+ fun dispatch(session: Session, block: (Session, Session.Listener) -> Unit) {
synchronized(listeners) {
- val session = getSession() ?: return Unit.also {
- Timber.w("You don't have any attached session")
- }
listeners.forEach {
tryOrNull { block(session, it) }
}
}
}
-
- private fun getSession(): Session? {
- return sessionManager.getSessionComponent(sessionId)?.session()
- }
+}
+
+internal fun Session?.dispatchTo(sessionListeners: SessionListeners, block: (Session, Session.Listener) -> Unit) {
+ if (this == null) {
+ Timber.w("You don't have any attached session")
+ return
+ }
+ sessionListeners.dispatch(this, block)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt
index 5c8cf99dc6..660ab8726f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt
@@ -16,20 +16,45 @@
package org.matrix.android.sdk.internal.session.content
-import org.matrix.android.sdk.api.MatrixUrls
import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
+import org.matrix.android.sdk.api.MatrixUrls.removeMxcPrefix
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
+import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
+import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
import org.matrix.android.sdk.internal.network.NetworkConstants
+import org.matrix.android.sdk.internal.session.contentscanner.ScanEncryptorUtils
+import org.matrix.android.sdk.internal.session.contentscanner.model.toJson
import org.matrix.android.sdk.internal.util.ensureTrailingSlash
import javax.inject.Inject
-internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver {
+internal class DefaultContentUrlResolver @Inject constructor(
+ homeServerConnectionConfig: HomeServerConnectionConfig,
+ private val scannerService: ContentScannerService
+) : ContentUrlResolver {
private val baseUrl = homeServerConnectionConfig.homeServerUriBase.toString().ensureTrailingSlash()
override val uploadUrl = baseUrl + NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "upload"
+ override fun resolveForDownload(contentUrl: String?, elementToDecrypt: ElementToDecrypt?): ContentUrlResolver.ResolvedMethod? {
+ return if (scannerService.isScannerEnabled() && elementToDecrypt != null) {
+ val baseUrl = scannerService.getContentScannerServer()
+ val sep = if (baseUrl?.endsWith("/") == true) "" else "/"
+
+ val url = baseUrl + sep + NetworkConstants.URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE + "download_encrypted"
+
+ ContentUrlResolver.ResolvedMethod.POST(
+ url = url,
+ jsonBody = ScanEncryptorUtils
+ .getDownloadBodyAndEncryptIfNeeded(scannerService.serverPublicKey, contentUrl ?: "", elementToDecrypt)
+ .toJson()
+ )
+ } else {
+ resolveFullSize(contentUrl)?.let { ContentUrlResolver.ResolvedMethod.GET(it) }
+ }
+ }
+
override fun resolveFullSize(contentUrl: String?): String? {
return contentUrl
// do not allow non-mxc content URLs
@@ -37,7 +62,7 @@ internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectio
?.let {
resolve(
contentUrl = it,
- prefix = NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "download/"
+ toThumbnail = false
)
}
}
@@ -49,16 +74,27 @@ internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectio
?.let {
resolve(
contentUrl = it,
- prefix = NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "thumbnail/",
+ toThumbnail = true,
params = "?width=$width&height=$height&method=${method.value}"
)
}
}
private fun resolve(contentUrl: String,
- prefix: String,
- params: String = ""): String? {
- var serverAndMediaId = contentUrl.removePrefix(MatrixUrls.MATRIX_CONTENT_URI_SCHEME)
+ toThumbnail: Boolean,
+ params: String = ""): String {
+ var serverAndMediaId = contentUrl.removeMxcPrefix()
+
+ val apiPath = if (scannerService.isScannerEnabled()) {
+ NetworkConstants.URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE
+ } else {
+ NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0
+ }
+ val prefix = if (toThumbnail) {
+ apiPath + "thumbnail/"
+ } else {
+ apiPath + "download/"
+ }
val fragmentOffset = serverAndMediaId.indexOf("#")
var fragment = ""
if (fragmentOffset >= 0) {
@@ -66,6 +102,11 @@ internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectio
serverAndMediaId = serverAndMediaId.substring(0, fragmentOffset)
}
- return baseUrl + prefix + serverAndMediaId + params + fragment
+ val resolvedUrl = if (scannerService.isScannerEnabled()) {
+ scannerService.getContentScannerServer()!!.ensureTrailingSlash()
+ } else {
+ baseUrl
+ }
+ return resolvedUrl + prefix + serverAndMediaId + params + fragment
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
index b657d950bd..7f35c91010 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
@@ -279,6 +279,11 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
Timber.e(failure, "## Failed to update file cache")
}
+ // Delete the temporary voice message file
+ if (params.attachment.type == ContentAttachmentData.Type.VOICE_MESSAGE) {
+ context.contentResolver.delete(params.attachment.queryUri, null, null)
+ }
+
val uploadThumbnailResult = dealWithThumbnail(params)
handleSuccess(params,
@@ -299,11 +304,6 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
filesToDelete.forEach {
tryOrNull { it.delete() }
}
-
- // Delete the temporary voice message file
- if (params.attachment.type == ContentAttachmentData.Type.AUDIO && params.attachment.mimeType == MimeTypes.Ogg) {
- context.contentResolver.delete(params.attachment.queryUri, null, null)
- }
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ContentScannerApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ContentScannerApi.kt
new file mode 100644
index 0000000000..46f1705806
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ContentScannerApi.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.contentscanner
+
+import okhttp3.ResponseBody
+import org.matrix.android.sdk.internal.network.NetworkConstants
+import org.matrix.android.sdk.internal.session.contentscanner.model.DownloadBody
+import org.matrix.android.sdk.internal.session.contentscanner.model.ScanResponse
+import org.matrix.android.sdk.internal.session.contentscanner.model.ServerPublicKeyResponse
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.POST
+import retrofit2.http.Path
+
+/**
+ * https://github.com/matrix-org/matrix-content-scanner
+ */
+internal interface ContentScannerApi {
+
+ @POST(NetworkConstants.URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE + "download_encrypted")
+ suspend fun downloadEncrypted(@Body info: DownloadBody): ResponseBody
+
+ @POST(NetworkConstants.URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE + "scan_encrypted")
+ suspend fun scanFile(@Body info: DownloadBody): ScanResponse
+
+ @GET(NetworkConstants.URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE + "public_key")
+ suspend fun getServerPublicKey(): ServerPublicKeyResponse
+
+ @GET(NetworkConstants.URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE + "scan/{domain}/{mediaId}")
+ suspend fun scanMedia(@Path(value = "domain") domain: String, @Path(value = "mediaId") mediaId: String): ScanResponse
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ContentScannerApiProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ContentScannerApiProvider.kt
new file mode 100644
index 0000000000..d8548bb238
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ContentScannerApiProvider.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.contentscanner
+
+import org.matrix.android.sdk.internal.session.SessionScope
+import javax.inject.Inject
+
+@SessionScope
+internal class ContentScannerApiProvider @Inject constructor() {
+ var contentScannerApi: ContentScannerApi? = null
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ContentScannerModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ContentScannerModule.kt
new file mode 100644
index 0000000000..7ea74225cd
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ContentScannerModule.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.contentscanner
+
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import io.realm.RealmConfiguration
+import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
+import org.matrix.android.sdk.internal.database.RealmKeysUtils
+import org.matrix.android.sdk.internal.di.ContentScannerDatabase
+import org.matrix.android.sdk.internal.di.SessionFilesDirectory
+import org.matrix.android.sdk.internal.di.UserMd5
+import org.matrix.android.sdk.internal.session.SessionModule
+import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.session.contentscanner.data.ContentScannerStore
+import org.matrix.android.sdk.internal.session.contentscanner.db.ContentScannerRealmModule
+import org.matrix.android.sdk.internal.session.contentscanner.db.RealmContentScannerStore
+import org.matrix.android.sdk.internal.session.contentscanner.tasks.DefaultDownloadEncryptedTask
+import org.matrix.android.sdk.internal.session.contentscanner.tasks.DefaultGetServerPublicKeyTask
+import org.matrix.android.sdk.internal.session.contentscanner.tasks.DefaultScanEncryptedTask
+import org.matrix.android.sdk.internal.session.contentscanner.tasks.DefaultScanMediaTask
+import org.matrix.android.sdk.internal.session.contentscanner.tasks.DownloadEncryptedTask
+import org.matrix.android.sdk.internal.session.contentscanner.tasks.GetServerPublicKeyTask
+import org.matrix.android.sdk.internal.session.contentscanner.tasks.ScanEncryptedTask
+import org.matrix.android.sdk.internal.session.contentscanner.tasks.ScanMediaTask
+import java.io.File
+
+@Module
+internal abstract class ContentScannerModule {
+ @Module
+ companion object {
+
+ @JvmStatic
+ @Provides
+ @ContentScannerDatabase
+ @SessionScope
+ fun providesContentScannerRealmConfiguration(realmKeysUtils: RealmKeysUtils,
+ @SessionFilesDirectory directory: File,
+ @UserMd5 userMd5: String): RealmConfiguration {
+ return RealmConfiguration.Builder()
+ .directory(directory)
+ .name("matrix-sdk-content-scanning.realm")
+ .apply {
+ realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
+ }
+ .allowWritesOnUiThread(true)
+ .modules(ContentScannerRealmModule())
+ .build()
+ }
+ }
+
+ @Binds
+ abstract fun bindContentScannerService(service: DisabledContentScannerService): ContentScannerService
+
+ @Binds
+ abstract fun bindContentScannerStore(store: RealmContentScannerStore): ContentScannerStore
+
+ @Binds
+ abstract fun bindDownloadEncryptedTask(task: DefaultDownloadEncryptedTask): DownloadEncryptedTask
+
+ @Binds
+ abstract fun bindGetServerPublicKeyTask(task: DefaultGetServerPublicKeyTask): GetServerPublicKeyTask
+
+ @Binds
+ abstract fun bindScanMediaTask(task: DefaultScanMediaTask): ScanMediaTask
+
+ @Binds
+ abstract fun bindScanEncryptedTask(task: DefaultScanEncryptedTask): ScanEncryptedTask
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt
new file mode 100644
index 0000000000..4ecb337603
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.contentscanner
+
+import androidx.lifecycle.LiveData
+import dagger.Lazy
+import kotlinx.coroutines.launch
+import okhttp3.OkHttpClient
+import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
+import org.matrix.android.sdk.api.session.contentscanner.ScanState
+import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo
+import org.matrix.android.sdk.api.util.Optional
+import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
+import org.matrix.android.sdk.internal.di.Unauthenticated
+import org.matrix.android.sdk.internal.network.RetrofitFactory
+import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.session.contentscanner.data.ContentScannerStore
+import org.matrix.android.sdk.internal.session.contentscanner.tasks.GetServerPublicKeyTask
+import org.matrix.android.sdk.internal.session.contentscanner.tasks.ScanEncryptedTask
+import org.matrix.android.sdk.internal.session.contentscanner.tasks.ScanMediaTask
+import org.matrix.android.sdk.internal.task.TaskExecutor
+import timber.log.Timber
+import javax.inject.Inject
+
+@SessionScope
+internal class DefaultContentScannerService @Inject constructor(
+ private val retrofitFactory: RetrofitFactory,
+ @Unauthenticated
+ private val okHttpClient: Lazy,
+ private val contentScannerApiProvider: ContentScannerApiProvider,
+ private val contentScannerStore: ContentScannerStore,
+ private val getServerPublicKeyTask: GetServerPublicKeyTask,
+ private val scanEncryptedTask: ScanEncryptedTask,
+ private val scanMediaTask: ScanMediaTask,
+ private val taskExecutor: TaskExecutor
+) : ContentScannerService {
+
+ // Cache public key in memory
+ override var serverPublicKey: String? = null
+ private set
+
+ override fun getContentScannerServer(): String? {
+ return contentScannerStore.getScannerUrl()
+ }
+
+ override suspend fun getServerPublicKey(forceDownload: Boolean): String? {
+ val api = contentScannerApiProvider.contentScannerApi ?: throw IllegalArgumentException("No content scanner define")
+
+ if (!forceDownload && serverPublicKey != null) {
+ return serverPublicKey
+ }
+
+ return getServerPublicKeyTask.execute(GetServerPublicKeyTask.Params(api)).also {
+ serverPublicKey = it
+ }
+ }
+
+ override suspend fun getScanResultForAttachment(mxcUrl: String, fileInfo: ElementToDecrypt?): ScanStatusInfo {
+ val result = if (fileInfo != null) {
+ scanEncryptedTask.execute(ScanEncryptedTask.Params(
+ mxcUrl = mxcUrl,
+ publicServerKey = getServerPublicKey(false),
+ encryptedInfo = fileInfo
+ ))
+ } else {
+ scanMediaTask.execute(ScanMediaTask.Params(mxcUrl))
+ }
+
+ return ScanStatusInfo(
+ state = if (result.clean) ScanState.TRUSTED else ScanState.INFECTED,
+ humanReadableMessage = result.info,
+ scanDateTimestamp = System.currentTimeMillis()
+ )
+ }
+
+ override fun setScannerUrl(url: String?) = contentScannerStore.setScannerUrl(url).also {
+ if (url == null) {
+ contentScannerApiProvider.contentScannerApi = null
+ serverPublicKey = null
+ } else {
+ val api = retrofitFactory
+ .create(okHttpClient, url)
+ .create(ContentScannerApi::class.java)
+ contentScannerApiProvider.contentScannerApi = api
+
+ taskExecutor.executorScope.launch {
+ try {
+ getServerPublicKey(true)
+ } catch (failure: Throwable) {
+ Timber.e("Failed to get public server api")
+ }
+ }
+ }
+ }
+
+ override fun enableScanner(enabled: Boolean) = contentScannerStore.enableScanner(enabled)
+
+ override fun isScannerEnabled(): Boolean = contentScannerStore.isScanEnabled()
+
+ override fun getCachedScanResultForFile(mxcUrl: String): ScanStatusInfo? {
+ return contentScannerStore.getScanResult(mxcUrl)
+ }
+
+ override fun getLiveStatusForFile(mxcUrl: String, fetchIfNeeded: Boolean, fileInfo: ElementToDecrypt?): LiveData> {
+ val data = contentScannerStore.getLiveScanResult(mxcUrl)
+ if (fetchIfNeeded && !contentScannerStore.isScanResultKnownOrInProgress(mxcUrl, getContentScannerServer())) {
+ taskExecutor.executorScope.launch {
+ try {
+ getScanResultForAttachment(mxcUrl, fileInfo)
+ } catch (failure: Throwable) {
+ Timber.e("Failed to get file status : ${failure.localizedMessage}")
+ }
+ }
+ }
+ return data
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DisabledContentScannerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DisabledContentScannerService.kt
new file mode 100644
index 0000000000..9087c71566
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DisabledContentScannerService.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.contentscanner
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
+import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo
+import org.matrix.android.sdk.api.util.Optional
+import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
+import org.matrix.android.sdk.internal.session.SessionScope
+import javax.inject.Inject
+
+/**
+ * Created to by-pass ProfileTask execution in LoginWizard.
+ */
+@SessionScope
+internal class DisabledContentScannerService @Inject constructor() : ContentScannerService {
+
+ override val serverPublicKey: String?
+ get() = null
+
+ override fun getContentScannerServer(): String? {
+ return null
+ }
+
+ override suspend fun getServerPublicKey(forceDownload: Boolean): String? {
+ return null
+ }
+
+ override suspend fun getScanResultForAttachment(mxcUrl: String, fileInfo: ElementToDecrypt?): ScanStatusInfo {
+ TODO("Not yet implemented")
+ }
+
+ override fun setScannerUrl(url: String?) {
+ }
+
+ override fun enableScanner(enabled: Boolean) {
+ }
+
+ override fun isScannerEnabled(): Boolean {
+ return false
+ }
+
+ override fun getLiveStatusForFile(mxcUrl: String, fetchIfNeeded: Boolean, fileInfo: ElementToDecrypt?): LiveData> {
+ return MutableLiveData()
+ }
+
+ override fun getCachedScanResultForFile(mxcUrl: String): ScanStatusInfo? {
+ return null
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ScanEncryptorUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ScanEncryptorUtils.kt
new file mode 100644
index 0000000000..8fc84a487e
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ScanEncryptorUtils.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.contentscanner
+
+import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
+import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
+import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileKey
+import org.matrix.android.sdk.internal.crypto.tools.withOlmEncryption
+import org.matrix.android.sdk.internal.session.contentscanner.model.DownloadBody
+import org.matrix.android.sdk.internal.session.contentscanner.model.EncryptedBody
+import org.matrix.android.sdk.internal.session.contentscanner.model.toCanonicalJson
+
+internal object ScanEncryptorUtils {
+
+ fun getDownloadBodyAndEncryptIfNeeded(publicServerKey: String?, mxcUrl: String, elementToDecrypt: ElementToDecrypt): DownloadBody {
+ // TODO, upstream refactoring changed the object model here...
+ // it's bad we have to recreate and use hardcoded values
+ val encryptedInfo = EncryptedFileInfo(
+ url = mxcUrl,
+ iv = elementToDecrypt.iv,
+ hashes = mapOf("sha256" to elementToDecrypt.sha256),
+ key = EncryptedFileKey(
+ k = elementToDecrypt.k,
+ alg = "A256CTR",
+ keyOps = listOf("encrypt", "decrypt"),
+ kty = "oct",
+ ext = true
+ ),
+ v = "v2"
+ )
+ return if (publicServerKey != null) {
+ // We should encrypt
+ withOlmEncryption { olm ->
+ olm.setRecipientKey(publicServerKey)
+
+ val olmResult = olm.encrypt(DownloadBody(encryptedInfo).toCanonicalJson())
+ DownloadBody(
+ encryptedBody = EncryptedBody(
+ cipherText = olmResult.mCipherText,
+ ephemeral = olmResult.mEphemeralKey,
+ mac = olmResult.mMac
+ )
+ )
+ }
+ } else {
+ DownloadBody(encryptedInfo)
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/data/ContentScannerStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/data/ContentScannerStore.kt
new file mode 100644
index 0000000000..5cfe851a5c
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/data/ContentScannerStore.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.contentscanner.data
+
+import androidx.lifecycle.LiveData
+import org.matrix.android.sdk.api.session.contentscanner.ScanState
+import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo
+import org.matrix.android.sdk.api.util.Optional
+
+internal interface ContentScannerStore {
+
+ fun getScannerUrl(): String?
+
+ fun setScannerUrl(url: String?)
+
+ fun enableScanner(enabled: Boolean)
+
+ fun isScanEnabled(): Boolean
+
+ fun getScanResult(mxcUrl: String): ScanStatusInfo?
+ fun getLiveScanResult(mxcUrl: String): LiveData>
+ fun isScanResultKnownOrInProgress(mxcUrl: String, scannerUrl: String?): Boolean
+
+ fun updateStateForContent(mxcUrl: String, state: ScanState, scannerUrl: String?)
+ fun updateScanResultForContent(mxcUrl: String, scannerUrl: String?, state: ScanState, humanReadable: String)
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScanResultEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScanResultEntity.kt
new file mode 100644
index 0000000000..0ffff441f8
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScanResultEntity.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.contentscanner.db
+
+import io.realm.RealmObject
+import io.realm.annotations.Index
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.session.contentscanner.ScanState
+import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo
+
+internal open class ContentScanResultEntity(
+ @Index
+ var mediaUrl: String? = null,
+ var scanStatusString: String? = null,
+ var humanReadableMessage: String? = null,
+ var scanDateTimestamp: Long? = null,
+ var scannerUrl: String? = null
+) : RealmObject() {
+
+ var scanResult: ScanState
+ get() {
+ return scanStatusString
+ ?.let {
+ tryOrNull { ScanState.valueOf(it) }
+ }
+ ?: ScanState.UNKNOWN
+ }
+ set(result) {
+ scanStatusString = result.name
+ }
+
+ fun toModel(): ScanStatusInfo {
+ return ScanStatusInfo(
+ state = this.scanResult,
+ humanReadableMessage = humanReadableMessage,
+ scanDateTimestamp = scanDateTimestamp
+ )
+ }
+
+ companion object
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerEntityQueries.kt
new file mode 100644
index 0000000000..b47be235c6
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerEntityQueries.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.contentscanner.db
+
+import io.realm.Realm
+import io.realm.kotlin.createObject
+import io.realm.kotlin.where
+
+internal fun ContentScanResultEntity.Companion.get(realm: Realm, attachmentUrl: String, contentScannerUrl: String?): ContentScanResultEntity? {
+ return realm.where()
+ .equalTo(ContentScanResultEntityFields.MEDIA_URL, attachmentUrl)
+ .apply {
+ contentScannerUrl?.let {
+ equalTo(ContentScanResultEntityFields.SCANNER_URL, it)
+ }
+ }
+ .findFirst()
+}
+
+internal fun ContentScanResultEntity.Companion.getOrCreate(realm: Realm, attachmentUrl: String, contentScannerUrl: String?): ContentScanResultEntity {
+ return ContentScanResultEntity.get(realm, attachmentUrl, contentScannerUrl)
+ ?: realm.createObject().also {
+ it.mediaUrl = attachmentUrl
+ it.scanDateTimestamp = System.currentTimeMillis()
+ it.scannerUrl = contentScannerUrl
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerInfoEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerInfoEntity.kt
new file mode 100644
index 0000000000..d1910de36a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerInfoEntity.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.contentscanner.db
+
+import io.realm.RealmObject
+
+internal open class ContentScannerInfoEntity(
+ var serverUrl: String? = null,
+ var enabled: Boolean? = null
+) : RealmObject() {
+
+ companion object
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerRealmModule.kt
new file mode 100644
index 0000000000..bb53140ad9
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerRealmModule.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.contentscanner.db
+
+import io.realm.annotations.RealmModule
+
+/**
+ * Realm module for content scanner classes
+ */
+@RealmModule(library = true,
+ classes = [
+ ContentScannerInfoEntity::class,
+ ContentScanResultEntity::class
+ ])
+internal class ContentScannerRealmModule
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/RealmContentScannerStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/RealmContentScannerStore.kt
new file mode 100644
index 0000000000..947a66c8b9
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/RealmContentScannerStore.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.contentscanner.db
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Transformations
+import com.zhuinden.monarchy.Monarchy
+import io.realm.Realm
+import io.realm.RealmConfiguration
+import io.realm.kotlin.createObject
+import io.realm.kotlin.where
+import org.matrix.android.sdk.api.extensions.orFalse
+import org.matrix.android.sdk.api.session.contentscanner.ScanState
+import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo
+import org.matrix.android.sdk.api.util.Optional
+import org.matrix.android.sdk.api.util.toOptional
+import org.matrix.android.sdk.internal.di.ContentScannerDatabase
+import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.session.contentscanner.data.ContentScannerStore
+import org.matrix.android.sdk.internal.util.isValidUrl
+import javax.inject.Inject
+
+@SessionScope
+internal class RealmContentScannerStore @Inject constructor(
+ @ContentScannerDatabase
+ private val realmConfiguration: RealmConfiguration
+) : ContentScannerStore {
+
+ private val monarchy = Monarchy.Builder()
+ .setRealmConfiguration(realmConfiguration)
+ .build()
+
+ override fun getScannerUrl(): String? {
+ return monarchy.fetchAllMappedSync(
+ { realm ->
+ realm.where()
+ }, {
+ it.serverUrl
+ }
+ ).firstOrNull()
+ }
+
+ override fun setScannerUrl(url: String?) {
+ monarchy.runTransactionSync { realm ->
+ val info = realm.where().findFirst()
+ ?: realm.createObject()
+ info.serverUrl = url
+ }
+ }
+
+ override fun enableScanner(enabled: Boolean) {
+ monarchy.runTransactionSync { realm ->
+ val info = realm.where().findFirst()
+ ?: realm.createObject()
+ info.enabled = enabled
+ }
+ }
+
+ override fun isScanEnabled(): Boolean {
+ return monarchy.fetchAllMappedSync(
+ { realm ->
+ realm.where()
+ }, {
+ it.enabled.orFalse() && it.serverUrl?.isValidUrl().orFalse()
+ }
+ ).firstOrNull().orFalse()
+ }
+
+ override fun updateStateForContent(mxcUrl: String, state: ScanState, scannerUrl: String?) {
+ monarchy.runTransactionSync {
+ ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl).scanResult = state
+ }
+ }
+
+ override fun updateScanResultForContent(mxcUrl: String, scannerUrl: String?, state: ScanState, humanReadable: String) {
+ monarchy.runTransactionSync {
+ ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl).apply {
+ scanResult = state
+ scanDateTimestamp = System.currentTimeMillis()
+ humanReadableMessage = humanReadable
+ }
+ }
+ }
+
+ override fun isScanResultKnownOrInProgress(mxcUrl: String, scannerUrl: String?): Boolean {
+ var isKnown = false
+ monarchy.runTransactionSync {
+ val info = ContentScanResultEntity.get(it, mxcUrl, scannerUrl)?.scanResult
+ isKnown = when (info) {
+ ScanState.IN_PROGRESS,
+ ScanState.TRUSTED,
+ ScanState.INFECTED -> true
+ else -> false
+ }
+ }
+ return isKnown
+ }
+
+ override fun getScanResult(mxcUrl: String): ScanStatusInfo? {
+ return monarchy.fetchAllMappedSync({ realm ->
+ realm.where()
+ .equalTo(ContentScanResultEntityFields.MEDIA_URL, mxcUrl)
+ .apply {
+ getScannerUrl()?.let {
+ equalTo(ContentScanResultEntityFields.SCANNER_URL, it)
+ }
+ }
+ }, {
+ it.toModel()
+ })
+ .firstOrNull()
+ }
+
+ override fun getLiveScanResult(mxcUrl: String): LiveData> {
+ val liveData = monarchy.findAllMappedWithChanges(
+ { realm: Realm ->
+ realm.where()
+ .equalTo(ContentScanResultEntityFields.MEDIA_URL, mxcUrl)
+ .equalTo(ContentScanResultEntityFields.SCANNER_URL, getScannerUrl())
+ },
+ { entity ->
+ entity.toModel()
+ }
+ )
+ return Transformations.map(liveData) {
+ it.firstOrNull().toOptional()
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/model/DownloadBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/model/DownloadBody.kt
new file mode 100644
index 0000000000..5bac96a0c0
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/model/DownloadBody.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.contentscanner.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
+import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.util.JsonCanonicalizer
+
+@JsonClass(generateAdapter = true)
+internal data class DownloadBody(
+ @Json(name = "file") val file: EncryptedFileInfo? = null,
+ @Json(name = "encrypted_body") val encryptedBody: EncryptedBody? = null
+)
+
+@JsonClass(generateAdapter = true)
+internal data class EncryptedBody(
+ @Json(name = "ciphertext") val cipherText: String,
+ @Json(name = "mac") val mac: String,
+ @Json(name = "ephemeral") val ephemeral: String
+)
+
+internal fun DownloadBody.toJson(): String = MoshiProvider.providesMoshi().adapter(DownloadBody::class.java).toJson(this)
+
+internal fun DownloadBody.toCanonicalJson() = JsonCanonicalizer.getCanonicalJson(DownloadBody::class.java, this)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/model/ScanResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/model/ScanResponse.kt
new file mode 100644
index 0000000000..f783fe0a6c
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/model/ScanResponse.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.contentscanner.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+/**
+ * {
+ * "clean": true,
+ * "info": "File clean at 6/7/2018, 6:02:40 PM"
+ * }
+ */
+@JsonClass(generateAdapter = true)
+internal data class ScanResponse(
+ @Json(name = "clean") val clean: Boolean,
+ /** Human-readable information about the result. */
+ @Json(name = "info") val info: String?
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/model/ServerPublicKeyResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/model/ServerPublicKeyResponse.kt
new file mode 100644
index 0000000000..2e97a85bca
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/model/ServerPublicKeyResponse.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.contentscanner.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class ServerPublicKeyResponse(
+ @Json(name = "public_key")
+ val publicKey: String?
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/DownloadEncryptedTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/DownloadEncryptedTask.kt
new file mode 100644
index 0000000000..f92c869cb8
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/DownloadEncryptedTask.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.contentscanner.tasks
+
+import okhttp3.ResponseBody
+import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.session.contentscanner.ContentScannerApiProvider
+import org.matrix.android.sdk.internal.session.contentscanner.ScanEncryptorUtils
+import org.matrix.android.sdk.internal.task.Task
+import javax.inject.Inject
+
+internal interface DownloadEncryptedTask : Task {
+ data class Params(
+ val publicServerKey: String?,
+ val encryptedInfo: ElementToDecrypt,
+ val mxcUrl: String
+ )
+}
+
+internal class DefaultDownloadEncryptedTask @Inject constructor(
+ private val contentScannerApiProvider: ContentScannerApiProvider
+) : DownloadEncryptedTask {
+
+ override suspend fun execute(params: DownloadEncryptedTask.Params): ResponseBody {
+ val dlBody = ScanEncryptorUtils.getDownloadBodyAndEncryptIfNeeded(
+ params.publicServerKey,
+ params.mxcUrl,
+ params.encryptedInfo
+ )
+
+ val api = contentScannerApiProvider.contentScannerApi ?: throw IllegalArgumentException()
+ return executeRequest(null) {
+ api.downloadEncrypted(dlBody)
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/GetServerPublicKeyTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/GetServerPublicKeyTask.kt
new file mode 100644
index 0000000000..41c2ec9c38
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/GetServerPublicKeyTask.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.contentscanner.tasks
+
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.session.contentscanner.ContentScannerApi
+import org.matrix.android.sdk.internal.session.contentscanner.model.ServerPublicKeyResponse
+import org.matrix.android.sdk.internal.task.Task
+import javax.inject.Inject
+
+internal interface GetServerPublicKeyTask : Task {
+ data class Params(
+ val contentScannerApi: ContentScannerApi
+ )
+}
+
+internal class DefaultGetServerPublicKeyTask @Inject constructor() : GetServerPublicKeyTask {
+
+ override suspend fun execute(params: GetServerPublicKeyTask.Params): String? {
+ return executeRequest(null) {
+ params.contentScannerApi.getServerPublicKey()
+ }.publicKey
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/ScanEncryptedTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/ScanEncryptedTask.kt
new file mode 100644
index 0000000000..dab9b5538f
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/ScanEncryptedTask.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.contentscanner.tasks
+
+import org.matrix.android.sdk.api.failure.toScanFailure
+import org.matrix.android.sdk.api.session.contentscanner.ScanState
+import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.session.contentscanner.ContentScannerApiProvider
+import org.matrix.android.sdk.internal.session.contentscanner.ScanEncryptorUtils
+import org.matrix.android.sdk.internal.session.contentscanner.data.ContentScannerStore
+import org.matrix.android.sdk.internal.session.contentscanner.model.ScanResponse
+import org.matrix.android.sdk.internal.task.Task
+import javax.inject.Inject
+
+internal interface ScanEncryptedTask : Task {
+ data class Params(
+ val mxcUrl: String,
+ val publicServerKey: String?,
+ val encryptedInfo: ElementToDecrypt
+ )
+}
+
+internal class DefaultScanEncryptedTask @Inject constructor(
+ private val contentScannerApiProvider: ContentScannerApiProvider,
+ private val contentScannerStore: ContentScannerStore
+) : ScanEncryptedTask {
+
+ override suspend fun execute(params: ScanEncryptedTask.Params): ScanResponse {
+ val mxcUrl = params.mxcUrl
+ val dlBody = ScanEncryptorUtils.getDownloadBodyAndEncryptIfNeeded(params.publicServerKey, params.mxcUrl, params.encryptedInfo)
+
+ val scannerUrl = contentScannerStore.getScannerUrl()
+ contentScannerStore.updateStateForContent(params.mxcUrl, ScanState.IN_PROGRESS, scannerUrl)
+
+ try {
+ val api = contentScannerApiProvider.contentScannerApi ?: throw IllegalArgumentException()
+ val executeRequest = executeRequest(null) {
+ api.scanFile(dlBody)
+ }
+ contentScannerStore.updateScanResultForContent(
+ mxcUrl,
+ scannerUrl,
+ ScanState.TRUSTED.takeIf { executeRequest.clean } ?: ScanState.INFECTED,
+ executeRequest.info ?: ""
+ )
+ return executeRequest
+ } catch (failure: Throwable) {
+ contentScannerStore.updateStateForContent(params.mxcUrl, ScanState.UNKNOWN, scannerUrl)
+ throw failure.toScanFailure() ?: failure
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/ScanMediaTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/ScanMediaTask.kt
new file mode 100644
index 0000000000..505eb7098c
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/ScanMediaTask.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.contentscanner.tasks
+
+import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
+import org.matrix.android.sdk.api.MatrixUrls.removeMxcPrefix
+import org.matrix.android.sdk.api.failure.toScanFailure
+import org.matrix.android.sdk.api.session.contentscanner.ScanState
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.session.contentscanner.ContentScannerApiProvider
+import org.matrix.android.sdk.internal.session.contentscanner.data.ContentScannerStore
+import org.matrix.android.sdk.internal.session.contentscanner.model.ScanResponse
+import org.matrix.android.sdk.internal.task.Task
+import javax.inject.Inject
+
+internal interface ScanMediaTask : Task {
+ data class Params(
+ val mxcUrl: String
+ )
+}
+
+internal class DefaultScanMediaTask @Inject constructor(
+ private val contentScannerApiProvider: ContentScannerApiProvider,
+ private val contentScannerStore: ContentScannerStore
+) : ScanMediaTask {
+
+ override suspend fun execute(params: ScanMediaTask.Params): ScanResponse {
+ // "mxc://server.org/QNDpzLopkoQYNikJfoZCQuCXJ"
+ if (!params.mxcUrl.isMxcUrl()) {
+ throw IllegalAccessException("Invalid mxc url")
+ }
+ val scannerUrl = contentScannerStore.getScannerUrl()
+ contentScannerStore.updateStateForContent(params.mxcUrl, ScanState.IN_PROGRESS, scannerUrl)
+
+ var serverAndMediaId = params.mxcUrl.removeMxcPrefix()
+ val fragmentOffset = serverAndMediaId.indexOf("#")
+ if (fragmentOffset >= 0) {
+ serverAndMediaId = serverAndMediaId.substring(0, fragmentOffset)
+ }
+
+ val split = serverAndMediaId.split("/")
+ if (split.size != 2) {
+ throw IllegalAccessException("Invalid mxc url")
+ }
+
+ try {
+ val scanResponse = executeRequest(null) {
+ val api = contentScannerApiProvider.contentScannerApi ?: throw IllegalArgumentException()
+ api.scanMedia(split[0], split[1])
+ }
+ contentScannerStore.updateScanResultForContent(
+ params.mxcUrl,
+ scannerUrl,
+ ScanState.TRUSTED.takeIf { scanResponse.clean } ?: ScanState.INFECTED,
+ scanResponse.info ?: ""
+ )
+ return scanResponse
+ } catch (failure: Throwable) {
+ contentScannerStore.updateStateForContent(params.mxcUrl, ScanState.UNKNOWN, scannerUrl)
+ throw failure.toScanFailure() ?: failure
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt
index 9d80f27e01..51d305f441 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt
@@ -29,7 +29,6 @@ internal class DefaultEventService @Inject constructor(
override suspend fun getEvent(roomId: String, eventId: String): Event {
val event = getEventTask.execute(GetEventTask.Params(roomId, eventId))
- event.ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
// Fast lane to the call event processors: try to make the incoming call ring faster
if (callEventProcessor.shouldProcessFastLane(event.getClearType())) {
callEventProcessor.processFastLane(event)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
index 5cb9687518..a31d0cdec3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
@@ -205,10 +205,11 @@ internal class LocalEchoEventFactory @Inject constructor(
fun createMediaEvent(roomId: String, attachment: ContentAttachmentData): Event {
return when (attachment.type) {
- ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment)
- ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment)
- ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment)
- ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment)
+ ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment)
+ ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment)
+ ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment, isVoiceMessage = false)
+ ContentAttachmentData.Type.VOICE_MESSAGE -> createAudioEvent(roomId, attachment, isVoiceMessage = true)
+ ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment)
}
}
@@ -296,8 +297,7 @@ internal class LocalEchoEventFactory @Inject constructor(
return createMessageEvent(roomId, content)
}
- private fun createAudioEvent(roomId: String, attachment: ContentAttachmentData): Event {
- val isVoiceMessage = attachment.waveform != null
+ private fun createAudioEvent(roomId: String, attachment: ContentAttachmentData, isVoiceMessage: Boolean): Event {
val content = MessageAudioContent(
msgType = MessageType.MSGTYPE_AUDIO,
body = attachment.name ?: "audio",
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
index 82684f8c7e..4332255a8b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.timeline
import io.realm.Realm
import io.realm.RealmConfiguration
+import io.realm.RealmResults
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
@@ -25,14 +26,17 @@ import kotlinx.coroutines.android.asCoroutineDispatcher
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import okhttp3.internal.closeQuietly
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
+import org.matrix.android.sdk.internal.database.mapper.EventMapper
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler
+import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer
import org.matrix.android.sdk.internal.util.createBackgroundHandler
import timber.log.Timber
@@ -52,6 +56,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String,
fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
timelineEventMapper: TimelineEventMapper,
timelineInput: TimelineInput,
+ private val threadsAwarenessHandler: ThreadsAwarenessHandler,
eventDecryptor: TimelineEventDecryptor) : Timeline {
companion object {
@@ -79,6 +84,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String,
timelineEventMapper = timelineEventMapper,
realm = backgroundRealm,
getContextOfEventTask = getEventTask,
+ threadsAwarenessHandler = threadsAwarenessHandler,
onEventsUpdated = this::postSnapshot,
onNewTimelineEvents = this::onNewTimelineEvents
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
index 31a6e627ec..ab3f574130 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
@@ -38,6 +38,7 @@ import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler
+import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
import org.matrix.android.sdk.internal.task.TaskExecutor
internal class DefaultTimelineService @AssistedInject constructor(
@@ -52,6 +53,7 @@ internal class DefaultTimelineService @AssistedInject constructor(
private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
private val timelineEventMapper: TimelineEventMapper,
private val loadRoomMembersTask: LoadRoomMembersTask,
+ private val threadsAwarenessHandler: ThreadsAwarenessHandler,
private val readReceiptHandler: ReadReceiptHandler
) : TimelineService {
@@ -73,7 +75,8 @@ internal class DefaultTimelineService @AssistedInject constructor(
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
loadRoomMembersTask = loadRoomMembersTask,
readReceiptHandler = readReceiptHandler,
- getEventTask = contextOfEventTask
+ getEventTask = contextOfEventTask,
+ threadsAwarenessHandler = threadsAwarenessHandler
)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
index cbbc54e90d..9ede2f6562 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
@@ -59,6 +59,8 @@ internal class DefaultGetEventTask @Inject constructor(
}
}
+ event.ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
+
return event
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
index 72613e5c6b..d258dabad9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
@@ -30,6 +30,7 @@ import org.matrix.android.sdk.internal.database.model.ChunkEntity
import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
import org.matrix.android.sdk.internal.database.query.findAllIncludingEvents
import org.matrix.android.sdk.internal.database.query.where
+import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
import java.util.concurrent.atomic.AtomicReference
/**
@@ -68,6 +69,7 @@ internal class LoadTimelineStrategy(
val getContextOfEventTask: GetContextOfEventTask,
val timelineInput: TimelineInput,
val timelineEventMapper: TimelineEventMapper,
+ val threadsAwarenessHandler: ThreadsAwarenessHandler,
val onEventsUpdated: () -> Unit,
val onNewTimelineEvents: (List) -> Unit
)
@@ -210,6 +212,7 @@ internal class LoadTimelineStrategy(
fetchTokenAndPaginateTask = dependencies.fetchTokenAndPaginateTask,
timelineEventMapper = dependencies.timelineEventMapper,
uiEchoManager = uiEchoManager,
+ threadsAwarenessHandler = dependencies.threadsAwarenessHandler,
initialEventId = mode.originEventId(),
onBuiltEvents = dependencies.onEventsUpdated
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
index 7b45d4cdc5..447c4a3aa0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
@@ -22,16 +22,19 @@ import io.realm.RealmObjectChangeListener
import io.realm.RealmQuery
import io.realm.RealmResults
import io.realm.Sort
+import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
+import org.matrix.android.sdk.internal.database.mapper.EventMapper
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
import org.matrix.android.sdk.internal.database.model.ChunkEntity
import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
+import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
import timber.log.Timber
import java.util.Collections
import java.util.concurrent.atomic.AtomicBoolean
@@ -55,6 +58,7 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity,
private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
private val timelineEventMapper: TimelineEventMapper,
private val uiEchoManager: UIEchoManager? = null,
+ private val threadsAwarenessHandler: ThreadsAwarenessHandler,
private val initialEventId: String?,
private val onBuiltEvents: () -> Unit) {
@@ -223,11 +227,12 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity,
timelineEventEntities.removeChangeListener(timelineEventCollectionListener)
}
- private fun loadFromDb(count: Long, direction: Timeline.Direction): Long {
+ private suspend fun loadFromDb(count: Long, direction: Timeline.Direction): Long {
val displayIndex = getNextDisplayIndex(direction) ?: return 0
val baseQuery = timelineEventEntities.where()
val timelineEvents = baseQuery.offsets(direction, count, displayIndex).findAll().orEmpty()
if (timelineEvents.isEmpty()) return 0
+ fetchRootThreadEventsIfNeeded(timelineEvents)
if (direction == Timeline.Direction.FORWARDS) {
builtEventsIndexes.entries.forEach { it.setValue(it.value + timelineEvents.size) }
}
@@ -245,6 +250,21 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity,
return timelineEvents.size.toLong()
}
+ /**
+ * This function is responsible to fetch and store the root event of a thread event
+ * in order to be able to display the event to the user appropriately
+ */
+ private suspend fun fetchRootThreadEventsIfNeeded(offsetResults: List) {
+ val eventEntityList = offsetResults
+ .mapNotNull {
+ it.root
+ }.map {
+ EventMapper.map(it)
+ }
+ threadsAwarenessHandler.fetchRootThreadEventsIfNeeded(eventEntityList)
+ }
+
+
private fun TimelineEventEntity.buildAndDecryptIfNeeded(): TimelineEvent {
val timelineEvent = buildTimelineEvent(this)
val transactionId = timelineEvent.root.unsignedData?.transactionId
@@ -275,6 +295,7 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity,
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
timelineEventMapper = timelineEventMapper,
uiEchoManager = uiEchoManager,
+ threadsAwarenessHandler = threadsAwarenessHandler,
initialEventId = null,
onBuiltEvents = onBuiltEvents
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
index 721dae0b1b..75d02dfd98 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
@@ -26,6 +26,7 @@ import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
import org.matrix.android.sdk.internal.database.model.EventEntity
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
import timber.log.Timber
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
@@ -34,7 +35,8 @@ import javax.inject.Inject
internal class TimelineEventDecryptor @Inject constructor(
@SessionDatabase
private val realmConfiguration: RealmConfiguration,
- private val cryptoService: CryptoService
+ private val cryptoService: CryptoService,
+ private val threadsAwarenessHandler: ThreadsAwarenessHandler
) {
private val newSessionListener = object : NewSessionListener {
@@ -106,10 +108,19 @@ internal class TimelineEventDecryptor @Inject constructor(
val result = cryptoService.decryptEvent(request.event, timelineId)
Timber.v("Successfully decrypted event ${event.eventId}")
realm.executeTransaction {
- val eventId = event.eventId ?: ""
- EventEntity.where(it, eventId = eventId)
+ val eventId = event.eventId ?: return@executeTransaction
+ val eventEntity = EventEntity
+ .where(it, eventId = eventId)
.findFirst()
- ?.setDecryptionResult(result)
+
+ eventEntity?.apply {
+ val decryptedPayload = threadsAwarenessHandler.handleIfNeededDuringDecryption(
+ it,
+ roomId = event.roomId,
+ event,
+ result)
+ setDecryptionResult(result, decryptedPayload)
+ }
}
} catch (e: MXCryptoError) {
Timber.v("Failed to decrypt event ${event.eventId} : ${e.localizedMessage}")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
index 335f619623..f178074507 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
@@ -24,11 +24,13 @@ import org.matrix.android.sdk.api.session.initsync.InitSyncStep
import org.matrix.android.sdk.api.session.sync.model.GroupsSyncResponse
import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse
import org.matrix.android.sdk.api.session.sync.model.SyncResponse
+import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.SessionId
import org.matrix.android.sdk.internal.di.WorkManagerProvider
import org.matrix.android.sdk.internal.session.SessionListeners
+import org.matrix.android.sdk.internal.session.dispatchTo
import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker
import org.matrix.android.sdk.internal.session.initsync.ProgressReporter
import org.matrix.android.sdk.internal.session.initsync.reportSubtask
@@ -39,6 +41,7 @@ import org.matrix.android.sdk.internal.session.sync.handler.PresenceSyncHandler
import org.matrix.android.sdk.internal.session.sync.handler.SyncResponsePostTreatmentAggregatorHandler
import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler
import org.matrix.android.sdk.internal.session.sync.handler.room.RoomSyncHandler
+import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
import org.matrix.android.sdk.internal.util.awaitTransaction
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import timber.log.Timber
@@ -51,6 +54,7 @@ private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER"
internal class SyncResponseHandler @Inject constructor(
@SessionDatabase private val monarchy: Monarchy,
@SessionId private val sessionId: String,
+ private val sessionManager: SessionManager,
private val sessionListeners: SessionListeners,
private val workManagerProvider: WorkManagerProvider,
private val roomSyncHandler: RoomSyncHandler,
@@ -62,6 +66,7 @@ internal class SyncResponseHandler @Inject constructor(
private val tokenStore: SyncTokenStore,
private val processEventForPushTask: ProcessEventForPushTask,
private val pushRuleService: PushRuleService,
+ private val threadsAwarenessHandler: ThreadsAwarenessHandler,
private val presenceSyncHandler: PresenceSyncHandler
) {
@@ -94,6 +99,10 @@ internal class SyncResponseHandler @Inject constructor(
Timber.v("Finish handling toDevice in $it ms")
}
val aggregator = SyncResponsePostTreatmentAggregator()
+
+ // Prerequisite for thread events handling in RoomSyncHandler
+ threadsAwarenessHandler.fetchRootThreadEventsIfNeeded(syncResponse)
+
// Start one big transaction
monarchy.awaitTransaction { realm ->
measureTimeMillis {
@@ -158,8 +167,9 @@ internal class SyncResponseHandler @Inject constructor(
}
private fun dispatchInvitedRoom(roomsSyncResponse: RoomsSyncResponse) {
+ val session = sessionManager.getSessionComponent(sessionId)?.session()
roomsSyncResponse.invite.keys.forEach { roomId ->
- sessionListeners.dispatch { session, listener ->
+ session.dispatchTo(sessionListeners) { session, listener ->
listener.onNewInvitedRoom(session, roomId)
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
index c5ec34176c..f299d3effa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
@@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.session.sync.handler
+import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
@@ -32,6 +33,8 @@ import org.matrix.android.sdk.internal.session.initsync.ProgressReporter
import timber.log.Timber
import javax.inject.Inject
+private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO)
+
internal class CryptoSyncHandler @Inject constructor(private val cryptoService: DefaultCryptoService,
private val verificationService: DefaultVerificationService) {
@@ -40,11 +43,11 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService:
toDevice.events?.forEachIndexed { index, event ->
progressReporter?.reportProgress(index * 100F / total)
// Decrypt event if necessary
- Timber.i("## CRYPTO | To device event from ${event.senderId} of type:${event.type}")
+ Timber.tag(loggerTag.value).i("To device event from ${event.senderId} of type:${event.type}")
decryptToDeviceEvent(event, null)
if (event.getClearType() == EventType.MESSAGE &&
event.getClearContent()?.toModel()?.msgType == "m.bad.encrypted") {
- Timber.e("## CRYPTO | handleToDeviceEvent() : Warning: Unable to decrypt to-device event : ${event.content}")
+ Timber.tag(loggerTag.value).e("handleToDeviceEvent() : Warning: Unable to decrypt to-device event : ${event.content}")
} else {
verificationService.onToDeviceEvent(event)
cryptoService.onToDeviceEvent(event)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
index 4a3a3617ca..f090975cad 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
@@ -76,6 +76,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
private val cryptoService: DefaultCryptoService,
private val roomMemberEventHandler: RoomMemberEventHandler,
private val roomTypingUsersHandler: RoomTypingUsersHandler,
+ private val threadsAwarenessHandler: ThreadsAwarenessHandler,
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
@UserId private val userId: String,
private val timelineInput: TimelineInput) {
@@ -364,10 +365,17 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
}
eventIds.add(event.eventId)
- if (event.isEncrypted() && insertType != EventInsertType.INITIAL_SYNC) {
+ val isInitialSync = insertType == EventInsertType.INITIAL_SYNC
+
+ if (event.isEncrypted() && !isInitialSync) {
decryptIfNeeded(event, roomId)
}
+ threadsAwarenessHandler.handleIfNeeded(
+ realm = realm,
+ roomId = roomId,
+ event = event)
+
val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it }
val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType)
if (event.stateKey != null) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
new file mode 100644
index 0000000000..767a967522
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.sync.handler.room
+
+import com.zhuinden.monarchy.Monarchy
+import io.realm.Realm
+import org.matrix.android.sdk.api.session.crypto.CryptoService
+import org.matrix.android.sdk.api.session.crypto.MXCryptoError
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.events.model.RelationType
+import org.matrix.android.sdk.api.session.events.model.toContent
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
+import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
+import org.matrix.android.sdk.api.session.room.send.SendState
+import org.matrix.android.sdk.api.session.sync.model.SyncResponse
+import org.matrix.android.sdk.api.util.JsonDict
+import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
+import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
+import org.matrix.android.sdk.internal.database.mapper.EventMapper
+import org.matrix.android.sdk.internal.database.mapper.toEntity
+import org.matrix.android.sdk.internal.database.model.EventEntity
+import org.matrix.android.sdk.internal.database.model.EventInsertType
+import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
+import org.matrix.android.sdk.internal.database.query.where
+import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory
+import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
+import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask
+import org.matrix.android.sdk.internal.util.awaitTransaction
+import javax.inject.Inject
+
+/**
+ * This handler is responsible for a smooth threads migration. It will map all incoming
+ * threads as replies. So a device without threads enabled/updated will be able to view
+ * threads response as replies to the original message
+ */
+internal class ThreadsAwarenessHandler @Inject constructor(
+ private val permalinkFactory: PermalinkFactory,
+ private val cryptoService: CryptoService,
+ @SessionDatabase private val monarchy: Monarchy,
+ private val getEventTask: GetEventTask
+) {
+
+ /**
+ * Fetch root thread events if they are missing from the local storage
+ * @param syncResponse the sync response
+ */
+ suspend fun fetchRootThreadEventsIfNeeded(syncResponse: SyncResponse) {
+ val handlingStrategy = syncResponse.rooms?.join?.let {
+ RoomSyncHandler.HandlingStrategy.JOINED(it)
+ }
+ if (handlingStrategy !is RoomSyncHandler.HandlingStrategy.JOINED) return
+ val eventList = handlingStrategy.data
+ .mapNotNull { (roomId, roomSync) ->
+ roomSync.timeline?.events?.map {
+ it.copy(roomId = roomId)
+ }
+ }.flatten()
+
+ fetchRootThreadEventsIfNeeded(eventList)
+ }
+
+ /**
+ * Fetch root thread events if they are missing from the local storage
+ * @param eventList a list with the events to examine
+ */
+ suspend fun fetchRootThreadEventsIfNeeded(eventList: List) {
+ if (eventList.isNullOrEmpty()) return
+
+ val threadsToFetch = emptyMap().toMutableMap()
+ Realm.getInstance(monarchy.realmConfiguration).use { realm ->
+ eventList.asSequence()
+ .filter {
+ isThreadEvent(it) && it.roomId != null
+ }.mapNotNull { event ->
+ getRootThreadEventId(event)?.let {
+ Pair(it, event.roomId!!)
+ }
+ }.forEach { (rootThreadEventId, roomId) ->
+ EventEntity.where(realm, rootThreadEventId).findFirst() ?: run { threadsToFetch[rootThreadEventId] = roomId }
+ }
+ }
+ fetchThreadsEvents(threadsToFetch)
+ }
+
+ /**
+ * Fetch multiple unique events using the fetchEvent function
+ */
+ private suspend fun fetchThreadsEvents(threadsToFetch: Map) {
+ val eventEntityList = threadsToFetch.mapNotNull { (eventId, roomId) ->
+ fetchEvent(eventId, roomId)?.let {
+ it.toEntity(roomId, SendState.SYNCED, it.ageLocalTs)
+ }
+ }
+
+ if (eventEntityList.isNullOrEmpty()) return
+
+ // Transaction should be done on its own thread, like below
+ monarchy.awaitTransaction { realm ->
+ eventEntityList.forEach {
+ it.copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC)
+ }
+ }
+ }
+
+ /**
+ * This function will fetch the event from the homeserver, this is mandatory when the
+ * initial thread message is too old and is not saved in the device, so in order to
+ * construct the "reply to" format we need to know the event thread.
+ * @return the Event or null otherwise
+ */
+ private suspend fun fetchEvent(eventId: String, roomId: String): Event? {
+ return runCatching {
+ getEventTask.execute(GetEventTask.Params(roomId = roomId, eventId = eventId))
+ }.fold(
+ onSuccess = {
+ it
+ },
+ onFailure = {
+ null
+ })
+ }
+
+ /**
+ * Handle events mainly coming from the RoomSyncHandler
+ */
+ fun handleIfNeeded(realm: Realm,
+ roomId: String,
+ event: Event) {
+ val payload = transformThreadToReplyIfNeeded(
+ realm = realm,
+ roomId = roomId,
+ event = event,
+ decryptedResult = event.mxDecryptionResult?.payload) ?: return
+
+ event.mxDecryptionResult = event.mxDecryptionResult?.copy(payload = payload)
+ }
+
+ /**
+ * Handle events while they are being decrypted
+ */
+ fun handleIfNeededDuringDecryption(realm: Realm,
+ roomId: String?,
+ event: Event,
+ result: MXEventDecryptionResult): JsonDict? {
+ return transformThreadToReplyIfNeeded(
+ realm = realm,
+ roomId = roomId,
+ event = event,
+ decryptedResult = result.clearEvent)
+ }
+
+ /**
+ * If the event is a thread event then transform/enhance it to a visual Reply Event,
+ * If the event is not a thread event, null value will be returned
+ * If there is an error (ex. the root/origin thread event is not found), null willl be returend
+ */
+ private fun transformThreadToReplyIfNeeded(realm: Realm, roomId: String?, event: Event, decryptedResult: JsonDict?): JsonDict? {
+ roomId ?: return null
+ if (!isThreadEvent(event)) return null
+ val rootThreadEventId = getRootThreadEventId(event) ?: return null
+ val payload = decryptedResult?.toMutableMap() ?: return null
+ val body = getValueFromPayload(payload, "body") ?: return null
+ val msgType = getValueFromPayload(payload, "msgtype") ?: return null
+ val rootThreadEvent = getEventFromDB(realm, rootThreadEventId) ?: return null
+ val rootThreadEventSenderId = rootThreadEvent.senderId ?: return null
+
+ decryptIfNeeded(rootThreadEvent, roomId)
+
+ val rootThreadEventBody = getValueFromPayload(rootThreadEvent.mxDecryptionResult?.payload?.toMutableMap(), "body")
+
+ val permalink = permalinkFactory.createPermalink(roomId, rootThreadEventId, false)
+ val userLink = permalinkFactory.createPermalink(rootThreadEventSenderId, false) ?: ""
+
+ val replyFormatted = LocalEchoEventFactory.REPLY_PATTERN.format(
+ permalink,
+ userLink,
+ rootThreadEventSenderId,
+ // Remove inner mx_reply tags if any
+ rootThreadEventBody,
+ body)
+
+ val messageTextContent = MessageTextContent(
+ msgType = msgType,
+ format = MessageFormat.FORMAT_MATRIX_HTML,
+ body = body,
+ formattedBody = replyFormatted
+ ).toContent()
+
+ payload["content"] = messageTextContent
+
+ return payload
+ }
+
+ /**
+ * Decrypt the event
+ */
+
+ private fun decryptIfNeeded(event: Event, roomId: String) {
+ try {
+ if (!event.isEncrypted() || event.mxDecryptionResult != null) return
+
+ // Event from sync does not have roomId, so add it to the event first
+ val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
+ event.mxDecryptionResult = OlmDecryptionResult(
+ payload = result.clearEvent,
+ senderKey = result.senderCurve25519Key,
+ keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
+ forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
+ )
+ } catch (e: MXCryptoError) {
+ if (e is MXCryptoError.Base) {
+ event.mCryptoError = e.errorType
+ event.mCryptoErrorReason = e.technicalMessage.takeIf { it.isNotEmpty() } ?: e.detailedErrorDescription
+ }
+ }
+ }
+
+ /**
+ * Try to get the event form the local DB, if the event does not exist null
+ * will be returned
+ */
+ private fun getEventFromDB(realm: Realm, eventId: String): Event? {
+ val eventEntity = EventEntity.where(realm, eventId = eventId).findFirst() ?: return null
+ return EventMapper.map(eventEntity)
+ }
+
+ /**
+ * Returns True if the event is a thread
+ * @param event
+ */
+ private fun isThreadEvent(event: Event): Boolean =
+ event.content.toModel()?.relatesTo?.type == RelationType.THREAD
+
+ /**
+ * Returns the root thread eventId or null otherwise
+ * @param event
+ */
+ private fun getRootThreadEventId(event: Event): String? =
+ event.content.toModel()?.relatesTo?.eventId
+
+ @Suppress("UNCHECKED_CAST")
+ private fun getValueFromPayload(payload: JsonDict?, key: String): String? {
+ val content = payload?.get("content") as? JsonDict
+ return content?.get(key) as? String
+ }
+}
diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt
index 29077c3a76..b135954f63 100644
--- a/tools/check/forbidden_strings_in_code.txt
+++ b/tools/check/forbidden_strings_in_code.txt
@@ -160,7 +160,7 @@ Formatter\.formatShortFileSize===1
# android\.text\.TextUtils
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
-enum class===107
+enum class===108
### Do not import temporary legacy classes
import org.matrix.android.sdk.internal.legacy.riot===3
diff --git a/tools/jitsi/build_jisti_libs.sh b/tools/jitsi/build_jisti_libs.sh
index 34051d463c..e352575775 100755
--- a/tools/jitsi/build_jisti_libs.sh
+++ b/tools/jitsi/build_jisti_libs.sh
@@ -25,8 +25,8 @@ cd jitsi-meet
# This is commit after version 2.2.2, which does not compile
# git checkout 5a934c071a5cbe64de275a25d0ed62d8193cdd03
-# Version android-sdk-3.1.0, commit 7a64bf006ea027b77564d8847570e1ac46ff0ec0
-git checkout android-sdk-3.1.0
+# Version android-sdk-3.10.0, commit 99e56e229dfa3c490096e37c3e5b76d2a3f23e32
+git checkout android-sdk-3.10.0
echo
echo "##################################################"
diff --git a/vector/build.gradle b/vector/build.gradle
index 655cff2e73..3c4619fc19 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -15,7 +15,7 @@ kapt {
// Note: 2 digits max for each value
ext.versionMajor = 1
ext.versionMinor = 3
-ext.versionPatch = 8
+ext.versionPatch = 9
static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct'
@@ -373,7 +373,7 @@ dependencies {
implementation 'com.facebook.stetho:stetho:1.6.0'
// Phone number https://github.com/google/libphonenumber
- implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.37'
+ implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.38'
// FlowBinding
implementation libs.github.flowBinding
@@ -470,10 +470,10 @@ dependencies {
// WebRTC
// org.webrtc:google-webrtc is for development purposes only
// implementation 'org.webrtc:google-webrtc:1.0.+'
- implementation('com.facebook.react:react-native-webrtc:1.87.3-jitsi-6624067@aar')
+ implementation('com.facebook.react:react-native-webrtc:1.92.1-jitsi-9093212@aar')
// Jitsi
- implementation('org.jitsi.react:jitsi-meet-sdk:3.1.0') {
+ implementation('org.jitsi.react:jitsi-meet-sdk:3.10.0') {
exclude group: 'com.google.firebase'
exclude group: 'com.google.android.gms'
exclude group: 'com.android.installreferrer'
diff --git a/vector/lint.xml b/vector/lint.xml
index dde29af62e..9d9b208df7 100644
--- a/vector/lint.xml
+++ b/vector/lint.xml
@@ -68,6 +68,7 @@
+
@@ -83,4 +84,7 @@
+
+
+
diff --git a/vector/src/androidTest/java/im/vector/app/EspressoExt.kt b/vector/src/androidTest/java/im/vector/app/EspressoExt.kt
index fbcb9b8cb3..59982c72aa 100644
--- a/vector/src/androidTest/java/im/vector/app/EspressoExt.kt
+++ b/vector/src/androidTest/java/im/vector/app/EspressoExt.kt
@@ -68,6 +68,18 @@ object EspressoHelper {
}
}
+fun withRetry(attempts: Int = 3, action: () -> Unit) {
+ runCatching { action() }.onFailure {
+ val remainingAttempts = attempts - 1
+ if (remainingAttempts <= 0) {
+ throw it
+ } else {
+ Thread.sleep(500)
+ withRetry(remainingAttempts, action)
+ }
+ }
+}
+
fun getString(@StringRes id: Int): String {
return EspressoHelper.getCurrentActivity()!!.resources.getString(id)
}
@@ -235,11 +247,16 @@ fun clickOnAndGoBack(@StringRes name: Int, block: () -> Unit) {
Espresso.pressBack()
}
-inline fun > interactWithSheet(contentMatcher: Matcher, noinline block: () -> Unit = {}) {
+inline fun > interactWithSheet(
+ contentMatcher: Matcher,
+ @BottomSheetBehavior.State openState: Int = BottomSheetBehavior.STATE_EXPANDED,
+ @BottomSheetBehavior.State exitState: Int = BottomSheetBehavior.STATE_HIDDEN,
+ noinline block: () -> Unit = {}
+) {
waitUntilViewVisible(contentMatcher)
val behaviour = (EspressoHelper.getBottomSheetDialog()!!.dialog as BottomSheetDialog).behavior
- withIdlingResource(BottomSheetResource(behaviour, BottomSheetBehavior.STATE_EXPANDED), block)
- withIdlingResource(BottomSheetResource(behaviour, BottomSheetBehavior.STATE_HIDDEN)) {}
+ withIdlingResource(BottomSheetResource(behaviour, openState), block)
+ withIdlingResource(BottomSheetResource(behaviour, exitState)) {}
}
class BottomSheetResource(
diff --git a/vector/src/androidTest/java/im/vector/app/espresso/tools/WaitActivity.kt b/vector/src/androidTest/java/im/vector/app/espresso/tools/WaitActivity.kt
index 7744d4b720..05f1ca2815 100644
--- a/vector/src/androidTest/java/im/vector/app/espresso/tools/WaitActivity.kt
+++ b/vector/src/androidTest/java/im/vector/app/espresso/tools/WaitActivity.kt
@@ -18,17 +18,25 @@ package im.vector.app.espresso.tools
import android.app.Activity
import android.view.View
-import androidx.test.espresso.Espresso
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.assertion.ViewAssertions
+import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers
import im.vector.app.activityIdlingResource
import im.vector.app.waitForView
import im.vector.app.withIdlingResource
import org.hamcrest.Matcher
+import org.hamcrest.Matchers.not
inline fun waitUntilActivityVisible(noinline block: (() -> Unit) = {}) {
withIdlingResource(activityIdlingResource(T::class.java), block)
}
fun waitUntilViewVisible(viewMatcher: Matcher) {
- Espresso.onView(ViewMatchers.isRoot()).perform(waitForView(viewMatcher))
+ onView(ViewMatchers.isRoot()).perform(waitForView(viewMatcher))
+}
+
+fun waitUntilDialogVisible(viewMatcher: Matcher) {
+ onView(viewMatcher).inRoot(isDialog()).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
+ waitUntilViewVisible(viewMatcher)
}
diff --git a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
index f998a9f23c..e4a536d422 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
@@ -16,6 +16,7 @@
package im.vector.app.ui
+import androidx.test.espresso.IdlingPolicies
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
@@ -30,6 +31,7 @@ import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith
import java.util.UUID
+import java.util.concurrent.TimeUnit
/**
* This test aim to open every possible screen of the application
@@ -51,6 +53,8 @@ class UiAllScreensSanityTest {
// 2021-04-08 Testing 429 change
@Test
fun allScreensTest() {
+ IdlingPolicies.setMasterPolicyTimeout(120, TimeUnit.SECONDS)
+
// Create an account
val userId = "UiTest_" + UUID.randomUUID().toString()
elementRobot.signUp(userId)
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
index a3bc5b26fc..99af7851ef 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
@@ -28,6 +28,7 @@ import com.adevinta.android.barista.interaction.BaristaDrawerInteractions.openDr
import im.vector.app.EspressoHelper
import im.vector.app.R
import im.vector.app.espresso.tools.waitUntilActivityVisible
+import im.vector.app.espresso.tools.waitUntilDialogVisible
import im.vector.app.espresso.tools.waitUntilViewVisible
import im.vector.app.features.createdirect.CreateDirectRoomActivity
import im.vector.app.features.home.HomeActivity
@@ -104,17 +105,19 @@ class ElementRobot {
}.isSuccess
if (expectSignOutWarning != isShowingSignOutWarning) {
- Timber.w("Unexpected sign out flow, expected warning to be: ${expectSignOutWarning.toWarningType()} but was ${isShowingSignOutWarning.toWarningType()}")
+ val expected = expectSignOutWarning.toWarningType()
+ val actual = isShowingSignOutWarning.toWarningType()
+ Timber.w("Unexpected sign out flow, expected warning to be: $expected but was $actual")
}
if (isShowingSignOutWarning) {
// We have sent a message in a e2e room, accept to loose it
clickOn(R.id.exitAnywayButton)
// Dark pattern
- waitUntilViewVisible(withId(android.R.id.button2))
+ waitUntilDialogVisible(withId(android.R.id.button2))
clickDialogNegativeButton()
} else {
- waitUntilViewVisible(withId(android.R.id.button1))
+ waitUntilDialogVisible(withId(android.R.id.button1))
clickDialogPositiveButton()
}
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt
index fd579c0d9f..934c6c76a1 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt
@@ -17,9 +17,13 @@
package im.vector.app.ui.robot
import androidx.test.espresso.Espresso.pressBack
+import androidx.test.espresso.matcher.ViewMatchers.withText
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
import com.adevinta.android.barista.interaction.BaristaListInteractions.clickListItem
+import com.google.android.material.bottomsheet.BottomSheetBehavior
import im.vector.app.R
+import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
+import im.vector.app.interactWithSheet
import java.lang.Thread.sleep
class MessageMenuRobot(
@@ -36,7 +40,9 @@ class MessageMenuRobot(
fun editHistory() {
clickOn(R.string.message_view_edit_history)
- pressBack()
+ interactWithSheet(withText(R.string.message_edits), openState = BottomSheetBehavior.STATE_COLLAPSED) {
+ pressBack()
+ }
autoClosed = true
}
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt
index fef5d4a1a2..8b87abadab 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt
@@ -26,6 +26,7 @@ import com.adevinta.android.barista.assertion.BaristaVisibilityAssertions.assert
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
import com.adevinta.android.barista.interaction.BaristaEditTextInteractions.writeTo
import im.vector.app.R
+import im.vector.app.espresso.tools.waitUntilViewVisible
import im.vector.app.waitForView
class OnboardingRobot {
@@ -42,6 +43,7 @@ class OnboardingRobot {
userId: String,
password: String,
homeServerUrl: String) {
+ waitUntilViewVisible(withId(R.id.loginSplashSubmit))
assertDisplayed(R.id.loginSplashSubmit, R.string.login_splash_submit)
clickOn(R.id.loginSplashSubmit)
assertDisplayed(R.id.loginServerTitle, R.string.login_server_title)
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt
index 24fe5adf64..53d6c16bb7 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt
@@ -30,12 +30,15 @@ import com.adevinta.android.barista.interaction.BaristaClickInteractions.longCli
import com.adevinta.android.barista.interaction.BaristaEditTextInteractions.writeTo
import com.adevinta.android.barista.interaction.BaristaMenuClickInteractions.clickMenu
import com.adevinta.android.barista.interaction.BaristaMenuClickInteractions.openMenu
+import com.google.android.material.bottomsheet.BottomSheetBehavior
import im.vector.app.R
import im.vector.app.espresso.tools.waitUntilViewVisible
import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBottomSheet
+import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
import im.vector.app.features.reactions.data.EmojiDataSource
import im.vector.app.interactWithSheet
import im.vector.app.waitForView
+import im.vector.app.withRetry
import java.lang.Thread.sleep
class RoomDetailRobot {
@@ -68,10 +71,14 @@ class RoomDetailRobot {
openMessageMenu(message) {
addQuickReaction(quickReaction)
}
+ println("Open reactions bottom sheet")
// Open reactions
- longClickOn(quickReaction)
+ longClickReaction(quickReaction)
// wait for bottom sheet
- pressBack()
+ interactWithSheet(withText(R.string.reactions), openState = BottomSheetBehavior.STATE_COLLAPSED) {
+ pressBack()
+ }
+ println("Room Detail Robot: Open reaction from emoji picker")
// Test add reaction
openMessageMenu(message) {
addReactionFromEmojiPicker()
@@ -81,16 +88,24 @@ class RoomDetailRobot {
edit()
}
// TODO Cancel action
- writeTo(R.id.composerEditText, "Hello universe!")
+ val edit = "Hello universe - long message to avoid espresso tapping edited!"
+ writeTo(R.id.composerEditText, edit)
// Wait a bit for the keyboard layout to update
waitUntilViewVisible(withId(R.id.sendButton))
clickOn(R.id.sendButton)
// Wait for the UI to update
- waitUntilViewVisible(withText("Hello universe! (edited)"))
+ waitUntilViewVisible(withText("$edit (edited)"))
// Open edit history
- openMessageMenu("Hello universe! (edited)") {
+ openMessageMenu("$edit (edited)") {
editHistory()
}
+ waitUntilViewVisible(withId(R.id.composerEditText))
+ }
+
+ private fun longClickReaction(quickReaction: String) {
+ withRetry {
+ longClickOn(quickReaction)
+ }
}
fun openMessageMenu(message: String, block: MessageMenuRobot.() -> Unit) {
@@ -111,7 +126,7 @@ class RoomDetailRobot {
}
fun openSettings(block: RoomSettingsRobot.() -> Unit) {
- clickOn(R.id.roomToolbarTitleView)
+ clickMenu(R.id.timeline_setting)
waitForView(withId(R.id.roomProfileAvatarView))
sleep(1000)
block(RoomSettingsRobot())
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/RoomSettingsRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/RoomSettingsRobot.kt
index 15186fe0c8..2c57dd058d 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/RoomSettingsRobot.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/RoomSettingsRobot.kt
@@ -26,6 +26,7 @@ import com.adevinta.android.barista.interaction.BaristaDialogInteractions.clickD
import com.adevinta.android.barista.interaction.BaristaListInteractions.clickListItem
import im.vector.app.R
import im.vector.app.espresso.tools.waitUntilActivityVisible
+import im.vector.app.espresso.tools.waitUntilDialogVisible
import im.vector.app.espresso.tools.waitUntilViewVisible
import im.vector.app.features.roommemberprofile.RoomMemberProfileActivity
@@ -78,9 +79,9 @@ class RoomSettingsRobot {
// Room permissions
clickListItem(R.id.matrixProfileRecyclerView, 17)
- waitUntilViewVisible(withText(R.string.room_permissions_title))
+ waitUntilViewVisible(withText(R.string.room_permissions_change_room_avatar))
clickOn(R.string.room_permissions_change_room_avatar)
- waitUntilViewVisible(withId(android.R.id.button2))
+ waitUntilDialogVisible(withId(android.R.id.button2))
clickDialogNegativeButton()
waitUntilViewVisible(withText(R.string.room_permissions_title))
// Toggle
@@ -95,7 +96,7 @@ class RoomSettingsRobot {
private fun leaveRoom(block: DialogRobot.() -> Unit) {
clickListItem(R.id.matrixProfileRecyclerView, 13)
- waitUntilViewVisible(withId(android.R.id.button2))
+ waitUntilDialogVisible(withId(android.R.id.button2))
val dialogRobot = DialogRobot()
block(dialogRobot)
if (dialogRobot.returnedToPreviousScreen) {
@@ -135,7 +136,7 @@ class RoomSettingsRobot {
// Role
clickListItem(R.id.matrixProfileRecyclerView, 3)
- waitUntilViewVisible(withId(android.R.id.button2))
+ waitUntilDialogVisible(withId(android.R.id.button2))
clickDialogNegativeButton()
waitUntilViewVisible(withId(R.id.matrixProfileRecyclerView))
pressBack()
diff --git a/vector/src/debug/AndroidManifest.xml b/vector/src/debug/AndroidManifest.xml
index 8ffcec6bc1..dba8440602 100644
--- a/vector/src/debug/AndroidManifest.xml
+++ b/vector/src/debug/AndroidManifest.xml
@@ -5,6 +5,7 @@
+
diff --git a/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
index 960994b169..64de648a23 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
@@ -35,6 +35,7 @@ import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.toast
import im.vector.app.databinding.ActivityDebugMenuBinding
import im.vector.app.features.debug.sas.DebugSasEmojiActivity
+import im.vector.app.features.debug.settings.DebugPrivateSettingsActivity
import im.vector.app.features.qrcode.QrCodeScannerActivity
import im.vector.lib.ui.styles.debug.DebugMaterialThemeDarkDefaultActivity
import im.vector.lib.ui.styles.debug.DebugMaterialThemeDarkTestActivity
@@ -75,6 +76,7 @@ class DebugMenuActivity : VectorBaseActivity() {
}
private fun setupViews() {
+ views.debugPrivateSetting.setOnClickListener { openPrivateSettings() }
views.debugTestTextViewLink.setOnClickListener { testTextViewLink() }
views.debugOpenButtonStylesLight.setOnClickListener {
startActivity(Intent(this, DebugVectorButtonStylesLightActivity::class.java))
@@ -115,6 +117,10 @@ class DebugMenuActivity : VectorBaseActivity() {
}
}
+ private fun openPrivateSettings() {
+ startActivity(Intent(this, DebugPrivateSettingsActivity::class.java))
+ }
+
private fun renderQrCode(text: String) {
views.debugQrCode.setData(text)
}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/di/MavericksViewModelDebugModule.kt b/vector/src/debug/java/im/vector/app/features/debug/di/MavericksViewModelDebugModule.kt
new file mode 100644
index 0000000000..8be4470b3f
--- /dev/null
+++ b/vector/src/debug/java/im/vector/app/features/debug/di/MavericksViewModelDebugModule.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.debug.di
+
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.multibindings.IntoMap
+import im.vector.app.core.di.MavericksAssistedViewModelFactory
+import im.vector.app.core.di.MavericksViewModelComponent
+import im.vector.app.core.di.MavericksViewModelKey
+import im.vector.app.features.debug.settings.DebugPrivateSettingsViewModel
+
+@InstallIn(MavericksViewModelComponent::class)
+@Module
+interface MavericksViewModelDebugModule {
+
+ @Binds
+ @IntoMap
+ @MavericksViewModelKey(DebugPrivateSettingsViewModel::class)
+ fun debugPrivateSettingsViewModelFactory(factory: DebugPrivateSettingsViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
+}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsActivity.kt
new file mode 100644
index 0000000000..25a068e794
--- /dev/null
+++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsActivity.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.debug.settings
+
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.R
+import im.vector.app.core.extensions.addFragment
+import im.vector.app.core.platform.VectorBaseActivity
+import im.vector.app.databinding.ActivitySimpleBinding
+
+@AndroidEntryPoint
+class DebugPrivateSettingsActivity : VectorBaseActivity() {
+
+ override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater)
+
+ override fun initUiAndData() {
+ if (isFirstCreation()) {
+ addFragment(
+ R.id.simpleFragmentContainer,
+ DebugPrivateSettingsFragment::class.java
+ )
+ }
+ }
+}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt
new file mode 100644
index 0000000000..808c379354
--- /dev/null
+++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.debug.settings
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.airbnb.mvrx.fragmentViewModel
+import com.airbnb.mvrx.withState
+import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.databinding.FragmentDebugPrivateSettingsBinding
+
+class DebugPrivateSettingsFragment : VectorBaseFragment() {
+
+ private val viewModel: DebugPrivateSettingsViewModel by fragmentViewModel()
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentDebugPrivateSettingsBinding {
+ return FragmentDebugPrivateSettingsBinding.inflate(inflater, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ setViewListeners()
+ }
+
+ private fun setViewListeners() {
+ views.forceDialPadTabDisplay.setOnCheckedChangeListener { _, isChecked ->
+ viewModel.handle(DebugPrivateSettingsViewActions.SetDialPadVisibility(isChecked))
+ }
+ }
+
+ override fun invalidate() = withState(viewModel) {
+ views.forceDialPadTabDisplay.isChecked = it.dialPadVisible
+ }
+}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt
new file mode 100644
index 0000000000..ecbb241387
--- /dev/null
+++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.debug.settings
+
+import im.vector.app.core.platform.VectorViewModelAction
+
+sealed class DebugPrivateSettingsViewActions : VectorViewModelAction {
+ data class SetDialPadVisibility(val force: Boolean) : DebugPrivateSettingsViewActions()
+}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt
new file mode 100644
index 0000000000..624c46556a
--- /dev/null
+++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.debug.settings
+
+import com.airbnb.mvrx.MavericksViewModelFactory
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import im.vector.app.core.di.MavericksAssistedViewModelFactory
+import im.vector.app.core.di.hiltMavericksViewModelFactory
+import im.vector.app.core.platform.EmptyViewEvents
+import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.features.settings.VectorDataStore
+import kotlinx.coroutines.launch
+
+class DebugPrivateSettingsViewModel @AssistedInject constructor(
+ @Assisted initialState: DebugPrivateSettingsViewState,
+ private val vectorDataStore: VectorDataStore
+) : VectorViewModel(initialState) {
+
+ @AssistedFactory
+ interface Factory : MavericksAssistedViewModelFactory {
+ override fun create(initialState: DebugPrivateSettingsViewState): DebugPrivateSettingsViewModel
+ }
+
+ companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory()
+
+ init {
+ observeVectorDataStore()
+ }
+
+ private fun observeVectorDataStore() {
+ vectorDataStore.forceDialPadDisplayFlow.setOnEach {
+ copy(
+ dialPadVisible = it
+ )
+ }
+ }
+
+ override fun handle(action: DebugPrivateSettingsViewActions) {
+ when (action) {
+ is DebugPrivateSettingsViewActions.SetDialPadVisibility -> handleSetDialPadVisibility(action)
+ }
+ }
+
+ private fun handleSetDialPadVisibility(action: DebugPrivateSettingsViewActions.SetDialPadVisibility) {
+ viewModelScope.launch {
+ vectorDataStore.setForceDialPadDisplay(action.force)
+ }
+ }
+}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt
new file mode 100644
index 0000000000..0ad4b185ec
--- /dev/null
+++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.debug.settings
+
+import com.airbnb.mvrx.MavericksState
+
+data class DebugPrivateSettingsViewState(
+ val dialPadVisible: Boolean = false
+) : MavericksState
diff --git a/vector/src/debug/res/layout/activity_debug_menu.xml b/vector/src/debug/res/layout/activity_debug_menu.xml
index fadffecf83..ac70e4ef0e 100644
--- a/vector/src/debug/res/layout/activity_debug_menu.xml
+++ b/vector/src/debug/res/layout/activity_debug_menu.xml
@@ -20,6 +20,12 @@
android:padding="@dimen/layout_horizontal_margin"
android:showDividers="middle">
+
+
المزامنة الأولية:
\nينتظر رد الخادم…
- غير %1$s اسمه الى %2$s
+ غير %1$s اسمه العلنيّ الى %2$s%1$s أضاف %2$s كعنوان لهذه الغرفة.%1$s أضاف %2$s كعنوان لهذه الغرفة.
@@ -1336,4 +1337,27 @@
%1$s فعّل تشفير طرف لطرف (لم يُتعرف على خوارزمية %2$s).عطلتّ تشفير طرف لطرف.%1$s فعّل تشفير طرف لطرف.
+ غيّر الموضوع
+ عدّل الأذونات
+ غيّر اسم الفضاء
+ غيّر اسم الغرفة
+ فعّل تشفير الفضاء
+ غيّر العنوان الرئيسي للفضاء
+ غيّر العنوان الرئيسي للغرفة
+ غيّر الصورة الرمزية للفضاء
+ غيّر الصورة الرمزية للغرفة
+ عدّل الودجات
+ نبّه الكل
+ أزل رسائل الآخرين
+ احظر مستخدمين
+ أطرد مستخدمين
+ غيّر الإعدادات
+ أدعوا مستخدمين
+ أرسل رسائل
+ فعّل تشفير الغرفة
+ أُستعيد النسخ الاحتياطي %s !
+ رجاءً أدخل مفتاح الاستعادة
+ فك قفل التأريخ
+ يستورد المفاتيح…
+ ينزّل المفاتيح…
\ No newline at end of file
diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml
index b509b3a9e8..bd89c0011b 100644
--- a/vector/src/main/res/values-cs/strings.xml
+++ b/vector/src/main/res/values-cs/strings.xml
@@ -69,7 +69,8 @@
Úvodní synchronizace:
\nImportuji místnostiÚvodní synchronizace:
-\nImportuji místností, jichž jste členy
+\nNačítání konverzací
+\nPokud jste se připojili k mnoha místnostem, může to chvíli trvatÚvodní synchronizace:
\nImportuji místnost, jež jste opustiliÚvodní synchronizace:
@@ -78,7 +79,7 @@
\nImportuji data účtuOdesílám zprávu…Úvodní synchronizace:
-\nImportuji pozvání
+\nImportuji pozvánky do místnostíVymazat frontu neodeslaných zpráv%1$s pozvali %2$s. Důvod: %3$s%1$s vás pozvali. Důvod: %2$s
@@ -381,7 +382,7 @@
PřečístVstoupit do místnostiUživatelské jméno
- Založit účet
+ Vytvořit účetPřihlásit seOdhlásit seAdresa domovského serveru
@@ -587,7 +588,7 @@
Opustit místnostOpravdu chcete opustit tuto místnost\?Opravdu chcete vyhodit %s z této konverzace\?
- Založit
+ VytvořitOn-lineOff-lineNečinný
@@ -720,7 +721,7 @@
NÍZKÁ PRIORITAPOZVÁNKYZačít konverzaci
- Založit místnost
+ Vytvořit místnostVstoupit do místnostiVstoupit do místnostiZadejte ID nebo přezdívku místnosti
@@ -1223,7 +1224,7 @@
Hledej v minulostiPro správu widgetů v této místnosti potřebujete oprávněníZaložení widgetu se nezdařilo
- Založit konferenční hovor s jitsi
+ Vytvořit konferenční hovor s jitsiJste si jisti, že chcete smazat widget z této místnosti\?%d aktivní widget
@@ -1253,7 +1254,7 @@
Použít fotoaparátPoužít mikrofonČíst media chráněná DRM
- Nelze založit widget.
+ Nelze vytvořit widget.Odeslání požadavku selhalo.Energetická hladina musí být pozitivní celé číslo.Nejste v této místnosti.
@@ -1310,8 +1311,8 @@
VypnoutTichéHlučné
- Založit
- Založit komunitu
+ Vytvořit
+ Vytvořit komunituJméno komunityPříkladId komunity
@@ -1384,7 +1385,7 @@
+%d%d+Žádný platný APK Google Plaz Services nenalezen. Oznámení možná nebudou pracovat spolehlivě.
- Založit přístupovou frázi
+ Vytvořit přístupovou fráziPotvrdit přístupovou fráziZadat přístupovou fráziPřistupová fráze se neshoduje
@@ -1550,7 +1551,7 @@
Událost moderována správcem místnostiNaposledy upravil %1$s dne %2$sNečitelná událost, nelze zobrazit
- Založit novou místnost
+ Vytvořit novou místnostŽádná síť. Prosím, zkontrolujte své spojení do internetu.ZměnitZměnit síť
@@ -1561,7 +1562,7 @@
MístnostiPřímé zprávyNová místnost
- ZALOŽIT
+ VYTVOŘITNázevVeřejnáKdokoli smí vstoupit do této místnosti
@@ -1608,7 +1609,7 @@
Úpravy nenalezenyFiltrovat konverzace…Nemůžete najít, co hledáte\?
- Založit novou místnost
+ Vytvořit novou místnostPoslat novou přímou zprávuUkázat adresář místnostíJméno nebo ID (#example:matrix.org)
@@ -1623,7 +1624,7 @@
Ukázat historii úpravVšeobecné podmínkyPročíst všeobecné podmínky
- Nechte se nalézt druhými
+ Nechte se najít druhýmiPoužijte boty, můstky, widgety a nálepkové sadyČtěte naServer pro identity
@@ -1631,11 +1632,11 @@
Nastavit server pro identityZměnit server pro identityNyní používáte %1$s, abyste nalezli a byli nalezeni existujícími kontakty, které znáte.
- Nyní nepoužíváte server pro identity. Abyste existující známé kontakty nalezli a nechali se jimi nalézt, nastavte nějaký níže.
+ Nyní nepoužíváte server pro identity. Abyste známé kontakty našli a nechali se jimi najít, nastavte nějaký níže.Emailová adresa k nalezeníVolby pro nalezení se ukážou, jakmile doplníte email.Volby pro nalezení se ukážou, jakmile doplníte telefonní číslo.
- Odpojení od serveru identit bude znamenat, že Vás jiní uživatelé nebudou moci nalézt a Vy nebudete moci pozvat druhé pomocí emailu nebo telefonního čísla.
+ Odpojení od serveru identit bude znamenat, že Vás jiní uživatelé nebudou moci najít a Vy nebudete moci pozvat druhé pomocí emailu nebo telefonního čísla.Telefonní čísla pro nalezeníPoslali jsme Vám potvrzovací email na %s, podívejte se do emailu a klikněte na protvrzovací odkazNevyřízený
@@ -1655,8 +1656,8 @@
Otevřít navigační zásuvkuOtevřít menu založení místnostiZavřít menu založení místnosti…
- Založit novou přímou konverzaci
- Založit novou místnost
+ Vytvořit novou přímou konverzaci
+ Vytvořit novou místnostZavřít titulek zálohy klíčůUkázat hesloSkrýt heslo
@@ -1734,7 +1735,7 @@
Připojit k Element Matrix ServicesUpravit připojení k serveruPřihlásit se na %1$s
- Založit účet
+ Vytvořit účetPřihlásit sePokračovat s SSOAdresa Element Matrix Services
@@ -1746,7 +1747,7 @@
\n
\nChcete se přihlásit webovým klientem\?Omlouváme se, tento server již nepřijímá nové účty.
- Aplikace nemůže založit účet na tomto domovském serveru.
+ Aplikace nemůže vytvořit účet na tomto domovském serveru.
\n
\nChcete se přihlásit webovým klientem\?Tato emailová adresa se nevztahuje k žádnému účtu.
@@ -1951,15 +1952,15 @@
Porovnejte kód s tím na obrazovce druhého uživatele.Zprávy s tímto uživatelem jsou koncově šifrovány a nemohou být čteny třetími stranami.Vaše nová relace je nyní ověřena. Má přístup k Vašim zašifrovaným zprávám a ostatní uživatelé ji uvidi jako důvěryhodnou.
- Křížový podpis
- Křížový podpis je zapnut.
+ Křížové podepisování
+ Křížové podpisování je zapnuto.
\nPrivátní klíče v zařízení.
- Křížový podpis je zapnut
+ Křížové podpisování je zapnuto
\nKlíče jsou důvěryhodné.
\nPrivátní klíče nejsou známy
- Křížový podpis je zapnut.
+ Křížové podpisování je zapnuto.
\nKlíče nejsou důvěryhodné
- Křížový podpis není zapnut
+ Křížové podpisování není zapnutoAktivní relaceUkázat všechny relaceSpráva relací
@@ -2006,7 +2007,7 @@
%d hlasů - Konečné výsledkyZvolená možnost
- Založí jednoduchou anketu
+ Vytvoří jednoduché hlasováníPoužijte metodu obnovyPokud se nemůžete dostat do existující relaceNové přihlášení
@@ -2142,19 +2143,19 @@
%1$s %2$sV této místnosti nejsou žádné souboryJinak, máte-li již účet a znáte-li svůj identifikátor a heslo, můžete použít tuto metodu:
- Přihlásit se s mým identifikátorem Matrixu
+ Přihlásit se identifikátorem MatrixuPřihlásit sePokud založíte účet na domovském serveru, použijte své Matrix ID (např. @user:domain.com) a heslo níže.Identifikátor uživateleTo není platný identifikátor uživatele. Platný formát: \'@uživatel:homeserver.org\'Nemohu najít platný domovský server. Prosím, zkontrolujte svůj identifikátorRežim letadlo je zapnut
- Použijte na svých zařízeních nejnovější ${app_name}, ${app_name} Web, ${app_name} Desktop, ${app_name} iOS, ${app_name} pro Android nebo jiný Matrix klient schopný křížového přihlášení
+ Použijte na svých zařízeních nejnovější ${app_name}, ${app_name} Web, ${app_name} Desktop, ${app_name} iOS, ${app_name} pro Android nebo jiný Matrix klient schopný křížového podepisování${app_name} Web
\n${app_name} Desktop${app_name} iOS
\n${app_name} Android
- nebo jiný Matrix klient schopný křížového přihlášení
+ nebo jiný Matrix klient schopný křížového podepisováníPoužijte na svých zařízeních nejnovější ${app_name}:Vynutí zahození probíhající skupinové relace v šifrované místnostiPodporováno jen v šifrovaných místnostech
@@ -2228,7 +2229,7 @@
Podařilo seOznámeníVolání ${app_name}u se nezdařilo
- Založit spojení v reálném čase se nezdařilo.
+ Nezdařilo se navázat spojení v reálném čase.
\nProsím, požádejte správce svého domovského serveru o konfiguraci TURN serveru, aby volání fungovala spolehlivě.Vybrat zvukové zařízeníTelefon
@@ -2276,7 +2277,7 @@
Ujistěte se, že kliknete na odkaz v e-mailu, který jsme Vám poslali.Bezpečná zálohaSpráva
- Založit bezpečnou zálohu
+ Vytvořit bezpečnou zálohuResetovat bezpečnou zálohuNastavit na tomto zařízeníOchrana před ztrátou přístupu k šifrovaným zprávám a datům pomocí zálohy šifrovacích klíčů na Vašem serveru.
@@ -2402,7 +2403,7 @@
Zapomněli jste PIN\?Resetovat PINNový PIN
- Pro resetování svého PINu se budete muset nově přihlásit a založit nový.
+ Pro resetování svého PINu se budete muset nově přihlásit a vytvořit nový.Zapnout PINChcete-li resetovat svůj PIN, klepněte na Zapomenutý PIN pro odhlášení a resetování.Pro vypnutí PINu potvrďte PIN
@@ -2415,7 +2416,7 @@
Zobrazit stavové události účastníků v místnostiZahrnuje události pozvat/vstoupit/opustit/vykopnout/vykázat a změny avatara/veřejného jména.
- Průzkum
+ HlasováníTlačítka botůReagoval(a): %sVýsledek ověření
@@ -2526,8 +2527,8 @@
Skrýt pokročiléUkázat pokročilé%1$d z %2$d
- Založit novou přímou konverzaci pomocí Matrix ID
- Založit novou přímou konverzaci pomocí skenu QR kódu
+ Vytvořit novou přímou konverzaci pomocí Matrix ID
+ Vytvořit novou přímou konverzaci pomocí skenu QR kóduAbyste nalezli existující kontakty, jež znáte, souhlasíte s odesláním svých kontaktních údajů (telefonní čísla nebo emailové adresy) na nastavený server identit (%1$s)\?
\n
\nZa účelem soukromí budou data před odesláním hašována.
@@ -2570,7 +2571,7 @@
Zveřejnit tuto adresuPřidat lokální adresuTato místnost nemá žádné lokální adresy
- Nastavte adresy pro tuto místnost, aby uživatelé mohli tuto místnost nalézt přes Váš domovský server (%1$s)
+ Nastavte adresy pro tuto místnost, aby uživatelé mohli tuto místnost najít přes Váš domovský server (%1$s)Lokální adresaNová zveřejněná adresa (např. #alias:server)Zatím žádné zveřejněné adresy.
@@ -2750,7 +2751,7 @@
\nZkuste znovu později nebo se obraťte na správce místnosti, máte-li přístup.I tak vstoupitVstoupit do prostoru
- Založit prostor
+ Vytvořit prostorNyní přeskočitPřipojte se k mému prostoru %1$s %2$sNebudou součástí %s
@@ -2770,7 +2771,7 @@
Jaké diskuse si přejete vést v %s\?Dejte prostoru jméno a pokračujte.Doplňte nějaké podrobnosti, aby zaujal. Můžete je kdykoli změnit.
- Založit prostor
+ Vytvořit prostorPouze na pozvání, nejlepší pro Vás nebo týmyPrivátníOtevřený pro všechny, nejlepší pro komunity
@@ -2783,19 +2784,19 @@
S kým pracujete\?Ke vstupu do existujícího prostoru potřebujete pozvání.Můžete změnit později
- Jaký typ prostoru chcete založit\?
+ Jaký typ prostoru chcete vytvořit\?Váš privátní prostorVáš veřejný prostorPřidat prostorOpustit místnost s daným id (nebo nynější místnost pokud prázdné)Vstoupit do prostoru s daným id
- Založit prostor
+ Vytvořit prostorHledat jméno
- Kdokoli v prostoru s touto místností ji může nalézt a vstoupit. Jen správci místnosti ji mohou připojit k prostoru.
+ Kdokoli v prostoru s touto místností ji může najít a vstoupit do ní. Do prostoru ji mohou připojit jen správci místnosti.Pouze členové prostoru
- Kdokoli může místnost nalézt a vstoupit
- Veřejná
- Pouze pozvaní mohou místnost nalézt a vstoupit
+ Kdokoliv může místnost najít a připojit se do ní
+ Veřejný
+ Pouze pozvaní mohou místnost najít a vstoupit do níPrivátníNeznámé nastavení přístupu (%s)Každý může na místnost zaklepat, členové pak mohou přijmout či odmítnout
@@ -2930,7 +2931,7 @@
Prostory, které mají přístupUmožněte členům prostoru ho najít a zpřístupnit.Členové prostoru %s mohou vyhledávat, prohlížet a připojovat se.
- Soukromé (pouze pro pozvané)
+ Soukromý (pouze pro pozvané)Chcete-li odesílat hlasové zprávy, povolte oprávnění mikrofonu.Aktualizace místnostiZprávy od bota
@@ -3024,7 +3025,7 @@
Nevypadá to jako platná e-mailová adresaOtevřít nastavení objevováníVyhledávání podle jména, ID nebo emailu
- Založit nový prostor
+ Vytvořit nový prostorKaždý může prostor najít a připojit se k němuAdresa prostoruKdo má přístup\?
@@ -3061,4 +3062,7 @@
NedostupnýOfflineOnline
+ Naslouchání oznámením
+ Vyberte domovský server
+ Nelze se spojit s domovským serverem na adrese %s. Zkontrolujte prosím svůj odkaz nebo vyberte domovský server ručně.
\ No newline at end of file
diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml
index 4312898f17..db652492e4 100644
--- a/vector/src/main/res/values-de/strings.xml
+++ b/vector/src/main/res/values-de/strings.xml
@@ -1949,8 +1949,8 @@
Raum verlassenVerlasse den Raum…Administratoren
- Moderierende
- benutzerdefiniert
+ Moderatoren
+ BenutzerdefiniertEingeladenNutzerAdmin in %1$s
diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml
index 2585eaa15a..a74165ac95 100644
--- a/vector/src/main/res/values-et/strings.xml
+++ b/vector/src/main/res/values-et/strings.xml
@@ -61,21 +61,22 @@
%1$s ja %2$d muudTühi jututuba
- Alglaadimine:
+ Esmane laadimine:
\nImpordin kontot…
- Alglaadimine:
+ Esmane laadimine:
\nImpordin krüptoseadistusi
- Alglaadimine:
+ Esmane laadimine:
\nImpordin jututubasid
- Alglaadimine:
-\nImpordin liitutud jututubasid
- Alglaadimine:
+ Esmane laadimine:
+\nLaadin sinu vestluste ja jtututubade andmeid
+\nKui sa oled liitunud paljude jututubadega, siis kulub natuke aega
+ Esmane laadimine:
\nImpordin kutsutud jututubasid
- Alglaadimine:
+ Esmane laadimine:
\nImpordin lahkutud jututubasid
- Alglaadimine:
+ Esmane laadimine:
\nImpordin kogukondi
- Alglaadimine:
+ Esmane laadimine:
\nImpordin kontoandmeidSaadan sõnumit…Tühjenda saatmisjärjekord
@@ -3005,4 +3006,7 @@
Pole leitavVõrgust väljasVõrgus
+ Vali koduserver
+ Ei õnnestu ühendus koduserveriga aadressil %s. Palun kontrolli kas link on õige või lisa koduserver käsitsi.
+ Vaatan, kas leidub teavitusi
\ No newline at end of file
diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml
index c2b55bb06c..f99084811e 100644
--- a/vector/src/main/res/values-fa/strings.xml
+++ b/vector/src/main/res/values-fa/strings.xml
@@ -62,21 +62,22 @@
اتاق خالیهمگامسازی نخستین:
-\nدر حال درونریزی حساب…
+\nدرونریزی حساب…همگامسازی نخستین:
-\nدر حال درونریزی رمزنگاری
+\nدرونریزی رمزنگاریهمگامسازی نخستین:
-\nدر حال درونریزی اتاقها
+\nدرونریزی اتاقهاهمگامسازی نخستین:
-\nدر حال درونریزی اتاقهای پیوسته
+\nبار کردن گفتوگوهایتان
+\nاگر به اتاقهای زیادی پیوستهاید، ممکن است کمی طول بکشدهمگامسازی نخستین:
-\nدر حال درونریزی اتاقهای دعوتشده
+\nدرونریزی اتاقهای دعوتشدههمگامسازی نخستین:
-\nدر حال درونریزی اتاقهای ترکشده
+\nدرونریزی اتاقهای ترک شدههمگامسازی نخستین:
-\nدر حال درونریزی انجمنها
+\nدرونریزی اجتماعهاهمگامسازی نخستین:
-\nدر حال درونریزی دادههای حساب
+\nدرونریزی دادههای حسابدر حال فرستادن پیام…پاکسازی صفِ در حال ارسالدعوت %1$s. دلیل: %2$s
@@ -2633,7 +2634,7 @@
%1$s یک نشانی جایگزین %2$s را برای این اتاق اضافه کرد.%1$s نشانیهای جایگزین %2$s را برای این اتاق اضافه کرد.
- همگامسازی اولیه:
+ همگامسازی نخستین:
\nبارگیری دادهها…همگامسازی نخستین:
\nمنتظر پاسخ کارساز…
@@ -3005,4 +3006,7 @@
ناموجودبرونخطبرخط
+ گزینش کارساز خانگی
+ نتوانست در نشانی %s به کارساز خاتگیای برسد. لطفاً پیوندتان را بررسی کرده یا کارساز خانگیای را به صورت دستی برگزینید.
+ شنود کردن برای آگاهیها
\ No newline at end of file
diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml
index 12eca6b823..8f4c4a9366 100644
--- a/vector/src/main/res/values-fr/strings.xml
+++ b/vector/src/main/res/values-fr/strings.xml
@@ -68,7 +68,8 @@
Synchronisation initiale :
\nImportation des salonsSynchronisation initiale :
-\nImportation des salons que vous avez rejoints
+\nChargement des conversations
+\nCela peut prendre du temps si vous avez rejoint beaucoup de salonsSynchronisation initiale :
\nImportation des salons où vous avez été invitéSynchronisation initiale :
@@ -3012,4 +3013,7 @@
IndisponibleHors ligneEn ligne
+ Choisir un serveur d’accueil
+ Impossible de contacter un serveur d’accueil à l’URL %s. Veuillez vérifier votre lien ou choisir manuellement un serveur d’accueil.
+ Écoute des notifications
\ No newline at end of file
diff --git a/vector/src/main/res/values-fy/strings.xml b/vector/src/main/res/values-fy/strings.xml
index 0e31fcbd00..a542951669 100644
--- a/vector/src/main/res/values-fy/strings.xml
+++ b/vector/src/main/res/values-fy/strings.xml
@@ -4,7 +4,7 @@
Donker temaSwart temaTsjinst wurdt inisjalisearre
- Syngronisearje…
+ Syngronisaasje…Lústerje nei barrensLûdsmeldingenStille meldingen
@@ -20,15 +20,15 @@
Kaaireservekopy brûkeApparaat ferifiearjeKaaireservekopy is noch net ree, in amerijke…
- As jo jo no ôfmelde, sille jo jo fersifere berjochten kwytreitsje
- Kaaireservekopy is dwaande. Asto dy no ôfmeldst, silsto de tagong ta dyn fersifere berjochten kwytreitsje.
- Feilige kaaireservekopy moat op al dyn apparaten aktyf wêze om de tagong ta dyn fersifere berjochten net kwyt te reitsjen.
+ As jo no ôfmelde, sille jo de fersifere berjochten kwytreitsje
+ Kaaireservekopy is dwaande. As jo no ôfmelde, sille jo de tagong ta jo fersifere berjochten kwytreitsje.
+ Feilige kaaireservekopy moat op al jo apparaten aktyf wêze om de tagong ta jo fersifere berjochten net kwyt te reitsjen.Ik wol myn fersifere berjochten netReservekopy fan kaaien wurdt makke…Kaaireservekopy brûkeBinne jo wis\?Reservekopy meitsje
- Do silst de tagong ta dyn fersifere berjochten ferlieze, útsein asto earst in reservekopy fan dyn kaaien makkest eardatsto dy ôfmeldst.
+ Jo sille de tagong ta jo fersifere berjochten ferlieze, útsein as jo earst in reservekopy fan jo kaaien meitsje eardat jo ôfmelde.Lisinsjes fan tredde partijenLade…OK
@@ -55,15 +55,15 @@
YnlûkeFerbining ferbrekkeYnhâld melde
- Aktive petear
- Rinnend gearkomstpetear.
-\nNim diel mei %1$s of %2$s
+ Aktive oprop
+ Rinnend gearkomstpetear.
+\nDielnimme mei %1$s of %2$smikrofoankameraKin de oprop net starte, probearje it letter nochrisGuon funksjes binne miskien ôfwêzich fanwegen ûntbrekkende rjochten…Dizze aksje is net mooglik fanwegen ûntbrekkende rjochten.
- Om in gearkomst yn dit groepspetear te starten hasto útnûgingsrjochten nedich
+ Om in gearkomst yn dit groepspetear te starten hawwe jo útnûgingsrjochten nedichKin de oprop net starteApparaatynformaasjeGearkomstpetearen wurde net stipe yn fersifere petearen
@@ -82,7 +82,7 @@
OfsluteAksjesOfmelde
- Binne jo wis dat jo jo ôfmelde wolle\?
+ Binne jo wis dat jo ôfmelde wolle\?SpraakopropFideo-opropGlobaal sykje
@@ -97,9 +97,9 @@
BefêstigingWarskôgingFlater
- Start
+ StartskermFavoriten
- Minsken
+ PersoanenPetearenMienskippenPetearnammen filterje
@@ -138,40 +138,41 @@
Beskriuw hjir jo probleemÔfrûnjeHâld it feilich
- Do binst klear!
- Brûk disse %1$s as in feiligensnet, foar it gefal ast dyn %2$s ferjitst.
+ Klear!
+ Brûk dizze %1$s as in befeiligingsnet, foar it gefal as jo %2$s ferjitte.Ophingje
- Ferfalle litte
+ OfwizeAkseptearje
- Do hast it haadadres fan dizze keamer fuortsmiten.
- %1$s hat it adres fan dizze keamer feroare.
- Do hast it adres fan dizze keamer feroare.
- Do binst fuort gien. Reden: %1$s
+ Jo hawwe it haadadres fan dit petear fuortsmiten.
+ %1$s hat it petearadres wizige.
+ Jo hawwe it petearadres wizige.
+ Jo binne fuortgien. Reden: %1$s%1$s is fuort gien. Reden: %2$s
- Do hast de keamer ferlitten. Reden: %1$s
+ Jo hawwe de keamer ferlitten. Reden: %1$s%1$s hat de keamer ferlitten. Reden: %2$s
- Do binst de keamer yn kaam. Reden: %1$s
+ Jo binne de keamer yn kaam. Reden: %1$s%1$s is de keamer yn kaam. Reden: %2$s
- Do binst de keamer yn kaam. Reden: %1$s
+ Jo binne de keamer yn kaam. Reden: %1$s%1$s is de keamer yn kaam. Reden: %2$s
- %1$s hat dy útnûge. Reden: %2$s
- Do hast %1$s útnûge. Reden: %2$s
+ %1$s hat jo útnûge. Reden: %2$s
+ Jo hawwe %1$s útnûge. Reden: %2$s%1$s hat %2$s útnûge. Reden: %3$s
- Dyn útnûging. Reden: %1$s
+ Jo útnûging. Reden: %1$s%1$s harren útnûging. Reden: %2$s
- Ferstjoeringslist leech meitsje
+ Utgeande wachtrige wiskjeBerjocht oan it fersjoeren…Berjocht ferstjoerd
- Inisjele Syngronisaasje:
-\nAkkount Data Binnenhelje
+ Inisjele syngronisaasje:
+\nAccountgegevens ymportearjeInisjele Syngronisaasje:
-\nMienskippen Binnenhelje
+\nMienskippen ymportearjeInisjele Syngronisaasje:
-\nOerblieuwne Keamers Binnenhelje
+\nFerlitten keamers ymportearjeInisjele syngronisaasje:
-\nKeamers dêr\'t jo foar útnûge binne ymportearje
+\nKeamers dêr’t jo foar útnûge binne ymportearjeInisjele syngronisaasje:
-\nKeamers wer ast yn kaam bist ymportearje
+\nJo petearen lade
+\nAs jo yn in protte keamers binne kin dit efkes duorjeInisjele syngronisaasje:
\nKeamers ymportearjeInisjele syngronisaasje:
@@ -181,49 +182,49 @@
Inisjele syngronisaasje:
\nWachtsjend op reaksje fan server…Lege keamer (wie %s)
- Lege keamer
+ Leech petearE-mailadresUtnûging fan %sTelefoannûmerOpladen fan ôfbylding mislearre
- ** Koe %s net ûntsiferje **
+ ** Koe net ûntsiferje: %s **Koe net ynlûkeOanpastOanpast (%1$d)Standert
- Do hast de fideokonferinsje oanpast
+ Jo hawwe de fideokonferinsje oanpastBehearderBerjocht fuortsmiten troch %1$s [reden: %2$s]Berjocht fuortsmiten [reden: %1$s]Berjocht fuortsmiten troch %1$s
- Gjin feroaringen.
- • Tsjinners dy foldoche oan %s bin tastien.
- Do hast de ACLs foar de keamer opnij ynsteld.
- %s hat de ACLs foar disse keamer ynsteld.
- Do hast disse keamer opwurdeare.
- %s hat disse keamer upwurdeare.
- Do hast end-to-end fersifering ynskakele (%1$s)
- %1$s hat end-to-end fersifering ynskakele (%2$s)
+ Gjin wizigingen.
+ • Servers dy’t oerienkomme mei %s binne tastien.
+ Jo hawwe de ACL’s foar dizze keamer ynsteld.
+ %s hat de ACL’s foar dizze keamer ynsteld.
+ Jo hawwe dizze keamer opwurdearre.
+ %s hat dizze keamer opwurdearre.
+ Jo hawwe end-to-end-fersifering ynskeakele (%1$s)
+ %1$s hat end-to-end-fersifering ynskeakele (%2$s)ûnbekend (%s).
- Alle keamerleden.
- Olle keamerleden, fanôf it momint dot se de keamer ynkaam binne.
- Olle keamerleden, fanôf it momint dot se útnûge binne.
+ alle dielnimmers oan it petear.
+ alle dielnimmers, fan it momint ôf dat se de keamer ynkaam binne.
+ alle dielnimmers oan it petear, fan it momint ôf dat se útnûge binne.%1$s: %2$sIepen foar eltsenien, it beste foar mienskippenAlle mienskippenMienskips IDMienskipsnammeMienskip Oanmeitsje
- \'%s\' is net in jildich mienskips ID
+ ‘%s’ is net in jildich mienskips-IDÛnjildich mienskips IDNije mienskips ID (b.v. +foo:matrix.org)
- Do binst op it momint net lid fan in mienskip.
+ Jo binne op dit stuit net lid fan in mienskip.Sukses
- Brûkers ID, Namme of email
- Disse keamer ferlitte
+ Brûkers-ID, namme of e-mailadres
+ Dizze keamer ferlitteOanmeitsje
- Út disse keamer smite
- Do hast gjin tastimming om enkripsje yn te skakeljen yn disse keamer.
+ Ut dizze keamer fuortsmite
+ Jo hawwe gjin tastimming om fersifering yn dizze keamer yn te skeakeljen.TelefoanMy net wer freegjeNij petear begjinne
@@ -236,32 +237,32 @@
Yn mien kontakten sykjeOm matrix kontakten sykjeFuortsmite…
- Wolst dit taheaksel fersjoere nei %1$s\?
+ Wolle jo dizze bylage nei %1$s ferstjoere\?Taheaksel ferstjoereDe ferifikaasje koade is net jildich.Koade
- In tekst berjocht is nei %s stjoerd. Graach de ferifikaasje koade ynfiere dyt der yn stiet.
- Kin net ferbine mei identiteits tsjinner
- Identiteits tsjinner URL ynfiere
- Emails en tillefoan nûmers ferstjoere
+ In tekstberjocht is nei %s stjoerd. Graach de ferifikaasjekoade ynfiere dy’t der ynstiet.
+ Kin net ferbine mei identiteitsserver
+ Fier identiteitsserver-URL yn
+ E-mailadressen en telefoannûmers ferstjoereTastimming jaanTastimming wer ynlûke
- Do hast gjin tastimming jûn emails en tillefoan nûmers nei disse identiteits tsjinner te stjoeren, om oare brûkers út dyn kontakten te finen.
- Do hast tastimming jûn emails en tillefoan nûmers nei disse identiteits tsjinner te stjoeren, om oare brûkers út dyn kontakten te finen.
- Emails en tillefoan nûmers ferstjoere
+ Om oare brûkers út jo kontakten te finen, hawwe jo gjin tastimming jûn e-mailadressen en telefoannûmers nei dizze identiteitsserver te stjoeren.
+ Om oare brûkers út jo kontakten te finen, hawwe jo tastimming jûn e-mailadressen en telefoannûmers nei dizze identiteitsserver te stjoeren.
+ E-mailadressen en telefoannûmers ferstjoereYn ôfwachtingBots, brêgen, widgets en sticker paketten brûkeFoar oaren fynber wéze
- Feroarings Skiednis Besjen
+ Bewurkingsskiednis toaneKeamer binnen gean…Keamer wurd oanmakke…Nije keamer oanmeitsjeGjin feroaringen fûn
- (feroare)
+ (bewurke)Wachtsjend…Hulp & OerFlugge Reaksjes
- Matrix SDK Ferzje
+ Matrix SDK-ferzjeKeamer ynstellingsÛnderwerpKeamer ûnderwerp (opsjoneel)
@@ -271,53 +272,53 @@
Nije KeamerDirekte BerjochtenKeamers
- Disse keamer kin net ynsjûn wurde. Wolst de keamer binnen gean\?
- Disse keamer jout no gjin tagong.
-\nProbearje it letter noch ris in kear, of freechje in administrator om te sjen ast wol de nediche rjochten hast.
+ Dizze keamer kin net yn it foar toand wurde. Wolle jo de keamer binnen gean\?
+ Dizze keamer jout no gjin tagong.
+\nProbearje it letter noch ris, of freegje in behearder om te sjen oft jo wol de nedige rjochten hawwe.Ynsjoch yn wrâld-lésbere keamers wurd noch net stipe troch ${app_name}
- Disse keamer kin net ynsjûn wurde
+ Dizze keamer kin net yn it foar toand wurdeGraach efkes wachtsje…Netwurk feroarjeFeroarje
- Gjin netwurk. Kontroleerje dyn internet ferbyning.
+ Gjin netwurk. Kontrolearje jo ynternetferbining.Nije Keamer Meitsje
- Net gûd foarme evenemint, kin net sjen litten wurde
- Foar it letst feroare troch %1$s op %2$s
+ Net krekt foarme evenemint, kin net toand wurde
+ Foar it lêst troch %1$s wizige op %2$sEvenemint troch brûker fuortsmiten
- Fuortsmiten berjiochten sjen litte
+ Fuortsmiten berjochten toaneBerjocht fuortsmitenReaksjesKeamer List
- Eltsenien kin disse keamer binnen gean
+ Elkenien kin dizze keamer binnen geanIepenbierFoarkarrenSugestje dwaanin keamer
- Nij Petear
+ Nije chatYnsjenMeidwaanFuortsmite
- Troch gean
+ TrochgeanNEEJA
- Informaasje
- Troch gean
- Keamer ûnderwerp
- Keamer namme
- Hjoerd
+ Ynformaasje
+ Trochgean
+ Keamerûnderwerp
+ Keamernamme
+ HjoedJusterLytsGemiddeldGrut
- Orizineel
- Ferstjoer as
+ Orizjineel
+ Ferstjoere as
- %d lidmaatskip wiizige
- %d lidmaatskippen wiizige
+ %d lidmaatskip wizige
+ %d lidmaatskippen wizigeGroepslistOanfraach ferstjoerd
- Kaai oanfraach ferstjoerd.
+ Kaaioanfraach ferstjoerd.Der is noch net op de keppeling yn it e-mailberjocht kliktDizze brûkersnamme is al yn gebrûkDer binne te folle oanfragen ferstjoerd
@@ -328,19 +329,19 @@
Kin de thússerver op dizze URL net berikke, kontrolearje ditDit is net in jildich Matrix-serveradresDizze URL is net te berikken, kontrolearje dit
- Fier in jildiche URL yn
+ Fier in jildige URL ynRegistrearjen mislearreKin net registrearje: netwurkflater
- Kin net oanmelde
+ Oanmelden mislearreKoe it e-mailadres net ferifiearje: soargje dat jo op de keppeling yn it e-mailberjocht klikt hawwe
- Der is in e-mail nei %s stjoerd. Klik hjirûnder sa gau as jo de keppeling yn de e-mail besocht hawwe.
- Kontrolearje dyn e-mail om troch te gean mei de registraasje
+ Der is in e-mailberjocht stjoerd nei %s. Klik hjirûnder sa gau as jo de keppeling yn it e-mailberjocht besocht hawwe.
+ Kontrolearje jo e-mail om troch te gean mei de registraasjeWachtwurd fergetten\?
- Wachtwurden binne net gelyk
+ Wachtwurden binne net lykUnjildich token
- Der mist in e-mailadres of telefoannûmer
- Telefoannûmer mist
- E-mailadres mist
+ E-mailadres of telefoannûmer ûntbrekt
+ Telefoannûmer ûntbrekt
+ E-mailadres ûntbrektDit e-mailadres is al yn gebrûk.Dit is gjin jildich telefoannûmerTelefoannûmer (opsjoneel)
@@ -353,7 +354,7 @@
Fideo meitsjeFoto meitsjeFoto of fideo meitsje
- Stickers ferstjoere
+ Stikkers ferstjoereBestannen ferstjoereHD ynskeakeljeHD útskeakelje
@@ -370,13 +371,13 @@
Identiteitsserver:Thússerver:Brûkersnamme al yn gebrûk
- Feiligens & Privacy
+ Befeiliging & privacyTastean
- Keamer ferzje
- Avanseare
- Rômtes
+ Keamerferzje
+ Avansearre
+ Romten allinnich ledenEltsenien kin de keamer fyne en der yn komme
- Eltsenien
+ ElkenienGasten tastean om de keamer yn te geanDit is it haad adresHaad adres
@@ -384,71 +385,71 @@
1 moanne1 wike3 dagen
- Foar oltiid
+ AltydTafoegjeNotifikaasjes
- Olgemien
+ AlgemienMatrix-flaterNetwurkflaterKin it berjocht net ferstjoereBerjocht fuortsmiten
- Waskôging hat ûndersteuning fan\'e tsjinner en in eksperimintele keamer nedich
+ Warskôging hat serverstipe en in eksperimintele keamer nedichEksperiminteel Rômte - Beheinde Keamer.
- Do bist útnûge
- eltsenien.
- Do hast takomstige berjochten foar %1$s sichtber makke
+ Jo binne útnûge
+ elkenien.
+ Jo hawwe takomstige berjochten foar %1$s sichtber makke%1$s hat takomstige berjochten foar %2$s sichtber makke
- Do hast de takomstige keamer skiednis foar %1$s sichtber makke
- %1$s hat de takomstige keamer skiednis foar %2$s sichtber makke
- Do hast it petear beënige.
+ Jo hawwe de takomstige petearskiednis foar %1$s sichtber makke
+ %1$s hat de takomstige petearskiednis foar %2$s sichtber makke
+ Jo hawwe de oprop beëinige.%s hat it petear beëinige.
- Do hast it petear beantwurde.
- %s hat it petear beantwurde.
- Do hast data stjoerd om in petear op te setten.
+ Jo hawwe de oprop beäntwurde.
+ %s hat de oprop beäntwurde.
+ Jo hawwe data stjoerd om in petear op te setten.%s hat data stjoerd om in petear op te setten.
- %s hat in fideo petear oanmakke.
- Do hast in fideo petear oanmakke.
- %s hat in fideo petear oanmakke.
- Do hast de namme fan\'e keamer feroare nei: %1$s
- %1$s hat de namme fan\'e keamer feroare nei: %2$s
- Do hast it ûnderwerp feroare nei: %1$s
- %1$s hat it ûnderwerp feroare nei: %2$s
- Do hast dyn namme fuortsmiten (it wie %1$s)
- %1$s hat syn namme fuortsmiten (it wie %2$s)
- Do hast dyn namme fan %1$s nei %2$s feroare
- %1$s hat syn namme fan %2$s nei %3$s feroare
- Do hast dyn namme nei %1$s feroare
- %1$s hat syn namme nei %2$s feroare
- Do hast de útnûging foar %1$s wer ynlutsen
+ %s hat in fideopetear oanmakke.
+ Jo hawwe in fideopetear iepene.
+ %s hat in fideo-oprop oanmakke.
+ Jo hawwe de keamernamme wizige nei: %1$s
+ %1$s hat de petearnamme wizige nei: %2$s
+ Jo hawwe it ûnderwerp wizige nei: %1$s
+ %1$s hat it ûnderwerp wizige nei: %2$s
+ Jo hawwe jo werjeftenamme fuortsmiten (wie %1$s)
+ %1$s hat de werjeftenamme fuortsmiten (wie %2$s)
+ Jo hawwe jo werjeftenamme fan %1$s wizige nei %2$s
+ %1$s hat de werjeftenamme fan %2$s wizige nei %3$s
+ Jo hawwe jo werjeftenamme wizige nei %1$s
+ %1$s hat de werjeftenamme wizige nei %2$s
+ Jo hawwe de útnûging foar %1$s wer ynlutsen%1$s hat de útnûging foar %2$s wer ynlutsen
- Do hast %1$s ferbonne
- Do bist de keamer yn kaam
- %1$s hat %2$s ferbonne
- Do hast %1$s der út skopt
- %1$s hat %2$s der út skopt
- Do hast de útnûging ôfwiisd
- %1$s hat de útnûging ôfwiisd
- Do hast de keamer ferlitten
+ Jo hawwe %1$s ferballe
+ Jo binne oansluten
+ %1$s hat %2$s ferballe
+ Jo hawwe %1$s der útskopt
+ %1$s hat %2$s der útskopt
+ Jo hawwe de útnûging wegere
+ %1$s hat de útnûging wegere
+ Jo hawwe de keamer ferlitten%1$s hat de keamer ferlitten
- Do hast de keamer ferlitten
+ Jo hawwe de keamer ferlitten%1$s hat de keamer ferlitten%1$s is de keamer yn kaam
- Do bist de keamer yn kaam
- %1$s is de keamer yn kaam
- %1$s hat dy útnûge
- Do hast %1$s útnûge
+ Jo binne de keamer yn kaam
+ %1$s nimt no diel oan it petear
+ %1$s hat jo útnûge
+ Jo hawwe %1$s útnûge%1$s hat %2$s útnûge
- Do hast de diskusje oanmakke
+ Jo hawwe de diskusje oanmakke%1$s hat de diskusje oanmakke
- Do hast de keamer oanmakke
+ Jo hawwe de keamer oanmakke%1$s hat de keamer oanmakke
- Dyn útnûging
- %s\'s útnûging
- Do hast in sticker stjoerd.
- %1$s hat in sticker stjoerd.
- Do hast in ôfbylding stjoerd.
+ Jo útnûging
+ Utnûging fan %s
+ Jo hawwe in stikker stjoerd.
+ %1$s hat in stikker stjoerd.
+ Jo hawwe in ôfbylding stjoerd.%1$s hat in ôfbylding stjoerd.
- Gjin jildiche Google Play Services APK fûn. Notifikaasjes kinne wol ris net wurkje.
+ Gjin jildige Google Play Services APK fûn. Notifikaasjes kinne wolris net wurkje.%d++%d%1$s: %2$s
@@ -456,54 +457,54 @@
Allinich foar flatersFoar berjochten en flatersIvich
- Informaasje krite sjen litte
+ Ynformaasjegebiet toaneynteareútteareSorry, in flater die sich foar
- Graach %s om disse tsjinst brûken te blieuwen.
+ Graach %s om dizze tsjinst brûke te bliuwen.Klik hjir om âldere berjochten te besjen
- Disse keamer is in trochsetting fan in oar petear
+ Dizze keamer is in fuortsetting fan in oar petearIt petear giet hjir troch
- Disse keamer is ferfong en is net mear aktyf.
- Graach dyn wachtwurd ynfiere.
- Graach in brûkersnamme ynfiere.
- Akkount Útskeakelje
- Om troch te gean, fier dyn wachtwurd yn:
- Graach alle berjochten ferjitte dyt ik ferstjoerd ha wannear myn akkount útskeakele is (Warskôging: hjir troch kin oare brûkers in ûnfolslein byld fan konversaasjes krijge)
- Akkount Deaktivearje
+ Dizze keamer is ferfongen en is net mear aktyf.
+ Fier jo wachtwurd yn.
+ Fier in brûkersnamme yn.
+ Account útskeakelje
+ Om troch te gean, fier jo wachtwurd yn:
+ Ferjit alle berjochten dy’t ik ferstjoerd haw wannear myn account útskeakele is (Warskôging: hjirtroch kinne oare brûkers in ûnfolslein byld fan petearen krije)
+ Account deaktivearjeNo besjen
- Om de %1$s thústsjinner brûken te blieuwen mast de betingsten besjen en befestigje.
+ Om de %1$s-thússerver brûke te bliuwen, moatte jo de betingsten lêze en befêstigje.Keamer ferjitteReden: %1$s
- Do binst út %1$s weiballe troch %2$s
+ %2$s hat jo út %1$s ferballe%1$s Brûke
- Skilje…
+ Belje…BrûkersAksje sjen litte
- Do hast neat feroare
- Do hast it keamerûnderwerp fuortsmiten
- %1$s hat it keamerûnderwerp fuortsmiten
- Do hast de keamernamme fuortsmiten
- %1$s hat de keamernamme fuortsmiten
- %1$s hat %2$s der út skopt. Reden: %3$s
- Do hast %1$s fuortskopt. Reden: %2$s
- Do hast hjir fernijd.
+ Jo hawwe neat wizige
+ Jo hawwe it keamerûnderwerp fuortsmiten
+ %1$s hat it petearûnderwerp fuortsmiten
+ Jo hawwe de keamernamme fuortsmiten
+ %1$s hat de petearnamme fuortsmiten
+ %1$s hat %2$s der útskopt. Reden: %3$s
+ Jo hawwe %1$s der útskopt. Reden: %2$s
+ Jo hawwe hjir bywurke.%s is hjir fernijd.
- Do hast skille.
+ Jo hawwe in audiopetear iepene.Beskikbere talen ophelje…Oare beskikbere talenJou it ûnderwerp fan de keamer oanDit is spam
- Der befyne sich gjin triemen yn disse keamer
- TRIEMEN
- Der befynt sich gjin media yn disse keamer
+ Der binne gjin bestannen yn dizze keamer
+ BESTANNEN
+ Der is gjin media yn dizze keamerMEDIAOmdraaie en bysnijeStickerLûdKameraKontakt
- Triem
+ BestânÔfbylding tafoege útIn flater die sich foar wylst it taheaksel ophelle waard.Spring nei it ein
@@ -514,43 +515,43 @@
FideoTaal útsykjeTaal
- Skakel \'integraasjes tastean\' yn Ynstellings yn om dit te dwaan.
- Integraasjes bin útskakele
- Integraasje Behearder
- Integraasje tastean
- Identiteits Tsjinner
- Thús Tsjinner
- Ynloggd as
+ Skeakelje yn Ynstellingen ‘Yntegraasjes tastean’ yn om dit te dwaan.
+ Yntegraasjes binne útskeakele
+ Yntegraasjebehearder
+ Yntegraasjes tastean
+ Identiteitsserver
+ Thússerver
+ Oanmeld asFerstjoereWachtwurd:Authentikaasje
- Disse operaasje hat oanfullende authentikaasje nedich.
-\nFier dyn wachtwurd yn om troch te gean.
+ Dizze operaasje hat oanfoljende autentikaasje nedich.
+\nFier jo wachtwurd yn om troch te gean.Foar it letst sjûn
- Iepenbiere Namme Feroarje
- Iepenbiere Namme
+ Iepenbiere namme wizigje
+ Iepenbiere nammeID
- Sesje informaasje
+ SesjeynformaasjeDatabesparjende modusTastimming jaanIn oare opsje kiezeTastimming jaan
- Dyn sichtberens ynstellings beheare.
- Sichtberens
+ Jo ûntdekynstellingen beheare.
+ UntdekkeJa, ik wol graach helpe!Tillefoan nûmerAlgemienSesjesKeamers mei net lézen berjochten festsetteLilkensskodzje
- De ûntwikkelders modus jout ûnsichtbere funksjes frei, en kin de applikaasje minder stabiel meitsje. Allinich foar ûntwikkelders!
- Ûntwikkelders modus
+ De ûntwikkelersmodus jout ûnsichtbere funksjes frij, en kin de applikaasje minder stabyl meitsje. Allinnich foar ûntwikkelers!
+ UntwikkelersmodusKontakt Opnimme Mei Behearder
- Dit is tapasselik
+ Dit is ûnfoechSkoftsje
- Ôfspylje
+ OfspyljeFerstjoere
- Do brûkst gjin Identiteits Tsjinner
+ Jo brûke gjin identiteitsserverÛnbekende flaterInteractieve Sesje FerifikaasjeDe ferifikaasje is ôfbrutsen.
@@ -558,39 +559,39 @@
De oare partij hat de ferifikaasje ôfbrutsen.
\n%sOanfraach Ôfbrutsen
- Kaai Ferifikaasje
+ KaaiferifikaasjeBrûk ferâldere ferifikaasje.
- Wurd der neat sjen litten\? Net eltse kliïnt kin mei interactieve ferifikaasje oerwei. Brûk ferâldere ferifikaasje.
+ Wurdt der neat toand\? Net elke kliïnt kin mei ynteraktive ferifikaasje oerwei. Brûk ferâldere ferifikaasje.Ferifikaasje oanfraach
- %s wol dyn sesje ferifiearje
- Feilige berjochten mei disse brûker binne ein-oant-ein fersifere, en kin net troch tredde partijen lézen wurde.
- Do hast disse sesje mei sukses ferifieare.
+ %s wol jo sesje ferifiearje
+ Feilige berjochten mei dizze brûker binne end-to-end-fersifere en kinne net troch tredde partijen lêzen wurde.
+ Jo hawwe dizze sesje mei sukses ferifiearre.Ferifieare!Oan it wachtsjen op partner om te befêstigjen …Oanfraach besjenBiometry ynskakeljePIN ynskakelje
- Feiligens ynstelle
+ Beskerming ynstelleTagong befeiligje troch in PIN en biometry te brûken.Tagong befeiligjePIN fergetten\?
- Dyn PIN ynfiere
+ Jo pin-koade ynfierePIN befestigje
- Brûk in PIN foar feiligens
- Te folle flaters, do binst útlogd
+ Brûk in pinkoade foar befeiliging
+ Te folle flaters, jo binne ôfmeldLokaal adres tafoegeIk ha in kopy makkeGraach in kopy meitsje
- Ophâlde
+ StopjeKlear
- Feiligje dyn reservekopy mei in Wachtwurdssin.
- Kaaien mei de hân eksportearje
- (Avanseare)
+ Befeiligje jo reservekopy mei in wachtwurd.
+ Kaaien hânmjittich eksportearje
+ (Avansearre)Reitsje jo fersifere gegevens nea kwytGjin Matrix sesje beskikber
- Graach de wachtwurdssin fuortsmite ast wolst dot ${app_name} in nije herstel kaai foar dy oanmakket.
+ Graach de wachtwurdsin fuortsmite as jo wolle dat ${app_name} in nije werstelkaai foar jo oanmakket.Graach in wachtwurdssin ynfiere
- Wachtwurdssin komt net oerien
+ Wachtwurden komme net oerienWachtwurdssin ynfiereWachtwurdssin befestigjeWachtwurdssin oanmeitsje
@@ -598,8 +599,8 @@
Mislearre%s fuortsmite\?PIN feroarje
- Avanseare
- Avanseare Ynstellings Foar Notifikaasjes
+ Avansearre
+ Avansearre notifikaasjeynstellingenFerjitteFavoriteÚtsykje
@@ -607,11 +608,11 @@
Graach in lân útsykjeLân útsykjeWachtwurden komme net oerien
- Dit emailadres koe net fûn wurde.
- Dit emailadres wurd ol brûkt.
- Dit telefoannûmer wurd ol brûkt.
- Emails en telefoannûmers
- Dyn wachtwurd is fernijd
+ Dit e-mailadres koe net fûn wurde.
+ Dit e-mailadres is al yn gebrûk.
+ Dit telefoannûmer is al yn gebrûk.
+ E-mailadressen en telefoannûmers
+ Jo wachtwurd is bywurkeIt wachtwurd is net jildichFernijen fan it wachtwurd is mislearreWachtwurd Fernije
@@ -625,46 +626,46 @@
ÛnderwerpWa kin de skiednis lêze\?Lokaal Adres
- Eltsenien dyt in link nei disse keamer hat, mar gjin gasten
+ Elkenien dy’t in keppeling nei dizze keamer hat, mar gjin gastenPriveeIepenbier
- Eltsenien kin by disse keamer oankopje, leden kinne don akseptearje as ôfslaan
- Eltsenien dyt in link nei disse keamer hat, sels gasten
- Do hast gjin brûkers negearre
+ Elkenien kin by dizze keamer oanklopje, leden kinne dan akseptearje of wegerje
+ Elkenien dy’t in keppeling nei dizze keamer hat, ynklusyf gasten
+ Jo hawwe gjin brûkers negearreNegearre brûkers
- Stim & Fideo
+ Stim & fideoAvansearre ynstellingenOanpaste en avansearre ynstellingen
- Akkount Tafoegje
- Oanpaste Ynstellingen.
+ Account tafoegje
+ Oanpaste ynstellingen.YnskeakeljeYnskeakelje
- Notifikaasjes binne ynskeakele foar dyn akkount.
- Akkount Ynstellingen.
+ Notifikaasjes binne foar jo account ynskeakele.
+ Accountynstellingen.Ynstellingen Iepenje
- Notifikaasjes binne ynskeakele yn de systeem ynstellingen.
+ Notifikaasjes binne ynskeakele yn de systeemynstellingen.Systeem Ynstellingen.Tests ÚtfiereTelefoannûmersE-mailadressenWachtwurd befêstigje
- Applikaasje informaasje yn de systeem ynstellingen sjen litte.
- Applikaasje informaasje
+ Applikaasjeynformaasje yn de systeemynstellingen toane.
+ ApplikaasjeynformaasjeTelefoannûmer tafoegje
- Der is gjin telefoannûmer tafoege oan syn akkount
+ Der is gjin telefoannûmer oan jo account tafoegeE-mailadres tafoegjeEmailYnstellingen
- Petear Ferlitte
+ Petear ferlitteDirekt PetearAlle berjochtenAlle berjochten (lûd)Ynstellingen
- Ynstellingen feroarje
- Keamer rjochten
- Do hast de tsjinner ACLs foar dizze keamer feroare.
- %s hat de server-ACL\'s foar dizze keamer feroare.
- Do hast dyn profyl ôfbylding feroare
+ Ynstellingen wizigje
+ Petearrjochten
+ Jo hawwe de server-ACL’s foar dizze keamer wizige.
+ %s hat de server-ACL’s foar dizze keamer wizige.
+ Jo hawwe jo profylôfbylding wizigeKoe de suggestje net ferstjoere (%s)Dankewol, it ferstjoeren fan de suggestje is slaggeSkriuw jo suggestje hjir
@@ -682,13 +683,13 @@
KeamersWolkom thús!Útnûge troch %s
- Hat jo in ûtnoeging stjoerd
+ Hat jo in útnûging stjoerdGean in keamer binnen om de app te brûken.Opnij probearjeFeroarjeBrûkers komme net oerienKaaien komme net oerien
- Der is en ûnjildich berjocht ûntfong
+ Der is in ûnjildich berjocht ûntfongenDe sesje hat ûnferwachts in berjocht ûntfongDe SAS kaam net oerienDe sesje wit neat fan dy transaksje
@@ -700,10 +701,10 @@
Back-up FuortsmiteKoe de back-up net fuortsmite (%s)Back-up oan it fuortsmiten…
- Back-up werom sette:
+ Reservekopy wurdt wersteld:Binne jo der wis fan\?Ferfange
- As bestân opslaan
+ As bestân bewarjeDieleWachtwurdssin is net sterk genôchSlagge!
@@ -713,7 +714,7 @@
Gjin brûkersKeamersMinsken
- Thús
+ StartskermfoarbyldFoarbyldOanmeitsje
@@ -741,7 +742,7 @@
Hiel lytsGrutte fan it lettertypeIk
- Nije Útnoeging
+ Nije útnûgingNij BerjochtKeamerNij Evenemint
@@ -758,35 +759,35 @@
%d útnûging
- %d útnoegingen
+ %d útnûgingen%d keamer%d keamersTyp hjir…
- De tsjinner stiet al yn de list
- Kin dizze tsjinner, as de rômte list fan dizze tsjinner net fine
- Fier de namme yn fan de tsjinner dy jo ûntdekke wolle.
- In nije tsjinner tafoegje
- Jo tsjinner
- Alle keamers op %s tsjinner
- Tsjinner namme
+ Dizze server stiet al yn de list
+ Kin dizze server of de keamerlist fan dizze server net fine
+ Fier de namme yn fan de server dy’t jo ûntdekke wolle.
+ In nije server tafoegje
+ Jo server
+ Alle petearen op server %s
+ ServernammeKeamer hat ûnbekend sesjes
- Ik befêstigje dat de kaaien oerien komme
+ Ik befêstigje dat de kaaien oerienkommeAs dit net oerienkomt, kin de feiligens fan jo kommunikaasje kompromittearre wêze.
- Befêstigje troch dit te fergelykjen mei Brûkers Ynstellingen fan jo oare sesje:
- Fan de swarte list ôf helje
- Sesje ferifiearre
- Op de swarte list set
+ Befêstigje troch dit te fergelykjen mei de Brûkersynstellingen fan jo oare sesje:
+ Deblokkearringslist
+ Sesje ferifiearje
+ BlokkearreFerifiearre
- Net Ferifiearre
- Ûntsiferings flater
- Sesje ID
+ Net ferifiearre
+ Untsiferingsflater
+ Sesje-IDAlgoritmeTemaOersjoch
- As haad adres ynstelle
+ As haadadres ynstelleNij adres (bygelyks #foo:matrix.org)Jo moatte útlogge om fersifering ynskeakelje te kinnen.Ein-oan-Ein fersifering is ynskeakele
@@ -800,72 +801,72 @@
PublisearjeKeamer tagongWa hat tagong ta dizze keamer\?
- Keamer Skiednis Lêsberheid
+ Tagong ta de petearskiednisKeamer TagongDizze keamer yn it keamer oersjoch sjen litte
- Tagong en sichtberens
+ Tagonklikheid en sichtberheidLege prioriteitStandert komprimearingMediaIn ativaasje koade ynfiereTelefoan nûmerBinne jo der wis fan dat jo de %1$s %2$s fuort smite wolle\?
- E-mail adressen en telefoan nûmers dy oan jo Matrix account keppele binne behearre
+ E-mailadressen en telefoannûmers dy’t oan jo Matrix-account keppele binne beheareDer die harren in flater foar wylst jo e-mail adres ferifiearre waard.
- Hjoeddeistich wachtwurd
+ Aktuele wachtwurd%1$s @ %2$sMyn account deaktivearjeAccount deaktivearjeBehearreBerjochten mei enter ferstjoereTrilje wannear jo neamd wurde
- Account eveneminten sjen litte
- Sjen litte wannear oft minsken de keamer yn kaam binne, as der út gien binne
- Brûk it /confetti kommando as ferstjoer in berjocht mei ❄️ as 🎉
- Chat effekten sjen litte
- Sjen kinne dat berjochten lêzen binne
+ Accountbarrens toane
+ Toane wannear oft minsken de keamer yn kaam binne, of der útgien binne
+ Brûk it kommando /confetti of stjoer in berjocht mei ❄️ of 🎉
+ Chateffekten toane
+ Lêsbefêstigingen toaneLit oare minsken witte dat jo oan it typen binne.
- Typ notifikaasjes ferstjoere
- Tiid stimpels foar alle berjochten sjen litte
- Fersifering Kaai Behear
+ Typenotifikaasjes ferstjoere
+ Tiidstimpels foar alle berjochten toane
+ Behear fan kryptografyske kaaienFersiferingOare ynstellingenNotifikaasjes
- Brûkers ynstellingen
- olm ferzje
+ Brûkersynstellingen
+ olm-ferzjeFerzjeEftergrûn Syngronisaasje ModusOptimalisearje foar batterij gebrûkWannear ik útnûge wurd foar in keamer
- Stille Notifikaasjes Ynstelle
- • Notifikaasjes befetsje allinich metadata
+ Stille notifikaasjes ynstelle
+ • Notifikaasjes befetsje allinnich metadata• Notifikaasjes wurde mei Firebase Cloud Messaging ferstjoerdDizze applikaasje hat tastimming nedich om op de eftergrûn te startenRestriksjes útskeakelje
- • Notifikaasje sil de ynhâld fan it berjocht net sjen litte
- LED kleur útsykje, triljen, lûd…
- Lûde Notifikaasjes Ynstelle
+ • Notifikaasjes sille de ynhâld fan it berjocht net toane
+ LED-kleur, triljen, lûd…
+ Lûde notifikaasjes ynstelleIt skerm foar 3 sekonden ynskeakeljeNotifikaasjes foar dizze sesje ynskeakelje
- Notifikaasjes foar dit account ynskeakelje
+ Notifikaasjes foar dizze account ynskeakeljeNotifikaasje lûdGewoanTsjinst Starte
- Notifikaasje Tsjinst
+ NotifikaasjetsjinstYnstellingen KontrolearjeSesje Ynstellingen.Oan it útfieren… (%1$d of %2$d)
- Profyl Ôfbylding
+ ProfylôfbyldingFerzje %sFerzje
- ÚTNOEGINGEN
+ ÚTNÛGINGENLEGE PRIORITEITKEAMERSFAVORITENOERSJOCHKEAMERSGjin resultaten
- Delheljen Ôfbrekke
+ Download annulearjeMinskenKeamer DetailsBestannen
@@ -873,73 +874,73 @@
ÚTNÛGEPermisjes feroarjeKeamer namme feroarje
- Skiednis sichtberens feroarje
+ Sichtberens skiednis wizigjeKeamer fersifering ynskeakeljeHaad adres fan de keamer feroarjeKeamer ôfbylding feroarjeElkenien op de hichte bringe
- Berjochten dy troch oaren ferstjoerd binne fuort smite
+ Berjochten dy’t troch oaren ferstjoerd binne fuortsmiteBrûkers ferbaljeBrûkers der út skoppeBrûkers útnûgjeBerjochten ferstjoereStandert rolPermisjes
- Negearre
- Útlogge
+ Negearje
+ OfmeldeNet fertrouweFertrouweBestân net fûnNet ferstjoerde berjochten fuortsmiteNet ferstjoerde berjochten opnij ferstjoere
- Alles ôfbrekke
+ Alles annulearjeAlles opnij ferstjoere
- Berjocht net ferstjoerd. %1$s as %2$s no\?
- Ferbining mei de tsjinner is ferlern.
+ Berjochten net ferstjoerd. No %1$s of %2$s\?
+ Ferbining mei de server is ferbrutsen.In reaksje ferstjoere (net fersifere)…In fersifere reaksje ferstjoere…In berjocht ferstjoere (net fersifere)…In fersifere berjocht ferstjoere…
- Allinich Matrix brûkers
- Útnoeging ôfbrekke
- Net mear negearre
+ Allinnich Matrix-brûkers
+ Utnûging annulearje
+ Net mear negearjeBrûker negearre
- Brûker net mear negearre
- Negearre
+ Brûker net mear negearje
+ NegearjeDegradearje
- Jo sels degradearje\?
- Sesje list sjen litte
- Neame
- In administrator meitsje
- In moderator meitsje
- Werom sette nei gewoane brûker
- Der út skoppe
+ Josels degradearje\?
+ Sesjelist toane
+ Fermelde
+ Behearder meitsje
+ Moderator meitsje
+ Weromsette nei gewoane brûker
+ Der útskoppeFerbalje
- Útnoeging ôfbrekke
- Útnûgje
+ Utnûging annulearje
+ UtnûgjeSESJES
- Direkte Berjochten
+ Direkte berjochtenSKILJE
- ADMINISTRAASJE ARK
+ BEHEARDERSARK%1$s %2$s lyn
- %1$s no
- Ôfwêzich
+ no %1$s
+ OfwêzichOfflineOnlineBinne jo der wis fan dat jo de keamer ferlitte wolle\?Keamer ferlitte
- 1 lid
+ 1 dielnimmerSyngronisearje…
- Dochs Trochgean
- Yn downloads opslaan\?
- Opslein
- In foto as fideo meitsje
+ Dochs trochgean
+ Yn downloads bewarje\?
+ Bewarre
+ In foto of fideo meitsjeKin gjin fideo opnimmeBelje%1$dm %2$ds%d s
- It delheljen ôfbrekke\?
- Opnij de fersifering kaaien fan jo oare sesjes opfreegje.
+ Downloaden annulearje\?
+ De fersiferingskaaien fan jo oare sesjes opnij opfreegje.Registrearjen mislearre: e-mail-eigendomsflaterKin net oanmelde: netwurkflaterURL moat mei http[s]:// begjinne
@@ -955,45 +956,45 @@
KoptelefoanLûdsprekkerProbearje ris %s te brûken
- Stimlûd ferstjoere
+ Spraakberjocht ferstjoereFideo-oprop begjinneSpraakoprop begjinne
- URL identiteitsserver
- API URL thússerver
- URL thússerver
+ Identiteitsserver-URL
+ Thússerver API-URL
+ Thússerver-URLIt oanjaan fan in flater yn dizze applikaasje is net slagge (%s)It oanjaan fan in flater yn dizze applikaasje is slaggeKeamer binnen geanFoarútgong (%s%%)
- Dizze applikaasje is de lêste kear fêstrûn. Wolle jo miskien it skerm iepenje om in flater yn dizze applikaasje oan te jaan\?
- It liket der op dat jo út lilkens mei jo telefoan skodzje. Wolle jo miskien it skerm iepenje om in flater yn dizze applikaasje oan te jaan\?
+ Dizze applikaasje is de lêste kear fêstrûn. Wolle jo dit melde\?
+ It liket der op dat jo út lilkens mei jo telefoan skodzje. Wolle jo in probleem melde\?Romten
- Utnûgingen
- Alle keamers yn it keameroersjoch toane, ek keamers mei ynhâld foar folwoeksenen.
- Keamers mei ynhâld foar folwoeksenen toane
- Keameroersjoch
+ Utnûge persoanen
+ Alle petearen yn de list toane, ek petearen mei ynhâld foar folwoeksenen.
+ Petearen mei ynhâld foar folwoeksenen toane
+ PetearlistGjin resultaten mear
- Oanrekommandearre Keamers
+ Oanrekommandearre petearenNije wearde
- Omskeakelje
+ WikseljeKopiearjeJo kinne josels net beljeJo meie gjin konferinsje starte
- Jo meie gjin konferinsjepetear yn dizze keamer starte
- Petearen starte
- Om dizze aksje út te fieren, skeakelje dan de kameratastimming yn fan de systeemynstellingen út.
+ Jo misse it rjocht om in gearkomste yn dit petear te starten
+ Starte mei chatten
+ Skeakelje om dizze aksje út te fieren fan de systeemynstellingen út de kameratastimming yn.Jo hawwe end-to-end-fersifering ynskeakele.%1$s hat end-to-end-fersifering ynskeakele.
- %1$s hat opkeard dat gasten dizze keamer binnen gean kinne.
- Jo hawwe opkeard dat gasten dizze keamer binnen gean kinne.
- %1$s hat opkeard dat gasten dizze keamer binnen gean kinne.
- Jo hawwe opkeard dat gasten dizze keamer binnen gean kinne.
+ %1$s hat opkeard dat gasten dit petear binnen gean kinne.
+ Jo hawwe opkeard dat gasten dit petear binnen gean kinne.
+ %1$s hat opkeard dat gasten dit petear binnen gean kinne.
+ Jo hawwe opkeard dat gasten dit petear binnen gean kinne.Jo hawwe gasten tastien om hjir binnen te gean.%1$s hat gasten tastien om hjir binnen te gean.Jo hawwe gasten tastien om dizze keamer binnen te gean.%1$s hat gasten tastien om dizze keamer binnen te gean.Systeemstandert
- Jo hawwe end-to-end-fersifering ynskeakele (net erkend algoritme %1$s).
+ Jo hawwe end-to-end-fersifering ynskeakele (ûnbekend algoritme %1$s).%1$s en 1 oar%1$s en %2$d oaren
@@ -1005,39 +1006,39 @@
%1$s, %2$s, %3$s en %4$s%1$s, %2$s en %3$s%1$s en %2$s
- Keamerútnûging
+ PetearútnûgingIt is no net mooglik om in lege keamer wer binnen te gean.
- Jo hawwe de útnûging nei %1$s oannaam
- %1$s hat de útnûging nei %2$s oannaam
- Jo hawwe de útnûging nei %1$s ynlutsen
- %1$s hat de útnûging nei %2$s wer ynlutsen
+ Jo hawwe de útnûging nei %1$s akseptearre
+ %1$s hat de útnûging oan %2$s akseptearre
+ Jo hawwe de útnûging oan %1$s ynlutsen
+ %1$s hat de útnûging oan %2$s wer ynlutsenJo hawwe de útnûging nei %1$s om de keamer binnen te gean wer ynlutsen%1$s hat de útnûging nei %2$s om de keamer binnen te gean wer ynlutsenJo hawwe %1$s útnûge%1$s hat %2$s útnûge
- Jo hawwe in útnûging nei %1$s stjoerd om de keamer binnen te gean
- %1$s hat in útnûging nei %2$s stjoerd om de keamer binnen te gean
- %1$s hat de keamerôfbylding fuortsmiten
- VoIP-konferinsje ôfrûn
+ Jo hawwe in útnûging nei %1$s stjoerd om by de keamer oan te sluten
+ %1$s hat in útnûging nei %2$s stjoerd om mei it petear mei te dwaan
+ %1$s hat de keameravatar fuortsmiten
+ VoIP-konferinsje foltôgeVoIP-konferinsje begûnJo hawwe in VoIP-konferinsje oanfrege%1$s hat in VoIP-konferinsje oanfrege
- 🎉 Alle servers bin ferballe fan it meidwaan! Dizze keamer kin net mear brûkt wurde.
- • Tsjinners dy oerien komme mei IP adressen binne no ferballe.
- • Tsjinners dy oerien komme mei IP adressen binne no tastien.
- • Tsjinners dy oerien komme mei %s binne út de list mei tastiene tsjinners wei helle.
- • Tsjinners dy oerien komme mei %s binne no tastien.
- • Tsjinners dy oerien komme mei %s binne út de list mei ferballe tsjinners wei helle.
- • Tsjinners dy oerien komme mei %s binne no ferballe.
- • Tsjinners dy oerien komme mei IP adressen binne ferballe.
- • Tsjinners dy oerien komme mei IP adressen binne tastien.
- • Tsjinners dy oerien komme mei %s binne ferballe.
- Jo hawwe de keamerôfbylding fuortsmiten
- Jo hawwe de keamer ôfbylding feroare
- %1$s hat de keamer ôfbylding feroare
- %1$s hat syn profylfoto feroare
- Jo hawwe %1$s harren útnûging ynlutsen. Reden: %2$s
- %1$s hat %2$s harren útnûging ynlutsen. Reden: %3$s
+ 🎉 Alle servers binne ferballe fan dielname! Dizze keamer kin net mear brûkt wurde.
+ • Servers dy’t oerienkomme mei IP-adressen binne no ferballe.
+ • Servers dy’t oerienkomme mei IP-adressen binne no tastien.
+ • Servers dy’t oerienkomme mei %s binne út de list mei tastiene servers fuorthelle.
+ • Servers dy’t oerienkomme mei %s binne no tastien.
+ • Servers dy’t oerienkomme mei %s binne út de list mei ferballe servers fuorthelle.
+ • Servers dy’t oerienkomme mei %s binne no ferballe.
+ • Servers dy’t oerienkomme mei IP-adressen binne ferballe.
+ • Servers dy’t oerienkomme mei IP-adressen binne tastien.
+ • Servers dy’t oerienkomme mei %s binne ferballe.
+ Jo hawwe de keameravatar fuortsmiten
+ Jo hawwe de keamerôfbylding wizige
+ %1$s hat de keamerôfbylding wizige
+ %1$s hat syn profylfoto wizige
+ Jo hawwe de útnûging fan %1$s ynlutsen. Reden: %2$s
+ %1$s hat de útnûging fan %2$s ynlutsen. Reden: %3$sJo hawwe de útnûging foar %1$s akseptearre. Reden: %2$s%1$s hat de útnûging foar %2$s akseptearre. Reden: %3$sJo hawwe %1$s net mear ferballe
@@ -1052,17 +1053,146 @@
Kies in telefoannûmer. Letter kinne jo derfoar kieze om minsken jo fine te litten fia dit nûmer.Kies in e-mailadres om te brûken foar accountwerstel. Letter kinne jo derfoar kieze om minsken jo fine te litten fia jo e-mailadres.Tebek nei it oanmeldingsskerm
- E-mail foar opnij ynstellen ferstjoere
+ E-mailberjocht foar opnij ynstellen ferstjoereYntsjinjeAccount oanmeitsjeOanmelde mei unike oanmeldingFerstjoere neiDepublisearje
- Start audiopetear
- Start fideopetear
+ Audiopetear starte
+ Fideopetear starteMissende tastimmingenRomtenMear ynfoOpnij ynstelleSlute
+ Jo binne op dit stuit yn dit petear
+ Jo aktuele pinkoade wizigje
+ Aktuele taal
+ Aktuele sesje
+ Allinnich werjeftenûmer fan oantal berjochten yn in ienfâldige notifikaasje.
+ Details as keamernamme en berjochtynhâld toane.
+ Ynhâld yn notifikaasjes toane
+ Besjoch jo ynstellingen om pushnotifikaasjes yn te skeakeljen
+ Pushnotifikaasjes binne útskeakele
+ Konfiguraasje notifikaasjes
+ Notifikaasjes
+ Dôvje
+ Allinnich fermeldingen
+ Alle berjochten
+ Alle berjochten (drok)
+ Accountynstellingen
+ Jo kinne notifikaasjes beheare yn %1$s.
+ Petearen mei miste notifikaasjes fêstsette
+ Notifikaasjedoelen
+ Opropnotifikaasjes konfigurearje
+ • Notifikaasjes befetsje meta- en berjochtgegevens
+ Notifikaasjetsjinst automatysk opnij starte
+ Notifikaasjetsjinst is net aktyf.
+\nProbearje de app opnij te starten.
+ Notifikaasjetsjinst is aktyf.
+ Notifikaasjes binne foar dizze sesje ynskeakele.
+ Problemen mei notifikaasjes oplosse
+ Standert notifikaasjes
+ Gegevens wiskje
+ Gegevens wiskje
+ Alle gegevens wiskje
+ Persoanlike gegevens wiskje
+ Skiednis wiskje
+ Om Matrix-appbehear te werstellen
+ Mediabuffer wiskje
+ Buffer wiskje
+ Yntegraasjes beheare
+ Yntegraasjes
+ Te finen e-mailadressen
+ E-mailnotifikaasjes ynskeakelje foar %s
+ Foegje in e-mailadres ta oan jo Matrix-account, om e-mailnotifikaasjes te ûntfangen
+ E-mailnotifikaasje
+ Der is gjin e-mailadres oan jo account tafoege
+ Werjeftenamme
+ Wiziget jo werjeftenamme allinnich yn de aktuele keamer
+ Wiziget jo werjeftenamme
+ Jo werjeftenamme
+ Befettet wizigingen yn avatar en werjeftenamme.
+ Befettet útnûging/meidwaan/ferlitten/skopt/ferballe-barrens en wizigingen avatar/werjeftenamme.
+ ${app_name} sil periodyk op de eftergrûn syngronisearje (konfigurearber).
+\nDit hat in negative ynfloed op jo batterij- en datagebrûk. Der sil in melding toand wurde ta ynformaasje.
+ Myn werjeftenamme
+ Berjochten dy’t myn werjeftenamme befetsje
+ 🔐️ Doch mei my mei op ${app_name}
+ Ah goeie, praat mei my op ${app_name}: %s
+ Freonen útnûgje
+ Lege prioriteit
+ Favoryt
+ Gjin
+ Dôvje
+ Allinnich fermeldingen
+ Spacerjochten
+ Foegje in identiteitsserver ta yn de ynstellingen om dit te dwaan.
+ Oproppen
+ %1$s hat it haadadres foar dit petear fuortsmiten.
+ Jo hawwe it haadadres foar dit petear ynsteld op %1$s.
+ %1$s hat it haadadres foar dit petear ynsteld op %2$s.
+ Jo hawwe %1$s as petearadres tafoege en %2$s fuortsmiten.
+ %1$s hat %2$s as petearadres tafoege en %3$s fuortsmiten.
+
+ Jo hawwe %1$s as petearadres fuortsmiten.
+ Jo hawwe %1$s as petearadresens fuortsmiten.
+
+
+ %1$s hat %2$s as petearadres fuortsmiten.
+ %1$s hat %2$s as petearadressen fuortsmiten.
+
+
+ Jo hawwe %1$s as petearadres tafoege.
+ Jo hawwe %1$s as petearadressen tafoege.
+
+
+ %1$s hat %2$s as petearadres tafoege.
+ %1$s hat %2$s as petearadressen tafoege.
+
+ Jo hawwe de útnûging foar %1$s ynlutsen. Reden: %2$s
+ %1$s hat de útnûging foar %2$s ynlutsen. Reden: %3$s
+ Jo hawwe %1$s in útnûging foar it petear stjoerd. Reden: %2$s
+ %1$s hat %2$s in útnûging stjoerd foar in petear. Reden: %3$s
+ Jo hawwe %1$s ferballe. Reden: %2$s
+ %1$s hat %2$s ferballe. Reden: %3$s
+ Jo hawwe de ferballing fan %1$s opheven. Reden: %2$s
+ %1$s hat %2$s ûnferballe. Reden: %3$s
+ Jo hawwe de útnûging wegere. Reden: %1$s
+ %1$s hat de útnûging wegere. Reden: %2$s
+ Inisjele syngronisaasje:
+\nKryptografy ymportearje
+ It apparaat fan de ôfstjoerder hat gjin kaaien foar dit berjocht stjoerd.
+ %1$s fan %2$s nei %3$s
+ %1$s hat it machtigingsnivo fan %2$s oanpast.
+ Jo hawwe it machtigingsnivo fan %1$s oanpast.
+ Moderator
+ Fideokonferinsje oanpast troch %1$s
+ Jo hawwe de fideokonferinsje beëinige
+ Fideokonferinsje beëinige troch %1$s
+ Jo hawwe in fideokonferinsje start
+ Fideokonferinsje start troch %1$s
+ Jo hawwe de widget %1$s oanpast
+ %1$s hat de widget %2$s oanpast
+ Jo hawwe de widget %1$s fuortsmiten
+ %1$s hat de widget %2$s fuortsmiten
+ Jo hawwe de widget %1$s tafoege
+ %1$s hat de widget %2$s tafoege
+ Jo hawwe jo profyl %1$s bywurke
+ %1$s hat syn/har profyl %2$s bywurke
+ (avatar is ek wizige)
+ Belied
+ Gjin belied troch de identiteitsserver opjûn
+ Identiteitsserverbelied ferstopje
+ Identiteitsserverbelied toane
+ Privacybelied
+ Privacybelied
+ Samar wat
+ ${app_name} Android
+ Iepenbiere namme
+ De iepenbiere namme fan in sesje is sichtber foar minsken mei wa’t jo kommunisearje
+ Iepenbiere namme (sichtber foar minsken mei wa’t jo kommunisearje)
+ Iepenbiere namme
+ Der is al in gearkomst oan de gong!
\ No newline at end of file
diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml
index 1d9214f926..3c22d2b47f 100644
--- a/vector/src/main/res/values-hu/strings.xml
+++ b/vector/src/main/res/values-hu/strings.xml
@@ -68,7 +68,8 @@
Induló szinkronizáció:
\nSzobák betöltéseInduló szinkronizáció:
-\nCsatlakozott szobák betöltése
+\nBeszélgetések betöltése
+\nHa sok szobában vagy jelen sokáig tarthatInduló szinkronizáció:
\nMeghívott szobák betöltéseInduló szinkronizáció:
@@ -3010,4 +3011,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró
ElérhetetlenKapcsolat nélkülKapcsolódva
+ Matrix szerver kiválasztása
+ A matrix szervert nem sikerül elérni ezen az URL-en: %s. Ellenőrizd a kapcsolatodat vagy add meg a matrix szervert kézzel.
+ Értesítések figyelése
\ No newline at end of file
diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml
index c087680eb4..68356e5b2c 100644
--- a/vector/src/main/res/values-in/strings.xml
+++ b/vector/src/main/res/values-in/strings.xml
@@ -334,7 +334,7 @@ Ijinkan akses lewat halaman selanjutnya untuk menemukan pengguna ${app_name} yan
Tinggalkan ruang iniKeluarkan dari ruang iniLarang
- Hapus Larangan
+ Hapus CekalanSetel ulang ke user biasaJadikan moderatorJadikan admin
@@ -403,7 +403,7 @@ Ijinkan akses lewat halaman selanjutnya untuk menemukan pengguna ${app_name} yan
Alasan laporan konten iniApa Anda ingin menyembunyikan seluruh pesan dari pengguna ini\?
\n
-\nHarap diperhatikan bahwa tindakan ini akan me-restart aplikasi dan mungkin akan memakan waktu beberapa saat.
+\nHarap diperhatikan bahwa tindakan ini akan memulai ulang aplikasi dan mungkin akan membutuhkan waktu beberapa saat.Batalkan UnggahanBatalkan UnduhanCari
@@ -522,7 +522,7 @@ Ijinkan akses lewat halaman selanjutnya untuk menemukan pengguna ${app_name} yan
Perintah tak dikenal: %sTunjukkan tindakanLarang user dengan id berikut
- Cabut larangan pengguna dengan id berikut
+ Menghapus cekalan pengguna dengan id berikutTentukan tingkat kuasa seorang penggunaUndang pengguna dengan id berikut bergabung ke ruang iniGabung ke ruangan dengan alamat berikut
@@ -764,7 +764,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Kata sandi Anda telah diperbaharuiTunjukkan semua pesan dari %s\?
\n
-\nMohon perhatikan bahwa tindakan ini akan me-restart aplikasi dan mungkin akan memakan waktu.
+\nMohon perhatikan bahwa tindakan ini akan me-restart aplikasi dan mungkin akan membutuhkan waktu.Apa benar Anda ingin menyingkirkan sasaran pemberitahuan ini?Apa benar Anda ingin menyingkirkan %1$s %2$s?Pilih negara
@@ -1237,26 +1237,27 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Bersihkan antrian kirimanMengirim pesan…Pesan terkirim
- Sinkronisasi Awal:
-\nMengimpor Data Akun
- Sinkronisasi Awal:
-\nMengimpor Komunitas
+ Sinkronisasi awal:
+\nMengimpor data akun
+ Sinkronisasi awal:
+\nMengimpor komunitasSinkronisasi Awal:
-\nMengimpor Ruangan yang Ditinggalkan
- Sinkronisasi Awal:
-\nMengimpor Ruangan yang Diundang
- Sinkronisasi Awal:
-\nMengimpor Ruangan Tergabung
- Sinkronisasi Awal:
-\nMengimpor Ruangan
+\nMengimpor ruangan yang ditinggalkan
+ Sinkronisasi awal:
+\nMengimpor ruangan yang diundang
+ Sinkronisasi awal:
+\nMemuat obrolan Anda
+\nJika Anda telah bergabung dengan banyak ruangan, ini mungkin membutuhkan waktu yang cukup lama
+ Sinkronisasi awal:
+\nMengimpor ruanganSinkronisasi awal:
-\nMengimpor crypto
+\nMengimpor kripto%1$s mengubah tingkat daya %2$s.
- Sinkronisasi Awal:
+ Sinkronisasi awal:
\nMengimpor akun…
- Sinkronisasi Awal:
+ Sinkronisasi awal:
\nMengunduh data…
- Sinkronisasi Awal:
+ Sinkronisasi awal:
\nMenunggu respons server…Ruangan kosong (tadi adalah %s)
@@ -1331,7 +1332,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.• Server yang cocok dengan IP literal sekarang diizinkan.• Server yang cocok dengan %s telah dihapus dari daftar izin.• Server yang cocok dengan %s sekarang telah diizinkan.
- • Server yang cocok dengan %s dihapus dari daftar larangan.
+ • Server yang cocok dengan %s dihapus dari daftar cekalan.Anda mengubah ACL server untuk ruangan ini.%s mengubah ACL server untuk ruangan ini.• Server yang cocok dengan IP literal diizinkan.
@@ -1872,7 +1873,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Semua kunci tercadangkanSiapkan Cadangan Aman
- Mencadangkan kunci Anda. Ini mungkin memakan beberapa menit…
+ Mencadangkan kunci Anda. Ini mungkin membutuhkan beberapa menit…Kelola di Cadangan KunciKunci pesan aman baruCadangan Kunci Baru
@@ -1929,11 +1930,11 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Mengambil versi cadangan…Anda mungkin kehilangan akses ke pesan Anda jika Anda keluar atau kehilangan perangkat ini.Yakin\?
- Kunci enkripsi Anda sekarang sedang dicadangkan di latar belakang ke homeserver Anda. Pencadangan awal dapat memakan waktu beberapa menit.
+ Kunci enkripsi Anda sekarang sedang dicadangkan di latar belakang ke homeserver Anda. Pencadangan awal dapat membutuhkan waktu beberapa menit.Pencadangan DimulaiKesalahan tidak terdugaKunci Pemulihan
- Membuat Kunci Pemulihan menggunakan frasa sandi, proses ini bisa memakan beberapa detik.
+ Membuat Kunci Pemulihan menggunakan frasa sandi, proses ini bisa membutuhkan beberapa detik.Bagikan kunci pemulihan ke…Mohon membuat salinanBerhenti
@@ -2354,7 +2355,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Tingkatkan ruangan publikPeningkatan dibutuhkanTingkatkan
- Mohon sabar, ini mungkin memakan waktu yang lama.
+ Mohon sabar, ini mungkin membutuhkan waktu yang lama.Bergabung ke ruangan yang digantiSaat ini orang-orang mungkin tidak dapat bergabung ke ruangan privat yang Anda buat.
\n
@@ -2571,7 +2572,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Anda tidak dapat mengakses pesan ini karena Anda telah diblokir oleh pengirimKarena enkripsi ujung-ke-ujung, Anda mungkin harus menunggu untuk pesan dari seseorang untuk datang karena kunci enkripsinya tidak dikirim secara benar ke Anda.Tidak Dapat Mendekripsi
- Menunggu untuk pesan ini, mungkin memakan beberapa waktu
+ Menunggu untuk pesan ini, mungkin membutuhkan beberapa waktuAnda tidak dapat mengakses pesan iniAtur avatarAnda berhasil mengubah pengaturan ruangan
@@ -2629,7 +2630,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Undangan terkirim ke %1$s dan %2$sUndangan terkirim ke %1$s🔐️ Bergabung dengan saya di ${app_name}
- Hi, bicara dengan saya di ${app_name}: %s
+ Halo, bicara dengan saya di ${app_name}: %sUndang temanUndang PenggunaMengundang pengguna…
@@ -2767,7 +2768,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Anda telah selesai!Kunci pemulihan AndaMengatur pemulihan.
- Ini mungkin memakan beberapa detik, mohon sabar.
+ Ini mungkin membutuhkan beberapa detik, mohon sabar.Masukkan frasa keamanan yang Anda tahu, digunakan untuk mengamankan rahasia di server Anda.Jangan menggunakan kata sandi Akun Anda.Masukkan %s Anda lagi untuk mengkonfirmasinya.
@@ -2953,4 +2954,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Tidak TersediaOfflineOnline
+ Pilih homeserver
+ Tidak dapat menjangkau homeserver di URL %s. Silakan periksa tautan Anda atau pilih sebuah homeserver secara manual.
+ Mendengarkan notifikasi
\ No newline at end of file
diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml
index 663e186ddf..ca3b1dd19e 100644
--- a/vector/src/main/res/values-it/strings.xml
+++ b/vector/src/main/res/values-it/strings.xml
@@ -69,13 +69,14 @@
Sincronizzazione iniziale:
\nImportazione stanzeSincronizzazione iniziale:
-\nImportazione stanze partecipate
+\nCaricamento delle conversazioni
+\nSe sei dentro a molte stanze, potrebbe volerci un po\'
Sincronizzazione iniziale:
\nImportazione stanze con invitoSincronizzazione iniziale:
-\nImportazione stanze lasciate
+\nImportazione stanze abbandonate
Sincronizzazione iniziale:
-\nImportazione comunità
+\nImportazione delle comunità
Sincronizzazione iniziale:
\nImportazione dati account%s ha aggiornato questa stanza.
@@ -3058,4 +3059,7 @@
Non disponibileOfflineOnline
+ Scegli homeserver
+ Impossibile raggiungere un homeserver all\'URL %s. Controlla il link o scegli manualmente un homeserver.
+ Ascolto di notifiche
\ No newline at end of file
diff --git a/vector/src/main/res/values-lv/strings.xml b/vector/src/main/res/values-lv/strings.xml
index bde0afd0eb..7def907952 100644
--- a/vector/src/main/res/values-lv/strings.xml
+++ b/vector/src/main/res/values-lv/strings.xml
@@ -184,7 +184,8 @@
Sākotnējā sinhronizācija:
\nImportē istabas, uz kurām uzaicinātsSākotnējā sinhronizācija:
-\nImportē istabas, kurās ieiets
+\nLādē jūsu sarunas
+\nJa esat pievienojies daudzām istabām, tas var aizņemt kādu laiku.
Sākotnējā sinhronizācija:
\nImportē istabasSākotnējā sinhronizācija:
@@ -333,7 +334,7 @@
ReģistrētiesPierakstītiesIzrakstīties
- Bāzes servera URL adrese
+ Mājasservera URL adreseIdentitifikācijas servera URL adreseMeklētSākt jaunu čatu
@@ -398,9 +399,9 @@
Reģistrēšanās ar epastu un tālruņa numuru vienlaicīgi pagaidām netiek atbalstīta. Ar kontu būs saistīts vienīgi tālruņa numuru.
\n
\nSavu epastu varat pievienot profilam iestatījumos.
- Bāzes serveris vēlas pārbaudīt, vai neesat robots
+ Mājasservers vēlas pārbaudīt, vai neesat robotsŠāds lietotājvārds jau ir aizņemts
- Bāzes serveris:
+ Mājasservers:Identitāšu serveris:Esmu verificējis(usi) savu epasta adresiLai atiestatītu paroli, ievadiet epasta adresi, kura piesaistīta kontam:
@@ -672,7 +673,7 @@ Lūdzu dod piekļuves atļauju nākamajā uznirstošajā logā, lai būtu iespē
Parole:IesniegtPierakstījies kā
- Bāzes serveris
+ MājasserverisIdentitāšu serverisLietotāja saskarneSaskarnes valoda
@@ -852,7 +853,7 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka.Izvēlies istabu kataloguServeris, iespējams, ir nepieejams vai pārslogotsIevadi pamatserveri no kura pieprasīt tā publisko istabu sarakstu
- Pamatservera URL
+ Mājasservera nosaukumsVisas istabas %s serverīVisas vietējās %s istabas
@@ -1669,7 +1670,7 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka.
Pievieno ¯\\_(ツ)_/¯ vienkārša teksta ziņas sākumāIespējo/atspējo markdownIestata istabas tematu
- Pievienojas istabai ar norādīto aliasu
+ Pievienojas istabai ar norādīto adresiAtceļ pieejas liegumu lietotājam ar norādīto idKomandai \"%s\" nepieciešami vairāk parametri vai arī kāds no parametriem ir nepareizs.Vai tiešām vēlaties dzēst visas nenosūtītas ziņas šajā istabā\?
@@ -1738,7 +1739,7 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka.
Ievadiet savu %s, lai turpinātu.Apstiprināt %sKonta parole
- Verificējiet savas ierīces no iestatījumiem.
+ Verifikācija tika atcelta. Jūs varat uzsākt atkal.Kāds no uzskaitītajiem var būt kompromitēts:
\n
\n- jūsu parole
@@ -1853,9 +1854,9 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka.
\nMēģiniet restartēt aplikāciju.
Notifikāciju Pakalpojums strādā.Notifikācija tika nospiesta!
- Neizdevās reģistrēt FCM žetonu serverī:
+ Neizdevās reģistrēt FCM žetonu mājasserverī:
\n%1$s
- FCM žetons veiksmīgi reģistrēts serverī.
+ FCM žetons veiksmīgi reģistrēts mājasserverī.FCM žetons veiksmīgi saņemts:
\n%1$sFCM žetons netika saņemts:
@@ -1945,7 +1946,7 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka.Lūdzu uztaisiet kopijuStopAizvietot
- Dublējums jau pastāv jūsu serverī
+ Dublējums jau pastāv jūsu mājasserverīAtgūšanas atslēgas tika saglabātas.Atgūšanas atslēga ir saglabāta \"%s\".
\n
@@ -2108,7 +2109,7 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka.TurpinātIzvēlieties zvana signālu:Ienākoša zvana signāls
- Izmantos %s kā rezervi, kad jūsu serveris nepiedāvā vienu (jūsu IP adrese tiks dalīta sarunas laikā)
+ Izmantos %s kā rezervi, kad jūsu mājasservers nepiedāvā vienu (jūsu IP adrese tiks dalīta sarunas laikā)Atļaut rezerves zvanu palīdzības serveriIzmantot noklusējuma $ {app_name} zvana signālu ienākošajiem zvaniemPirms zvana uzsākšanas lūgt apstiprinājumu
@@ -2338,4 +2339,465 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka.VietnesUzaicinājumiIeteiktās telpas
+ Laipni lūgti %1$s, %2$s.
+ Jūs vēl neesat nevienā istabā. Zemāk ir dažas no ieteiktajām istabām, bet jūs varat apskatīt arī citas, izmantojot zaļo pogu apakšējā labajā pusē.
+ Pievienoties tāpat
+ Pievienoties Telpai
+ Izveidot Telpu
+ Pagaidām izlaist
+ Pievienojies manai Telpai %1$s %2$s
+ Tie nebūs daļa no %s
+ Tikai uz šo istabu
+ Viņi varēs izpētīt %s
+ Uzaicināt uz %s
+ Kopīgot saiti
+ Ielūgt pēc lietotājvārda vai pa e-pastu
+ Ielūgt pēc lietotājvārda
+ Ielūgt pa e-pastu
+ Šobrīd esi tikai tu. %s būs vēl labāk kopā ar citiem.
+ Uzaicināt uz %s
+ Uzaicināt cilvēkus
+ Uzaiciniet cilvēkus uz savu Telpu
+ Apraksts
+ Izveido Telpu…
+ Nejaušs
+ Vispārīgi
+ Izveidosim katram no tiem savu istabu. Vēlāk varat pievienot arī citas, tostarp jau esošās.
+ Pie kādām lietām strādājat\?
+ Nodrošiniet piekļuvi %s uzņēmumam pareizajiem cilvēkiem. Vēlāk varat uzaicināt vairāk.
+ Kas ir jūsu komandas biedri\?
+ Mēs izveidosim tām istabas. Vēlāk varat pievienot arī citas.
+ Kādas ir dažas diskusijas, ko vēlaties apspriest %s\?
+ Dodiet tam nosaukumu, lai turpinātu.
+ Pievienojiet sīkāku informāciju, lai palīdzētu cilvēkiem to identificēt. Tās var mainīt jebkurā brīdī.
+ Pievienojiet dažas detaļas, lai tā izceltos. Tās var mainīt jebkurā brīdī.
+ Izveidot Telpu
+ Tikai uzaicinot, vislabāk priekš jums vai komandām
+ Atvērta ikvienam, vislabāk kopienām
+ Privāta
+ Publiska
+ Privāta Telpa jums un biedriem
+ Es un biedri
+ Pārliecinieties, ka piekļuve %s ir pareizajiem cilvēkiem. To var mainīt vēlāk.
+ Ar ko jūs strādājat\?
+ Privāta telpa, kur sakārtot istabas
+ Tikai es
+ Lai pievienotos esošai Telpai, ir nepieciešams ielūgums.
+ To var mainīt vēlāk
+ Kādu Telpu vēlaties izveidot\?
+ Telpas ir jauns veids, kā grupēt istabas un cilvēkus
+ Jūsu privātā Telpa
+ Jūsu publiskā Telpa
+ Pievienot Telpu
+ Privāta Telpa
+ Publiska Telpa
+ Pievienoties Telpai ar doto ID
+ Pievienot dotajai Telpai
+ Izveidot Telpu
+ Tips
+ Nav pieejams
+ Nav tiešsaistē
+ Tiešsaistē
+ Publiska Telpa
+ Publiska istaba
+ Skatīt kuri izlasīja
+ Aizvērt emocijzīmju izvēlni
+ Atvērt emocijzīmju izvēlni
+ Izvēlēts
+ Video
+ Izdzēst avatāru
+ Mainīt avatāru
+ Bilde
+ Atvērt logrīkus
+ Ekrānšāviņš
+ Velciet, lai pārtrauktu zvanu
+ Nezināma persona
+ Lietotāji
+ Savienoties
+ %1$s Pieskarieties, lai atgrieztos
+ Aktīvs zvans (%1$s) ·
+
+ Pauzētu zvanu
+ Pauzēts zvans
+ %1$d pauzētu zvanu
+
+ Aktīvs zvans (%1$s)
+ Tika pieļauta kļūda, meklējot tālruņa numuru
+ Zvanu taustiņi
+ Savienojums neizdevās
+ Nav atbildes
+ Neatbildēts video zvans
+ Neatbildēts zvans
+ Video zvans atteikts
+ Zvans atteikts
+ Video zvans beidzās • %1$s
+ Balss zvans beidzās • %1$s
+ Aktīvs video zvans
+ Aktīvs zvans
+ Ienākošs video zvans
+ Ienākošs zvans
+ Piezvanīt atpakaļ
+ Zvans beidzās
+ %1$s atteica zvanu
+ Jūs atteicāt šo zvanu
+ Jūs atteicāt šim zvanam %s
+ Jūs šobrīd esat šajā zvanā
+ %1$s uzsāka zvanu
+ Jūs uzsākāt zvanu
+ Telpa vēl nav izveidota. Atcelt telpas izveidi\?
+ Nevar sūtīt ziņas sev!
+ Apstipriniet PIN, lai izslēgtu PIN
+ Nomainīt tagadējo PIN
+ Nomainīt PIN
+ Ieslēgt PIN
+ Jauns PIN
+ Attiestatīt PIN
+ Aizmirsāt PIN\?
+ Ievadiet savu PIN
+ Apstipriniet PIN
+ Izvēlaties PIN
+ Nevar Atšifrēt
+ Iestatīt avatāru
+ Ieslēgt kameru
+ Izslēgt kameru
+ Ieslēgt mikrofonu
+ Izslēgt mikrofonu
+ Varat ievadīt jebkura cita identitātes servera URL adresi.
+ Varat arī ievadīt jebkuru citu identitātes servera URL.
+ Izmantot %1$s
+ Jūsu mājasserveris (%1$s) ierosina identitātes serverim izmantot %2$s
+ Jūsu privātuma dēļ ${app_name} atbalsta tikai šifrētu lietotāja e-pasta vēstules un tālruņa numura nosūtīšanu.
+ Vispirms iestatījumos pieņemiet identitātes servera noteikumus.
+ Vispirms konfigurējiet identitātes serveri.
+ Šī darbība nav iespējama. Mājasserveris ir novecojis.
+ Ielādē pieejamās valodu…
+ Citas pieejamās valodas
+ Pašreizējā valoda
+ Uzaicinājumi nosūtīti uz %1$s un %2$s
+ 🔐️ Pievienojies ${app_name}
+ Sazinieties ar mani izmantojot ${app_name}: %s
+ Lūdzu, izvēlieties paroli.
+ Lūdzu, izvēlieties lietotājvārdu.
+ Nevarēja saglabāt multivides failu
+ Nevarēja pievienot multivides failu galerijai
+ Galerijai pievienots multivides fails
+ Neatļaut ekrānšāviņus lietotnē
+ Izmantot failu
+ Ievadiet savu %s, lai turpinātu
+ Paziņojuma svarīguma iestatīšana pēc notikuma
+ Notifikāciju iestatījumi
+ Neizdevās importēt atslēgas
+ %s, lai ļautu cilvēkiem uzzināt, par ko ir šī istaba.
+ Jūs to nevarat darīt no mobilās ierīces
+ Nokopējiet to uz personālās mākoņa krātuves
+ Saglabājiet uz USB vai rezerves diska
+ Izprintējiet un turiet to drošībā
+ Jūsu %2$s un %1$s tagad ir iestatīti.
+\n
+\nSaglabājiet tos drošībā! Tie jums būs nepieciešami, lai atbloķētu šifrētus ziņojumus un aizsargātu informāciju, ja zaudēsiet visas aktīvās sesijas.
+ Turiet to drošībā
+ Viss pabeigts!
+ Šis varētu aizņemt dažas sekundes.
+ Ievadiet drošības frāzi kuru zināt tikai jūs, lai nodrošinātu noslēpumus jūsu serverī.
+ Neizmantojiet sava konta paroli.
+ Ievadiet savu %s atkal, lai apstiprinātu.
+ Nodrošiniet un atbloķējiet šifrētus ziņojumus ar %s.
+ Ģenerēt jaunu Ziņu Atslēgu
+ Ziņu Atslēga
+ Jūs neverificēsiet %1$s (%2$s, ja atcelsiet tagad. Sāciet no jauna lietotāja profilā.
+ Ja to atcelsiet, jaunajā ierīcē nevarēsiet lasīt šifrētos ziņojumus
+ Ja to atcelsiet, šajā ierīcē nevarēsiet lasīt šifrētus ziņojumus
+ Nosūtīt multividi ar oriģinālo izmēru
+
+ %d aktīvu sesiju
+ %d aktīva sesiju
+ %d aktīvas sesiju
+
+ Pārtrauc lietotāja ignorēšanu, rādot viņa ziņojumus
+ Ignorē lietotāju, slēpjot viņa ziņojumus no jums
+ Nav konfigurēts integrācijas pārvaldnieks.
+ Atvainojiet, mēģinot pievienoties konferencei, radās kļūda
+ Šis serveris jau ir iekļauts sarakstā
+ Nevar atrast šo serveri vai tā istabu sarakstu
+ Ievadiet jaunā servera nosaukumu, kuru vēlaties izpētīt.
+ Citas Telpas vai istabas, par kurām jūs, iespējams, nezināt
+ Telpa, kuru zināt, kas satur šo istabu
+ Izlemiet, kurš var atrast šo istabu un pievienoties tai.
+ Pieskarieties, lai rediģētu Telpas
+ Izvēlaties Telpas
+ Izlemiet, kuras Telpas var piekļūt šai istabai. Ja Telpa ir izvēlēta, tās dalībnieki varēs atrast istabas nosaukumu un pievienoties tai.
+ Telpas, kurām var piekļūt
+ Ļaujiet Telpas locekļiem atrast un piekļūt.
+ Telpas %s dalībnieki var atrast, apskatīt un pievienoties.
+ Ikviens, kas atrodas telpā, kurā ir šī telpa, var to atrast un pievienoties tai. Tikai šīs telpas administratori var to pievienot telpai.
+ Telpas dalībniekiem tikai
+ Mans redzamais vārds
+ Ikviens var atrast telpu un pievienoties
+ Ikviens var atrast istabu un pievienoties
+ Tikai ielūgtās personas var atrast un pievienoties
+ Privāts (tikai ielūgumiem)
+ Nav zināmi piekļuves iestatījumi (%s)
+ Ikviens var pieklauvēt istabai, dalībnieki var tad pieņemt vai noraidīt
+ Nav iespējams iegūt pašreizējās telpas direktorija redzamību (%1$s).
+ Skatiet un pārvaldiet adreses šajā telpā.
+ Kurš var piekļūt\?
+ Paziņojumus varat pārvaldīt sadaļā %1$s.
+ Lūdzu, ņemiet vērā, ka atsauču un atslēgvārdu paziņojumi nav pieejami šifrētās istabās mobilajā ierīcē.
+ Maina jūsu parādīto segvārdu tikai šajā telpā.
+ Iestata istabas nosaukumu
+ Pievienot jaunu serveri
+ Jūsu servers
+ Publisks
+ Privāts
+ Telpas adrese
+ Ļaut viesiem pievienoties
+ Telpas piekļuve
+ Konta iestatījumi
+
+ Sūtīt video ar oriģinālo izmēru
+ Sūtīt video ar oriģinālo izmēru
+ Sūtīt video ar oriģinālo izmēru
+
+
+ Sūtīt attēlu ar oriģinālo izmēru
+ Sūtīt attēlu ar oriģinālo izmēru
+ Sūtīt attēlus ar oriģinālo izmēru
+
+
+ %d balsu - Galīgie rezultāti
+ %d balss - Galīgie rezultāti
+ %d balsis - Galīgie rezultāti
+
+
+ %d balsu
+ %d balss
+ %d balsis
+
+ nestabila
+ stabila
+ Noklusējuma versija
+ Istabu versijas 👓
+ Ziņu redaktors
+ Hronoloģija
+ Šī sesija nevar kopīgot šo verifikāciju ar citām sesijām.
+\nVerifikācija tiks saglabāta lokāli un kopīgota kādā no nākamajām lietotnes versijām.
+ ${app_name} saskārās ar problēmu, atveidojot notikuma ar id \'%1$s\' saturu
+ ${app_name} neapstrādā \'%1$s\' tipa ziņojumu
+ ${app_name} neapstrādā \'%1$s\' tipa notikumus
+ Tā vietā pārbaudiet, salīdzinot emocijzīmes
+ Noskenēt ar šo ierīci
+ Noskenējiet kodu ar citu ierīci vai pārslēdziet un skenējiet ar šo ierīci
+ Balss
+ Lai nodrošinātu maksimālu drošību, izmantojiet citu uzticamu saziņas līdzekli vai pārbaudiet uz vietas.
+ Apstipriniet šo lietotāju, apstiprinot, ka viņa ekrānā parādās šādas unikālas emocijzīmes tādā pašā secībā.
+ Jūsu e-pasta domēnu nav atļauts reģistrēt šajā serverī
+ Izveido Telpu…
+ Telpas adrese
+ Pievieno ( ͡° ͜ʖ ͡°) parasta teksta ziņai
+ Rādīt kādu noderīgu informāciju, lai palīdzētu atkļūdošanas programmā
+ Parādīt atkļūdošanas informāciju ekrānā
+ ${app_name} var biežāk avarēt, ja rodas neparedzēta kļūda
+ Pašreizējā sesija ir lietotājam %1$s, un jūs sniedzat lietotāja %2$s akreditācijas datus. To neatbalsta ${app_name}.
+\nLūdzu, vispirms notīriet datus un pēc tam pierakstieties vēlreiz, izmantojot citu kontu.
+ Izdzēst visus šajā ierīcē pašlaik saglabātos datus\?
+\nPierakstieties vēlreiz, lai piekļūtu konta datiem un ziņojumiem.
+ Brīdinājums: Šajā ierīcē joprojām tiek glabāti jūsu personas dati (tostarp šifrēšanas atslēgas).
+\n
+\nIzdzēsiet tos, ja esat beidzis lietot šo ierīci vai vēlaties pierakstīties citā kontā.
+ Pierakstieties, lai atgūtu šifrēšanas atslēgas, kas glabājas tikai šajā ierīcē. Jums tās ir nepieciešamas, lai lasītu visus savus drošos ziņojumus jebkurā ierīcē.
+ Jūsu mājasservera (%1$s) administrators ir izslēdzis jūs no konta %2$s (%3$s).
+ To var izraisīt dažādi iemesli:
+\n
+\n• Jūs esat mainījis paroli citā sesijā.
+\n
+\n• Jūs esat izdzēsis šo sesiju no citas sesijas.
+\n
+\n• Jūsu servera administrators ir anulējis jūsu piekļuvi drošības apsvērumu dēļ.
+ Neizskatās pēc derīgas e-pasta adreses
+ Nosūta doto ziņojumu kā pārsteigumu
+ ${app_name} nepieciešama atļauja, lai saglabātu jūsu šifrēšanas atslēgas diskā.
+\n
+\nLūdzu, nākamajā uznirstošajā logā atļaujiet piekļuvi, lai varētu eksportēt atslēgas manuāli.
+ %1$s pie %2$s
+ Šajā istabā nav multivides
+ Multivide
+ Nevarēja apstrādāt kopīgošanas datus
+ Iegūstot pielikumu, radās kļūda.
+ Fails \'%1$s\' (%2$s) augšupielādei ir pārāk liels. Ierobežojums ir %3$s.
+ Fails ir pārāk liels, lai tos augšupielādētu.
+
+ %1$s, %2$s un %3$d citi lasīja
+ %1$s, %2$s un %3$d citi lasīja
+ %1$s, %2$s un %3$d citi lasīja
+
+ Šī funkcija ir beta versijā
+ Jaunas tiešās sarunas izveide, skenējot QR kodu
+ Jaunas tiešās sarunas izveide pēc Matrix ID
+ Izveidot jaunu tiešo sarunu
+ Aizveriet telpas izveides izvēlni…
+ Atveriet izvēlni izveidot telpu
+ Atveriet navigācijas atvilktni
+ Izskatās, ka serveris pārāk ilgi neatbild, to var izraisīt slikts savienojums vai servera kļūda. Lūdzu, pēc brīža mēģiniet vēlreiz.
+ Politika
+ Vai piekrītat sūtīt savus kontaktinformācijas datus (tālruņa numurus un/vai e-pasta vēstules) uz konfigurēto identitātes serveri (%1$s), lai atklātu jums zināmus esošos kontaktus\?
+\n
+\nLai nodrošinātu lielāku konfidencialitāti, nosūtītie dati pirms nosūtīšanas tiks šifrēti.
+ Identitātes serveris nesniedz nekādu politiku
+ Slēpt identitātes servera politiku
+ Parādīt identitātes servera politiku
+ Atvērt atklāšanas iestatījumus
+ Meklēt Vārdu
+ Meklēt pēc vārda, ID vai pasta
+ Kompresē video %d%%
+ Kompresē bildi…
+ Sniedziet atsauksmes
+ Atsauksmi neizdevās nosūtīt (%s)
+ Paldies, jūsu atsauksme ir veiksmīgi nosūtīta
+ Ja jums ir kādi papildjautājumi, varat sazināties ar mani
+ Jūs izmantojat Telpu beta versiju. Jūsu atsauksmes palīdzēs izstrādāt nākamās versijas. Jūsu platforma un lietotājvārds tiks atzīmēti, lai mēs varētu pēc iespējas labāk izmantot jūsu atsauksmes.
+ Atsauksmes
+ Atsauksmes par Telpām
+ Format:
+ Url:
+ session_name:
+ app_display_name:
+ push_key:
+ app_id:
+ Nav reģistrētu Palaišanas vārtu
+ Nav iestatīti Palaišanas Noteikumi
+ Palaišanas Noteikumi
+ Lasāmo telpu priekšskatījums vēl nav atbalstīts ${app_name}
+ Radīt jaunu Telpu
+ Izskatās, ka mēģināt izveidot savienojumu ar citu mājasserveri. Vai vēlaties izrakstīties\?
+ Identitātes serveris nav konfigurēts, tas ir nepieciešams, lai atiestatītu paroli.
+ Sesija nevar vienoties par atslēgas vienošanās, šifrēšanas, MAC vai SAS metodi
+ Sesija nezina par šo darījumu
+ Šifrējums neatbilda
+ Interaktīvā Sesijas verifikācija
+ Paziņot mani priekš
+ Jūs nesaņemsiet paziņojumus par pieminējumiem un atslēgvārdiem šifrētās telpās mobilajās ierīcēs.
+ Istabu atjauninājumi
+ Ziņas no bota
+ Istabu ielūgumi
+ Atslēgvārdi
+ \@istaba
+ Šifrētās grupu ziņas
+ Grupu ziņas
+ Šifrētās tiešās ziņas
+ Tiešās ziņas
+ Nekas neparādās\? Vēl ne visi klienti atbalsta interaktīvu verifikāciju. Izmantojiet veco verifikāciju.
+ Mans lietotājvārds
+ ${app_name} atklāja pielāgotu servera konfigurāciju jūsu lietotāja ID domēnam \"%1$s\":
+\n%2$s
+ Automātiskās servera pabeigšanas opcijas
+ Nederīga mājasservera atbilde
+ Neizdevās iegūt jaunāko atjaunošanas atslēgu versiju (%s).
+
+ %d jaunu atslēgu tika pievienotas šai sesijai.
+ %d jauna atslēgu tika pievienotas šai sesijai.
+ %d jaunas atslēgu tika pievienotas šai sesijai.
+
+ Neziniet atkopšanas paroli, varat %s.
+ Iegūst rezerves kopiju…
+ Tagad jūsu šifrēšanas atslēgas fona režīmā tiek dublētas mājasserverī. Sākotnējā dublēšana var aizņemt vairākas minūtes.
+ Atjaunošanas atslēgas ģenerēšana, izmantojot paroli, šis process var aizņemt vairākas sekundes.
+ Izskatās, ka jums jau ir izveidots atslēgas dublējums no citas sesijas. Vai vēlaties to aizstāt ar izveidoto\?
+ Lūdzu, dzēsiet parolesfrāzi, ja vēlaties, lai ${app_name} ģenerētu atkopšanas atslēgu.
+ Nav atrasts derīgs Google Play Services APK. Paziņojumi var nedarboties pareizi.
+ Jūsu mājasserveris vēl neatbalsta fona istabas lietotāju ielādēšanu. Izmēģiniet vēlāk.
+ Marķēšana ir izslēgta.
+ Marķēšana ir ieslēgta.
+ Parāda informāciju par lietotāju
+ Lai salabotu Matrix Apps vadību
+ Nomaina jūsu avatāru tikai šajā istabā
+ Nomaina šīs istabas avatāru
+ Kad istabas ir atjaunotas
+ • Paziņojumos nebūs redzams ziņas saturs
+ • Paziņojuma saturs ir nosūtīts tieši no Matrix mājasservera
+ Lietotnēm nav fona režīmā jāizveido savienojums ar mājasserveri, un tas samazinātu akumulatora enerģijas patēriņu
+ Fona ierobežojumi ir ieslēgti lietotnei ${app_name}.
+\nDarbs, ko lietotne mēģina veikt, tiks agresīvi ierobežots, kamēr tā atrodas fonā, un tas var ietekmēt paziņojumus.
+\n%1$s
+ Fona ierobežojumi ir izslēgti priekš ${app_name}. Šis tests jāveic, izmantojot mobilos datus (bez WIFI).
+\n%1$s
+ Neizdevās saņemt palaišanu. Iespējams, vajag pārielādēt lietotni.
+ Lietotne saņem Palaišanu
+ Lietotne gaida Palaišanu
+ Testa Palaišana
+ [%1$s]
+\nŠī kļūda ir ārpus ${app_name} kontroles. Tālrunī nav Google konta. Lūdzu, atveriet kontu pārvaldnieku un pievienojiet Google kontu.
+ [%1$s]
+\nŠī kļūda ir ārpus ${app_name} kontroles. Tā var rasties vairāku iemeslu dēļ. Iespējams, tā darbosies, ja vēlāk mēģināsiet vēlreiz, varat arī pārbaudīt, vai sistēmas iestatījumos nav ierobežota Google Play pakalpojuma datu izmantošana, vai ierīces pulkstenis ir pareizs, vai arī tā var notikt uz atsevišķām sistēmām.
+ [%1$s]
+\nŠī kļūda nav kontrolējama ar ${app_name}, un saskaņā ar Google datiem šī kļūda norāda, ka ierīcē ir pārāk daudz lietotņu, kas reģistrētas FCM. Kļūda rodas tikai gadījumos, kad ir ārkārtīgi liels lietotņu skaits, tāpēc šim nevajadzētu ietekmēt parastu lietotāju.
+ Izpilda… (%1$d of %2$d)
+ Atslēgvārds nevar saturēt \'%s\'
+ Atslēgvārds nevar sākties ar \'.\'
+ Pievienot jaunu atslēgvārdu
+ Jūsu atslēgvārdiem
+ Paziņot mani par
+ Cits
+ Atsauces un Atslēgvārdi
+ Noklusējuma Paziņojumi
+ Ieslēgt e-pasta paziņojumus priekš %s
+ Lai saņemtu e-pastu ar paziņojumu, lūdzu, piesaistiet e-pastu savam Matrix kontam
+ E-pasta paziņojumi
+ Nekas
+ Tikai Atsauces un Atslēgvārdi
+ Atjaunināt Telpu
+ Nomainīt Telpas nosaukumu
+ Ieslēgt Telpas šifrēšanu
+ Mainīt galveno Telpas adresi
+ Mainīt Telpas avatāru
+ Jums nav atļaujas atjaunināt lomas, kas nepieciešamas, lai mainītu dažādas šīs telpas daļas
+ Izvēlaties lomas, kas nepieciešamas, lai mainītu dažādas Telpas daļas
+ Skatiet un atjauniniet lomas, kas nepieciešamas, lai mainītu dažādas Telpas daļas.
+ Telpas atļaujas
+ Atceļot pievienošanās aizliegumu lietotājam, ļaus viņam atkal pievienoties Telpai.
+ Aizliedzot lietotājam pievienoties, viņš tiks izmests no šīs Telpas un vairs nevarēs tai pievienoties.
+ izmetot lietotāju, noņems viņu no šīs Telpas.
+\n
+\nLai novērstu viņu atkārtotu pievienošanos, jums tā vietā vajadzētu viņiem aizliegt pievienoties.
+
+ %dd
+ %dd
+ %dd
+
+
+ %dh
+ %dh
+ %dh
+
+
+ %dm
+ %dm
+ %dm
+
+ Turpināt tāpat
+ Beidz zvanu…
+ Nav atbildes
+ Lietotājs, kuram zvanījāt, ir aizņemts.
+ Lietotājs aizņemts
+ Zvans ar %s
+ Video zvans ar %s
+
+ Neatbildētu video zvanu
+ Neatbildēts video zvans
+ %d neatbildēti zvans
+
+
+ Neatbildētu zvanu
+ Neatbildēts zvans
+ %d neatbildēti zvans
+
+ Zvana…
+ Izvēlaties Mājasserveri
+ Mājasservera API URL adrese
+ Nevar sasniegt mājasserveri URL adresē %s. Lūdzu, pārbaudiet saiti vai izvēlieties mājasserveri manuāli.
+ Izmantot pēc noklusējuma un neprasīt atkal
+ Vienmēr prasīt
+ Lai sūtītu balss ziņojumus, piešķiriet Mikrofona atļauju.
+ Telpas
+ Uzziniet Vairāk
+ Uztver paziņojumus
\ No newline at end of file
diff --git a/vector/src/main/res/values-mk/strings.xml b/vector/src/main/res/values-mk/strings.xml
new file mode 100644
index 0000000000..a6b3daec93
--- /dev/null
+++ b/vector/src/main/res/values-mk/strings.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml
index d1428300e8..193a8223c0 100644
--- a/vector/src/main/res/values-pl/strings.xml
+++ b/vector/src/main/res/values-pl/strings.xml
@@ -64,21 +64,22 @@
Wiadomość usunięta [powód: %1$s]Wiadomość usunięta przez %1$s [powód: %2$s]%s zakutalizował(a) ten pokój.
- Synchronizacja początkowa:
+ Wstępna synchronizacja:
\nImportowanie konta…
- Synchronizacja początkowa:
+ Wstępna synchronizacja:
\nImportowanie kryptografii
- Synchronizacja początkowa:
-\nImportowanie Pokoi
- Synchronizacja początkowa:
-\nImportowanie dołączonych Pokoi
- Synchronizacja początkowa:
-\nImportowanie zaproszonych Pokoi
- Synchronizacja początkowa:
-\nImportowanie opuszczonych Pokoi
- Synchronizacja początkowa:
-\nImportowanie Społeczności
- Synchronizacja początkowa:
+ Wstępna synchronizacja:
+\nImportowanie pokoi
+ Wstępna synchronizacja:
+\nImportowanie Twoich konwersacji
+\nJeśli dołączyłeś(aś) do wielu pokoi, może to zająć dłuższą chwilę
+ Wstępna synchronizacja:
+\nImportowanie zaproszonych pokoi
+ Wstępna synchronizacja:
+\nImportowanie opuszczonych pokoi
+ Wstępna synchronizacja:
+\nImportowanie grup
+ Wstępna synchronizacja:
\nImportowanie danych KontaWysyłanie wiadomości…Wyczyść kolejkę wysyłania
@@ -103,7 +104,7 @@
Wyświetl odszyfrowane źródłoUsuńZmień nazwę
- Zgłoś zawartość
+ Zgłoś treśćAktywne połączeniePrzychodzące połączenie grupowe.
\nDołącz z %1$s lub %2$s
@@ -178,7 +179,7 @@
Stwórz kontoZaloguj sięWyloguj
- Adres serwera
+ Adres serwera domowegoSzukajRozpocznij nową rozmowęRozpocznij połączenie telefoniczne
@@ -217,7 +218,7 @@
Zapomniałeś(-aś) hasła?Użyj niestandardowych ustawień serwera (zaawansowane)Sprawdź swój adres e-mail, aby kontynuować rejestrację
- Serwer chciałby upewnić się, czy nie jesteś robotem
+ Serwer domowy prosi o potwierdzenie, że nie jesteś robotemNazwa użytkownika jest już używanaZweryfikowałem adres e-mailAby przywrócić hasło, wprowadź adres e-mail powiązany z kontem:
@@ -278,7 +279,7 @@ Przyznaj dostęp w następnym oknie.DołączPodglądOdrzuć
- Przejdź do pierwszej nieprzeczytanej wiadomości.
+ Przejdź do pierwszej nieprzeczytanej wiadomościZostałeś(-aś) zaproszony(-a) do tego pokoju przez %sPróbujesz uzyskać dostęp do %s. Czy chcesz dołączyć do pokoju, aby wziąć udział w dyskusji?pokój
@@ -330,7 +331,7 @@ Przyznaj dostęp w następnym oknie.Wyślij niewysłane wiadomości ponownieUsuń niewysłane wiadomościNie znaleziono pliku
- Nie masz uprawnień, aby pisać w tym pokoju
+ Nie masz uprawnień, aby pisać w tym pokoju.UfajNie ufajWyloguj
@@ -513,7 +514,7 @@ Zauważ, że ta czynność spowoduje ponowne uruchomienie aplikacji i może to t
Pokój zawiera nieznane sesjeWybierz katalog pokojówSerwer może być wyłączony lub przeciążony
- Adres serwera domowego
+ Nazwa serweraWszystkie pokoje na serwerze %sSzukaj w archiwumRozmiar czcionki
@@ -566,8 +567,8 @@ Zauważ, że ta czynność spowoduje ponowne uruchomienie aplikacji i może to t
Rejestracja jednocześnie za pomocą numeru telefonu i adresu e-mail nie jest obsługiwana dopóki nie pojawi się odpowiednie API. Tylko numer telefonu będzie brany pod uwagę.
Możesz dodać adres e-mail do swojego profilu w ustawieniach.
- Serwer Domowy:
- Serwer Tożsamości:
+ Serwer domowy:
+ Serwer tożsamości:Twoje hasło zostało zresetowane. Zostałeś wylogowany ze wszystkich sesji i nie będziesz więcej otrzymywać powiadomień push. Aby ponownie włączyć powiadomienia, zaloguj się ponownie na każdym urządzeniu.Wprowadzony token dostępu nie został rozpoznanyUszkodzony JSON
@@ -796,7 +797,7 @@ Czy chcesz dodać teraz kilka?Brakujące user_id w żądaniu.Brakuje wymaganego parametru.Parametr jest niepoprawny.
- Dodaj aplikacje Matrix
+ Zarządzaj integracjamiWyślij wiadomości głosoweDodałeś(-aś) nową sesję \'%s\', która żąda kluczy szyfrujących.Twoje niezweryfikowana sesja \'%s\' żąda kluczy szyfrujących.
@@ -901,12 +902,12 @@ Widoczność wiadomości w Matrix jest podobna do wiadomości e-mail. Nasze zapo
Określ poziom mocy użytkownikaUsuwa opa użytkownikowi z podanym IDZaprasza użytkownika z podanym ID do aktualnego pokoju
- Dołącza do pokoju z podanym aliasem
+ Dołącza do pokoju o podanym adresieUstawia temat pokojuWyrzuca użytkownika z podanym IDZmienia twój wyświetlany nickDo naprawiania zarządzania aplikacjami Matrix
- Ten pokój został zamieniony i nie jest już aktywny
+ Ten pokój został zamieniony i nie jest już aktywny.Konwersacja jest kontynuowana tutajTen pokój jest kontynuacją innej rozmowyLimit pakietów został przekroczony
@@ -1076,7 +1077,7 @@ Spróbuj uruchomić ponownie aplikację.
Diagnostyka powiadomieńRozwiązywanie problemówDiagnostyka podstawowa nie wykazała problemów. Jeżeli wciąż nie otrzymujesz powiadomień, prosimy o przesłanie raportu o błędach, w celu ich rozwiązania.
- Aplikacja nie potrzebuje łączyć się z serwerem domowym w tle, powinno to zredukować użycie baterii
+ Aplikacja nie potrzebuje łączyć się z serwerem domowym w tle, powinno to zmniejszyć użycie bateriiJeżeli nie pamiętasz swoich danych odzystkiwania, możesz %s.Zgubiłeś (-łaś) swój klucz odzyskiwania\? Możesz ustawić nowy w ustawieniach.Kopia zapasowa posiada poprawną sygnaturę z niezweryfikowanej sesji %s
@@ -1094,7 +1095,7 @@ Spróbuj uruchomić ponownie aplikację.
Dodaj KontoZarządzanie Kluczami KryptograficznymiZezwól na integracje
- Menadżer Integracji
+ Menedżer IntegracjiHasło jest nieprawidłoweHasła nie pasują do siebieWybierz
@@ -1232,8 +1233,8 @@ Spróbuj uruchomić ponownie aplikację.
\n%1$s
[%1$s]
\n${app_name} nie ma wpływu na wystąpienie tego problemu. Na tym urządzeniu nie ma konta Google. Otwórz menadżer kont i dodaj konto Google.
- Token FCM z powodzeniem zarejestrowany na serwerze domowym.
- Niepowodzenie przy rejestracji tokena FCM na serwerze domowym:
+ Token FCM pomyślnie zarejestrowany na serwerze domowym.
+ Nieudana rejestracja tokena FCM na serwerze domowym:
\n%1$sUsługa została zatrzymana i automatycznie uruchomiona ponownie.Usługa nie uruchomiła się ponownie
@@ -1254,7 +1255,7 @@ Spróbuj uruchomić ponownie aplikację.Wprowadź adres e-mail, aby możliwe było odzyskiwanie konta. Opcjonalnie użyj adresu e-mail lub numeru telefonu aby móc zostać odkrytym przez znajomych.Wprowadź adres e-mail, aby możliwe było odzyskiwanie konta. Opcjonalnie użyj adresu e-mail lub numeru telefonu aby móc zostać odkrytym przez znajomych.Pozwól na awaryjny serwer wspomagania połączeń
- Użyje %s aby wspomagać gdy Twój serwer domowy takiego nie ofertuje (Twój adres IP będzie udostępniony podczas połączenia)
+ Użyj %s, gdy Twój serwer domowy takiego nie ofertuje (Twój adres IP będzie udostępniony podczas połączenia)[%1$s]
\nBłąd jest poza kontrolą ${app_name} i nawiązując do Google sygnalizuje on, iż urządzenie posiada zbyt wiele aplikacji zarejestrowanych z FCM. Błąd występuje jedynie w przypadku posiadania skrajnie wielu aplikacji, w związku z czym nie powinno dotknąć to normalnego użytkownika.[%1$s]
@@ -1280,8 +1281,8 @@ Spróbuj uruchomić ponownie aplikację.Preferowany interwał synchronizacji%s
\nSynchronizacja może zostać opóźniona w zależności od zasobów (bateria) lub stanu urządzenia (hibernacja).
- Użyj Menedżera Integracji aby zarządzać botami, mostami, widżetami oraz pakietami naklejek.
-\nMenadżerowie Integracji odbierają dane konfiguracji, mogą zmieniać widżety, wysyłać zaproszenia do pokoi oraz ustawiać poziomy uprawnień na Twoje żądanie.
+ Użyj menedżera integracji aby zarządzać botami, mostami, widżetami i pakietami naklejek.
+\nMenedżerzy integracji odbierają dane konfiguracji, modyfikują widżety, wysyłają zaproszenia do pokoi i ustawiają poziomy uprawnień na Twe żądanie.Pokaż podgląd linków wewnątrz czatu jeśli twój serwer wspiera tę funkcję.Formatuj wiadomości używając składni Markdown zanim zostaną wysłane. Pozwala to na zaawansowane formatowanie takie jak używanie asterysków do wyświetlania tekstu w kursywie.Nie wpływa to na zaproszenia, wyrzucenia oraz bany.
@@ -1357,7 +1358,7 @@ Spróbuj uruchomić ponownie aplikację.
Klucz odzyskiwania został zapisany do \'%s\'.
\n
\nUwaga: plik może zostać usunięty, jeżeli aplikacja jest odinstalowana.
- Kopia zapasowa już istnieje na Twoim serwerze domowym
+ Kopia zapasowa istnieje już na Twoim serwerze domowymWygląda na to, iż kopia zapasowa kluczy została skonfigurowana za pomocą innej sesji. Czy chcesz zastąpić ją tą, którą tworzysz\?Generowanie Klucza Odzyskiwania używając hasła, proces może zająć kilka sekund.Kopia zapasowa uruchomiona
@@ -1871,9 +1872,9 @@ Spróbuj uruchomić ponownie aplikację.
Rozpocznij połączenie głosoweRozpocznij połączenie wideoPołączenie grupowe już trwa!
- Nie posiadasz wymaganych uprawnień aby rozpocząć połączenie grupowe w tym pokoju
+ Nie posiadasz uprawnień, aby rozpocząć połączenie grupowe w tym pokojuNie posiadasz wymaganych uprawnień do rozpoczęcia połączenia
- Nie posiadasz wymaganych uprawnień aby rozpocząć połączenie grupowe
+ Nie posiadasz uprawnień, aby rozpocząć połączenie grupowePrzejrzyj swoje ustawienia aby włączyć powiadomienia PUSHPowiadomienia PUSH są wyłączoneNie udało się odblokować użytkownika
@@ -2040,7 +2041,7 @@ Spróbuj uruchomić ponownie aplikację.
%d sek.Aplikacja odebrała PUSH
- Przetestuj wiadomość Push
+ Przetestuj powiadomienia pushOdbanuj użytkownikaPowód zbanowaniaWykopanie użytkownika spowoduje usunięcie go z tego pokoju.
@@ -2050,7 +2051,7 @@ Spróbuj uruchomić ponownie aplikację.Zapobiegaj przypadkowym połączeniomWycofaj publikacjęDodaj
- Skopiuj
+ KopiujZakończ rozmowęZresetujTryb samolotowy jest włączony
@@ -2089,7 +2090,7 @@ Spróbuj uruchomić ponownie aplikację.
Ten serwer domowy pracuje na starej wersji. Poproś jego administratora o zaktualizowanie go. Możesz kontynuować, ale niektóre funkcjonalności mogą nie działać poprawnie.Użyj proszę formatu międzynarodowego (numer telefonu musi zaczynać się od \"+\")Wpisz adres serwera, którego chcesz używać
- Zrobiłeś(-łaś) to dostępne tylko przez zaproszenie
+ Zrobiłeś(-łaś) to dostępne tylko przez zaproszenie.Uczyniłeś(-łaś) ten pokój dostępnym tylko poprzez zaproszenie.Uczyniłeś(-łaś) ten pokój publicznym dla każdego kto zna link.Nie dokonano żadnych zmian
@@ -2193,7 +2194,7 @@ Spróbuj uruchomić ponownie aplikację.
Zarządzaj adresami e-mail oraz numerami telefonów powiązanymi z Twoim kontem MatrixAdresy e-mail i numery telefonówWłącz \"Zezwalaj na integracje\" w Ustawieniach żeby to zrobić.
- Integracje są wyłączone
+ Integracje są zablokowaneTo zastąpi obecny Klucz bądź Hasło.Wygeneruj nowy Klucz Bezpieczeństwa albo Hasło dla istniejącej kopii zapasowej.Zabezpiecza przeciwko utracie dostępu do zaszyfrowanych wiadomości oraz danych poprzez zapisanie zaszyfrowanych kluczy na Twoim serwerze.
@@ -2284,7 +2285,7 @@ Spróbuj uruchomić ponownie aplikację.
Kiedy pokoje są aktualizowaneZabezpiecz i odblokuj zaszyfrowane wiadomości oraz zaufane poprzez %s.Naklejka
- %1$s zrobił(a) to dostępne tylko z zaproszeniem.
+ %1$s wymaga otrzymania zaproszenia do dołączenia.%1$s, %2$s i %3$d czyta%1$s, %2$s i %3$d czytają
@@ -2297,7 +2298,7 @@ Spróbuj uruchomić ponownie aplikację.
Zezwól na dostęp do Twoich kontaktów.Wybierz urządzenie dźwiękowePołączenie ${app_name} nieudane
- Zrezygnuj
+ OdrzućRozpocznij konwersacj꣹cze MatrixOdrzuć zmiany
@@ -2430,4 +2431,437 @@ Spróbuj uruchomić ponownie aplikację.
Twoje zaproszenieWysłałeś naklejkę.Wysłałeś zdjęcie.
+ Członkowie przestrzeni %s mogą znaleźć, podejrzeć i dołączyć.
+ Każdy w Przestrzeni z tym pokojem może go znaleźć i dołączyć. Tylko administratorzy mogą dodać go do przestrzeni.
+ Tylko członkowie Przestrzeni
+ Każdy może znaleźć Przestrzeń i do niej dołączyć
+ Każdy może znaleźć pokój i dołączyć
+ Publiczny
+ Tyko zaproszeni ludzie mogą znaleźć i dołączyć
+ Prywatny (Tylko z zaproszeniem)
+ Prywatny
+ Nieznane ustawienie dostępu (%s)
+ Każdy może zapukać do pokoju, członkowie mogą akceptować lub odrzucić
+ Przeglądaj i zarządzaj adresami tej Przestrzeni.
+ Adresy Przestrzeni
+ Zezwalaj gościom na dołączanie
+ Dostęp do Przestrzeni
+ Kto powinien mieć dostęp \?
+ Ustawienia konta
+ Możesz zarządzać notyfikacjami w %1$s.
+ Proszę zwrócić uwagę, że notyfikacje o wzmiankach i słowach kluczowych nie są dostępne w zaszyfrowanych pokojach na urządzeniach mobilnych.
+ Powiadamiaj mnie o
+ Zresetuj bezpieczną kopię zapasową
+ Skonfiguruj bezpieczną kopię zapasową
+ Bezpieczna kopia zapasowa
+ Dodaj przycisk otwierania klawiatury emoji w edytorze wiadomości
+ Pokaż klawiaturę emoji
+ Użyj komendy /confetti lub wyślij wiadomość zawierająca ❄️ lub 🎉
+ Pokaż efekty chatu
+ Nie będziesz dostawać powiadomień od wzmianek i słów kluczowych, w zaszyfrowanym pokoju na urządzeniu mobilnym.
+ Wyświetlanie powiadomień
+ Aktywuj powiadomienia email dla %s
+ Powiadomienie email
+ Żadne
+ Tylko wzmianki i słowa kluczowe
+ Oczekiwanie na notyfikacje
+ %1$s zmienił(a) adresy tego pokoju.
+ Zmieniłeś(aś) głowny i alternatywny adres tego pokoju.
+ %1$s zmienił(a) główny i alternatywny adres tego pokoju.
+ Zmieniłeś alternatywny adres tego pokoju.
+
+ Usunąłeś(aś) alternatywny adres %1$s dla tego pokoju.
+ Usunąłeś(aś) alternatywne adresy %1$s dla tego pokoju.
+
+
+
+ %1$s ustawił(a) główny adres tego pokoju na %2$s.
+
+ %1$s dodał(a) %2$s jako adres tego pokoju.
+ %1$s dodał(a) %2$s jako adresów tego pokoju.
+ %1$s dodał(a) %2$s adresów tego pokoju.
+ %1$s dodał(a) %2$s adresów tego pokoju.
+
+ Zaakceptowałeś zaproszenie dla %1$s. Powód: %2$s
+ Dołączyłeś. Powód: %1$s
+ %1$s dołączył(a). Powód: %2$s
+ Dołączyłeś do pokoju. Powód: %1$s
+ %1$s dołączył do pokoju. Powód: %2$s
+ %1$s zaprosił Cię. Powód: %2$s
+ Zaprosiłeś %1$s. Powód: %2$s
+ %1$s zaprosił(a) %2$s. Powód: %3$s
+ Twoje zaproszenie. Powód: %1$s
+ Zaproszenie %1$s. Powód: %2$s
+ %1$s z %2$s na %3$s
+ %1$s zmienił(a) poziom mocy na %2$s.
+ Zmieniłeś poziom mocy na %1$s.
+ • Serwery z pasującymi literałami IP są teraz zablokowane.
+ • Serwery z pasującymi literałami IP są teraz dozwolone.
+ • Serwery pasujące do %s zostały usunięte z listy dozwolonych.
+ • Serwery pasujące do %s są teraz dozwolone.
+ • Serwery pasujące do %s zostały usunięte z listy zablokowanych.
+ • Serwery pasujące do %s są teraz zablokowane.
+ %1$s, %2$s i %3$s
+ %s w Ustawieniach, aby otrzymywać zaproszenia bezpośrednio w Elememencie.
+ Zmiana tematu
+ Aktualizacja Przestrzeni
+ Aktualizacja pokoju
+ Wysyłanie zdarzeń m.room.server_acl
+ Zmiana uprawnień
+ Zmiana nazwy Przestrzeni
+ Zmiana nazwy pokoju
+ Zmiana widoczności historii
+ Włączanie szyfrowania przestrzeni
+ Włączanie szyfrowania pokoju
+ Zmiana głównego adresu Przestrzeni
+ Zmiana głównego adresu pokoju
+ Zmiana awatara Przestrzeni
+ Zmiana awatara pokoju
+ Modyfikowanie widgetów
+ Powiadamianie wszystkich
+ Usuwanie wiadomości wysłanych przez inne osoby
+ Blokowanie użytkowników
+ Wyrzucanie użytkowników
+ Zmiana ustawień
+ Zapraszanie użytkowników
+ Wysyłanie wiadomości
+ Rola domyślna
+ Nie masz uprawnień do modyfikowania roli wymaganych aby zmieniać poszczególne części tej przestrzeni
+ "Nie masz uprawnieni do modyfikowania roli wymaganych do zmiany poszczególnych części pokoju"
+ Przeglądaj i modyfikuj role wymagane do zmiany różnych części przestrzeni.
+ Wybierz role wymagane do zmieniania poszczególnych części pokoju
+ Wybierz role wymagane do zmiany poszczególnych części tej przestrzeni
+ Uprawnienia
+ Przeglądaj i modyfikuj role wymagane do zmiany różnych funkcji pokoju.
+ Uprawnienia Przestrzeni
+ Uprawnienia pokoju
+ Odblokowanie użytkownika pozwoli mu na ponowne dołączenie do tej przestrzeni.
+ Blokowanie użytkowników wyrzuci ich z tej przestrzeni i uniemożliwi im dołączenie ponownie.
+ Ten pokój jest prywatny. Nie będziesz w stanie dołączyć bez zaproszenia.
+ Kontynuuj mimo wszystko
+ Zakańczanie połączenia…
+ Brak odpowiedzi
+ Użytkownik, do którego dzwoniłeś, jest teraz zajęty.
+ Użytkownik zajęty
+ Zawiesiłeś(aś) połączenie
+ %s zawiesił(a) połączenie
+ Zawieś
+ Wznów
+ Wróć do rozmowy
+ Połączenie głosowe z %s
+ Połączenie wideo z %s
+
+ Nieodebrane połączenie wideo
+ %d nieodebrane połączenia wideo
+ %d nieodebranych połączeń wideo
+ %d nieodebranych połączeń wideo
+
+
+ Nie odebrane połączenie głosowe
+ %d nie odebrane połączenia głosowe
+ %d nie odebranych połączeń głosowych
+ %d nie odebranych połączeń głosowych
+
+ Połączenie przychodzące…
+ Błąd autoryzacji, niepoprawne dane logowania
+ Wybierz serwer domowy
+ Nie można było połączyć się z serwerem domowym %s. Proszę sprawdzić link lub wybrać serwer domowy manualnie.
+ Używaj jako domyślne i nie pytaj ponownie
+ Zawsze pytaj
+ Adres URL serwera domowego
+ Wyślij historię żądań udostępnienia klucza
+ Przestrzenie
+ Zaproszenia
+ Katalog pokoi
+ Sugerowane pokoje
+ Nowa wartość
+ Wróć
+ Przełącz
+ Brak uprawnień
+ Proszę nadać uprawnienia do mikrofonu, aby nagrywać wiadomości głosowe.
+ Aby wykonać tą akcję, proszę nadać uprawnienia do aparatu z poziomu ustawień systemowych.
+ Brakuje uprawnień do wykonania tej akcji, proszę je przydzielić z poziomu ustawień systemowych.
+ Przestrzenie
+ Dowiedz się więcej
+ Domyślny systemu
+ Włączyłeś(aś) szyfrowanie end-to-end (nierozpoznany algorytm %1$s).
+ %1$s włączył(a) szyfrowanie end-to-end (nierozpoznany algorytm: %2$s).
+ Włączyłeś(aś) szyfrowanie end-to-end.
+ %1$s włączył(a) szyfrowanie end-to-end.
+ Zabroniłeś(aś) gościom dołączać do pokoju.
+ %1$s zabronił(a) gościom dołączać do pokoju.
+ Zabroniłeś gościom dołączać do pokoju.
+ %1$s zabronił(a) gościom dołączać do pokoju.
+ Zezwoliłeś gościom na dołączanie tutaj.
+
+ %1$s usunął(ęła) alternatywny adres %2$s dla tego pokoju.
+ %1$s usunął(ęła) alternatywne adresy %2$s dla tego pokoju.
+ %1$s usunął(ęła) alternatywne adresy %2$s dla tego pokoju.
+ %1$s usunął(ęła) alternatywne adresy %2$s dla tego pokoju.
+
+
+ Dodałeś(aś) alternatywny adres %1$s dla tego pokoju.
+ Dodałeś(aś) alternatywne adresy %1$s dla tego pokoju.
+ Dodałeś(aś) alternatywne adresy %1$s dla tego pokoju.
+ Dodałeś(aś) alternatywne adresy %1$s dla tego pokoju.
+
+
+ %1$s dodał(a) alternatywny adres %2$s dla tego pokoju.
+ %1$s dodał(a) alternatywne adresy %2$s dla tego pokoju.
+ %1$s dodał(a) alternatywne adresy %2$s dla tego pokoju.
+ %1$s dodał(a) alternatywne adresy %2$s dla tego pokoju.
+
+ Usunąłeś(aś) główny adres tego pokoju.
+ %1$s usunął(a) główny adres tego pokoju.
+ Ustawiłeś(aś) główny adres tego pokoju na %1$s.
+ Zmodyfikowałeś(aś) wideokonferencję
+ Wideokonferencja zmodyfikowana przez %1$s
+ Zakończyłeś wideokonferencję
+ Zmodyfikowałeś(aś) widget %1$s
+ %1$s zmodyfikował(a) widget %2$s
+ Usunąłeś(aś) widget %1$s
+ %1$s usunął(ęła) widget %2$s
+ Dodałeś widget %1$s
+ %1$s dodał(a) widżet %2$s
+ Zaakceptowałeś zaproszenie dla %1$s
+ Anulowałeś zaproszenie dla %1$s
+ %1$s anulował zaproszenie dla %2$s
+ Anulowałeś zaproszenie dla %1$s do dołączenia do pokoju
+ %1$s odwołał zaproszenie dla %2$s do dołączenia do pokoju
+ Pokaż przydatne informacje aby pomóc w naprawianiu aplikacji
+ Pokazuj informacje diagnostyczne na ekranie
+ Moja nazwa wyświetlana
+ Moja nazwa użytkownika
+ Wiadomości bezpośrednie
+ Wiadomości od bota
+ Zaproszenia do pokoju
+ Słowa kluczowe
+ \@pokój
+ Zaszyfrowane wiadomości grupowe
+ Wiadomości grupowe
+ Zaszyfrowane wiadomości bezposrednie
+ Słowa kluczowe nie mogą zawierać \'%s\'
+ Słowa kluczowe nie mogą zaczynać się od \'.\'
+ Dodaj nowe słowo kluczowe
+ Twoje słowa kluczowe
+ Powiadamiaj mnie o
+ Inne
+ Wzmianki i słowa kluczowe
+ Domyślne powiadomienia
+ Aby otrzymywać emaile z notyfikacjami, proszę przypisać email do swojego konta Matrix
+ Wiadomość głosowa (%1$s)
+ Nie można odpowiadać lub edytować kiedy wiadomość głosowa jest aktywna
+ Nie udało się nagrać wiadomości głosowej
+ Nie można odtworzyć tej wiadomości głosowej
+ Włącz wiadomość głosową
+ Naciśnij na swoje nagranie aby zatrzymać lub przesłuchać
+ zostało %1$ds
+ Przytrzymaj aby nagrać, puść by wysłać
+ Przeciągnij aby anulować
+ Nagraj wiadomość głosową
+ Rozmowa grupowa rozpoczęta
+ Przepraszamy, wystąpił błąd podczas dołączania do: %s
+ Wymagana aktualizacja
+ Aktualizacja
+ Prosimy o cierpliwość, to może zając chwilę.
+ Pokój bez nazwy
+ Niektóre pokoje mogą być ukryte, gdyż są prywatne i wymagają od Ciebie zaproszenia.
+ Niektóre pokoje mogą być ukryte, gdyż są prywatne i wymagają od Ciebie zaproszenia.
+\nNie masz uprawnień aby dodawać pokoje.
+ W tej Przestrzeni nie ma żadnych pokoi
+ Proszę skontaktować się z administratorem Twojego serwera domowego aby uzyskać więcej informacji
+ Wygląda na to, że Twój serwer domowy jeszcze nie obsługuje Przestrzeni
+ Lubisz eksperymentować\?
+\nMożesz dodać istniejącą Przestrzeń do innej Przestrzeni.
+ Wszystkie pokoje, w których jesteś będą pokazywane na ekranie domowym.
+ Pokaż wszystkie pokoje w ekranie domowym
+ Zarządzaj pokojami oraz przestrzeniami
+ Oznacz jako nie sugerowana
+ Oznacz jako sugerowana
+ Sugerowane
+ Ustaw tą Przestrzeń jako publiczną
+ Zarządzaj pokojami
+ Szukasz kogoś , kto nie jest w %s\?
+ %s Cię zaprasza
+ Ostrzeżenie! Wymaga wsparcia serwera oraz eksperymentalnej wersji pokoju
+ Przestrzeń eksperymentalna - Pokój ograniczony.
+ Zostałeś zaproszony
+ Przestrzenie są nową metodą na zarządzanie pokojami i osobami.
+ Witamy w Przestrzeniach!
+ Dodaj pokoje
+ Dodaj przestrzeń do jakiejkolwiek przestrzeni którą zarządzasz.
+ Dodaj istniejące przestrzenie
+ Dodaj istniejące pokoje
+ Dodaj istniejące pokoje i przestrzenie
+ Wybierz aby opuścić
+ Opuść wybrane pokoje i przestrzenie…
+ Nie opuszczaj żadnych pokoi i przestrzeni
+ Opuścisz wszystkie pokoje i przestrzenie w %s.
+ Opuść wszystkie pokoje i przestrzenie
+ Jesteś jedynym administratorem tej przestrzeni. Opuszczenie jej oznacza brak kontroli nad nią.
+ Nie będziesz w stanie ponownie dołączyć, do momentu kiedy nie zostaniesz ponownie zaproszony.
+ Jesteś jedyną osoba tutaj. Jeśli wyjdziesz, nikt nie będzie w stanie dołączyć w przyszłości, włączając Ciebie.
+ Czy jesteś pewny, że chcesz opuścić %s\?
+ Opuść przestrzeń
+ Dodawaj pokoje
+ Przeglądaj pokoje
+ Witaj w %1$s, %2$s.
+ Jeszcze nie jesteś w żadnym pokoju. Poniżej znajdują się proponowane, możesz też zobaczyć więcej naciskając zielony przycisk na dole po prawej.
+ Ukończ konfigurację
+ Zaproś przez email, znajdź kontakty i więcej…
+ Aktualnie nie używasz serwera tożsamości. Aby zapraszać znajomych i być dla nich widoczny, skonfiguruj go poniżej.
+ Ten alias na razie nie jest dostępny.
+\nSpróbuj ponownie później lub zapytaj administratora tego pokoju czy masz dostęp.
+ Dołącz pomimo to
+ Dołącz do przestrzeni
+ Stwórz przestrzeń
+ Na razie pomiń
+ Dołącz do mojej przestrzeni %1$s %2$s
+ Nie będą częścią %s
+ Tylko do tego pokoju
+ Będą w stanie przeglądać %s
+ Zaproś do %s
+ Udostępnij link
+ Zaproś przez nazwę użytkownika lub email
+ Zaproś przez nazwę użytkownika
+ Zaproś przez email
+ Aktualnie jesteś tylko Ty. %s będzie jeszcze lepsza kiedy dołączą inni.
+ Zaproś do %s
+ Zaproś osoby
+ Zaproś osoby do Twojej przestrzeni
+ Opis
+ Tworzenie przestrzeni…
+ Losowy
+ Ogólny
+ Kim są Twoi znajomi \?
+ Stworzymy dla nich pokoje. Możesz też dodać następne w późniejszym etapie.
+ Jaki rodzaj dyskusji chcesz mieć w %s\?
+ Nadaj nazwę aby kontynuować.
+ Stwórz przestrzeń
+ Tylko z zaproszeniem, najlepsza dla Ciebie lub zespołów
+ Prywatna
+ Otwarta dla każdego, najlepsza dla społeczności
+ Publiczna
+ Prywatna przestrzeń dla Ciebie i Twoich znajomych
+ Ja i moi znajomi
+ Prywatna przestrzeń do organizacji Twoich pokoi
+ Tylko ja
+ Upewnij się, że odpowiednie osoby mają dostęp do %s. Możesz zmienić to później.
+ Z kim pracujesz\?
+ Aby dołączyć do istniejącej przestrzeni, potrzebujesz zaproszenia.
+ Możesz zmienić to później
+ Jaki rodzaj przestrzeni chcesz stworzyć\?
+ Przestrzenie są nowym sposobem na organizację pokojów i osób
+ Wiadomość wysłana
+ Wstępna synchronizacja:
+\nPobieranie danych…
+ Wstępna synchronizacja:
+\nCzekanie na odpowiedź serwera…
+ Pusty pokój (było %s)
+
+ %1$s, %2$s, %3$s i %4$d innych
+ %1$s, %2$s, %3$s i %4$d innych
+
+
+
+ %1$s, %2$s, %3$s i %4$s
+ Niestandardowe
+ Niestandardowe (%1$d)
+ Domyślne
+ Moderator
+ Administrator
+ Wideokonferencja zakończona przez %1$s
+ Rozpocząłeś wideokonferencję
+ Wideokonferencja rozpoczęta przez %1$s
+ Wysłałeś zaproszenie do %1$s aby dołączył do pokoju
+ Zaktualizowałeś swój profil %1$s
+ Usunąłeś(aś) awatar pokoju
+ Usunąłeś(aś) nazwę pokoju
+ Zmieniłeś listę kontroli dostępu ACL dla tego pokoju.
+ %s zmienił listę kontroli dostępu (ACL) dla tego pokoju.
+ • Dopasowywanie serwerów po ciągach IP jest zabronione.
+ • Dozwolone jest dopasowywanie serwera po ciągach IP.
+ %1$s zbanował(a) %2$s. Powód: %3$s
+ Odbanowałeś(aś) %1$s. Powód: %2$s
+ %1$s zdjął(ęła) bana %2$s. Powód: %3$s
+ Wyrzuciłeś(aś) %1$s. Powód: %2$s
+ Zażądałeś konferencji VoIP
+ 🎉 Wszystkie serwery zostały zbanowane od uczestnictwa. Ten pokój nie może być już używany.
+ Bez zmian.
+ Zaprosiłeś %1$s
+ %1$s zaprosił %2$s
+ %1$s zezwolił gościom dołączać do tego pokoju.
+ Zezwoliłeś gościom na dołączanie do tego pokoju.
+ %1$s zezwolił gościom dołączać do tego pokoju.
+ Zmieniłeś adres tego pokoju.
+ %1$s usunął avatar pokoju
+ Usunąłeś temat pokoju
+ %1$s zaakceptował zaproszenie dla %2$s. Powód: %3$s
+ Wycofałeś zaproszenie do pokoju dla %1$s. Powód: %2$s
+ %1$s wycofał zaproszenie do pokoju dla %2$s. Powód: %3$s
+ Wysłałeś zaproszenie do pokoju do %1$s. Powód: %2$s
+ %1$s wysłał zaproszenie do pokoju do %2$s . Powód: %3$s
+ Zbanowałeś %1$s. Powód: %2$s
+ %1$s wyrzucił %2$s. Powód: %3$s
+ Odrzuciłeś/aś zaproszenie. Powód: %1$s
+ %1$s odrzucił/a zaproszenie. Powód: %2$s
+ Wyszedłeś. Powód: %1$s
+ %1$s wyszedł. Powód: %2$s
+ Opuściłeś/aś pokój. Powód: %1$s
+ %1$s opuścił/a pokój. Powód: %2$s
+ Spotkania wykorzystują polityki bezpieczeństwa i uprawnień Jitsi. Wszystkie osoby obecne w danej chwili w pokoju zobaczą zaproszenie do dołączenia w momencie rozpoczęcia spotkania.
+ Serwer tożsamości nie udostępnił swojej polityki
+ Ukryj politykę serwera tożsamości
+ Wyświetl politykę serwera tożsamości
+ Otwórz ustawienia Poznawania
+ Wyszukaj nazwę
+ Wyszukaj po nazwie, ID lub mailu
+ Kompresowanie filmu %d%%
+ Kompresowanie obrazu…
+ Podziel się opinią
+ Nie udało się przesłać opinii (%s)
+ Dziękujemy, Twoja opinia została wysłana
+ Zachęcamy do kontaktu, jeśli masz dodatkowe pytania
+ Używasz przestrzeni w wersji beta. Ta opinia pomoże nam w tworzeniu kolejnych wersji. Twoja platforma i nazwa użytkownika zostaną odnotowane, abyśmy mogli w pełni wykorzystać Twoje sugestie.
+ Prześlij opinię o przestrzeniach
+ Stwórz nową przestrzeń
+ Wyświetla informacje o użytkowniku
+ Zmienia Twój awatar tylko w tym pokoju
+ Zmienia awatar obecnego pokoju
+ Zmienia Twój wyświetlany pseudonim tylko w tym pokoju
+ Ustawia nazwę pokoju
+ Przestaje ignorować użytkownika, od teraz jego wiadomości będą dla Ciebie widoczne
+ Ignoruje użytkownika, ukrywając dla Ciebie jego wiadomości
+ Opuścić obecną konferencję i przejść do innej\?
+ Wystąpił błąd podczas próby dołączenia do konferencji
+ Ten serwer znajduje się już na liście
+ Nie można odnaleźć tego serwera lub jego listy pokoi
+ Wprowadź nazwę nowego serwera, który chcesz odkrywać.
+ Dodaj nowy serwer
+ Twój serwer
+ Wersja pokoju
+ Inne przestrzenie lub pokoje, których możesz nie znać
+ Przestrzeń, o której wiesz, że zawiera ten pokój
+ Zdecyduj kto może odnaleźć i dołączyć do tego pokoju.
+ Dotknij, aby edytować przestrzenie
+ Wybierz przestrzenie
+ Zdecyduj które przestrzenie mogą mieć dostęp do tego pokoju. Członkowie wybranej przestrzeni będą mogli odnaleźć i dołączyć do nazwy pokoju.
+ Przestrzenie mogące uzyskać dostęp
+ Zezwól użytkownikom przestrzeni na znalezienie i dostęp.
+ Ulepszenia pokoju
+ wyproszenie użytkownika zaskutkuje usunięciem go z tej przestrzeni.
+\n
+\nAby uniemożliwić mu ponowne dołączenie, należy go zbanować.
+ Pokaż wszystkie pokoje w katalogu pokoi (również te zawierające treści dla dorosłych).
+ Pokaż pokoje z treścią dla dorosłych
+ %1$s zmienił(a) alternatywne adresy dla tego pokoju.
+ Dodano %1$s i usunięto %2$s jako adresy tego pokoju.
+ %1$s dodał(a) %2$s i usunął(eła) %3$s jako adres tego pokoju.
+ Odrzucono zaproszenie od %1$s. Powód: %2$s
+ %1$s odrzucił(a) zaproszenie %2$s. Powód: %3$s
+ Usuń nagranie
+ Opinie
+ Synchronizacja klucza samopodpisującego (Self Signing key)
+ Weryfikacja ręczna poprzez tekst
+ lub innego klienta Matrix z krzyżową weryfikacją nowych sesji logowania
\ No newline at end of file
diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml
index 8ef17a33d9..e25319e426 100644
--- a/vector/src/main/res/values-pt-rBR/strings.xml
+++ b/vector/src/main/res/values-pt-rBR/strings.xml
@@ -124,22 +124,23 @@
Você mudou o nível de poder de %1$s.%1$s mudou o nível de poder de %2$s.%1$s de %2$s para %3$s
- Sinc Inicial:
+ Sinc inicial:
\nImportando conta…
- Sinc Inicial:
+ Sinc inicial:
\nImportando crypto
- Sinc Inicial:
+ Sinc inicial:
\nImportando Salas
- Sinc Inicial:
-\nImportando Salas Que Você Se Juntou
- Sinc Inicial:
-\nImportando Salas Que Você Foi Convidada(o)
- Sinc Inicial:
-\nImportando Salas de Que Você Saiu
- Sinc Inicial:
-\nImportando Comunidades
- Sinc Inicial:
-\nImportando Dados de Conta
+ Sinc inicial:
+\nCarregando suas conversas
+\nSe você tem se juntado a um monte de salas, isto poderia levar um momento
+ Sinc inicial:
+\nImportando salas convidadas
+ Sinc inicial:
+\nImportando salas saídas
+ Sinc inicial:
+\nImportando comunidades
+ Sinc inicial:
+\nImportando dados de contaEnviando mensagem…Limpar fila de envioConvite de %1$s. Razão: %2$s
@@ -2697,9 +2698,9 @@
Diretório de salasNovo valorAlterar
- Sinc Inicial:
+ Sinc inicial:
\nFazendo download de dados…
- Sinc Inicial:
+ Sinc inicial:
\nEsperando por resposta de servidor…Nível de confiança confiadoNível de confiança alerta
@@ -3075,4 +3076,7 @@
IndisponívelOfflineOnline
+ Escolher servidorcasa
+ Não dá para alcançar um servidorcasa na URL %s. Por favor cheque seu link ou escolha um servidorcasa manualmente.
+ À escuta por notificações
\ No newline at end of file
diff --git a/vector/src/main/res/values-ro/strings.xml b/vector/src/main/res/values-ro/strings.xml
index 1c45088417..464a590f62 100644
--- a/vector/src/main/res/values-ro/strings.xml
+++ b/vector/src/main/res/values-ro/strings.xml
@@ -354,11 +354,12 @@
Sincronizare inițială:
\nSe importă camerele la care a-ți primit invitațiiSincronizare inițială:
-\nSe importă camerele la care v-ați alăturat
+\nSe încarcă conversațiile tale
+\nDacă te-ai alăturat mai multe camere, ar putea dura puțin mai multSincronizare inițială:
\nSe importa camereleSincronizare inițială:
-\nSe importă crypto
+\nSe importă datele criptograficeSincronizare inițială:
\nSe importă contul…Sincronizare inițială:
@@ -393,4 +394,111 @@
Acum sunt permise serverele ce au adrese IP ce se potrivesc.Sunt blocate serverele ce au adrese IP ce se potrivesc.Sunt permise serverele ce au adrese IP ce se potrivesc.
+ Se creează o copie a cheilor criptografice.…
+ Dacă vă deconectați acum, veți pierde mesajele criptate
+ Copierea cheilor nu sa încheiat, vă rugăm așteptați…
+ Folosește cheia de rezervă
+ Copia cheii de criptare
+ Detalii despre comunitate
+ Raport de erori
+ Istoricul activitățiilor
+ Detalii despre membri
+ Backup
+ Ești sigur \?
+ Folosește backup-ul cheii de criptare
+ Backup-ul cheii de criptare ar trebui să fie activ pe toate dispozitivele tale, pentru a evita pierderea mesajelor tale criptate.
+ Se creează backup-ul cheii de criptare. Daca te deconectezi de la aplicație acum vei pierde accesul la mesajele tale criptate.
+ Se ascultă notificările
+ Setările implicite ale sistemului
+ Tu ai pornit criptarea end-to-end (algoritm nerecunoscut %1$s).
+ %1$s a pornit criptarea end-to-end (algoritm nerecunoscut %2$s).
+ Tu ai pornit criptarea end-to-end.
+ %1$s a pornit criptarea end-to-end.
+ Tu ai împiedicat invitați din a se alătura acestei camere.
+ %1$s a împiedicat invitații din a se alătura acestei camere.
+ Tu ai împiedicat invitați să se alăture acestei camere.
+ %1$s a împiedicat invitații din a se alătura acestei camere.
+ Tu ai permis invitațiilor să se alăture aici.
+ %1$s a permis invitațiilor să se alăture aici.
+ Ai permis invitaților să se alăture acestei camere.
+ %1$s a permis invitațiilor să se alăture acestei camere.
+ Ai schimbat adresele pentru această cameră.
+ %1$s a schimbat adresele pentru aceasta cameră.
+ Ai schimbat adresele principală și alternativă pentru această cameră.
+ %1$s a schimbat adresele principală și secundară pentru această cameră.
+ Ai schimbat adresele alternative pentru această cameră.
+ %1$s a modificat adresele alternative pentru această cameră.
+
+ Ai șters adresa alternativă %1$s pentru aceasta cameră.
+ Ai șters adresele alternative %1$s pentru aceste camere.
+ Ai șters adresele alternative %1$s pentru aceste camere.
+
+
+ %1$s a șters adresa alternativă %2$s pentru această cameră.
+ %1$s a șters adresele alternative %2$s pentru această cameră.
+ %1$s a șters adresele alternative %2$s pentru această cameră.
+
+
+ Ai adăugat adresa alternativă %1$spentru această cameră.
+ Ai adăugat adresele alternative %1$s pentru această cameră.
+ Ai adăugat adresele alternative %1$s pentru această cameră.
+
+
+ %1$s a adăugat adresa alternativă %2$s pentru aceasta cameră.
+ %1$s a adăugat adresele alternative %2$s pentru această cameră.
+ %1$s a adăugat adresele alternative %2$s pentru această cameră.
+
+ Ai șters adresa principală pentru această cameră.
+ %1$s a șters adresa principală pentru această cameră.
+ Ai setat adresa principală pentru această cameră ca fiind %1$s.
+ %1$s a setat adresa principală pentru această adresa ca fiind %2$s.
+ Ai adăugat %1$s și îndepărtat %2$s ca adrese pentru această cameră.
+ %1$s a adăugat %2$s și îndepărtat %3$s ca adrese pentru această cameră.
+
+ Ai îndepărtat %1$s ca adresă pentru această adresă.
+ Ai îndepărtat %1$s ca adrese pentru această adresă.
+ Ai îndepărtat %1$s ca adrese pentru această adresă.
+
+
+ %1$s a retras %2$s ca o adresă pentru această cameră.
+ %1$s a retras %2$s ca adrese pentru această cameră.
+ %1$s a retras %2$s ca adrese pentru această cameră.
+
+
+ Ai adăugat %1$s ca o adresă pentru această cameră.
+ Ai adăugat %1$s ca adrese pentru această cameră.
+ Ai adăugat %1$s ca adrese pentru această cameră.
+
+
+ %1$s a adăugat pe %2$s ca o adresă pentru această cameră.
+ %1$s a adăugat pe %2$s ca adrese pentru această cameră.
+ %1$s a adăugat pe %2$s ca adrese pentru această cameră.
+
+ Ți-ai retras invitația către %1$s. Motivul: %2$s
+ %1$s și-a retras invitația către %2$s. Motivul: %3$s
+ %1$s la evacuat pe %2$s. Motivul: %3$s
+ Tu ai invitat %1$s. Motivul: %2$s
+ Ai acceptat invitaţia pentru %1$s. Motivul: %2$s
+ %1$s a acceptat invitaţia pentru %2$s. Motivul: %3$s
+ Ai retras invitaţia către %1$s de alăturare acestei camere. Motivul: %2$s
+ %1$s a retras invitaţia către %2$s de alăturare acestei camere. Motivul: %3$s
+ Tu ai trimis o invitaţie către %1$s pentru a se al\\ătura camerei. Motivul: %2$s
+ %1$s a trimis o invitaţie către %2$s pentru a se alătura camerei. Motivul: %3$s
+ Ai blocat utilizatorul %1$s. Motivul: %2$s
+ %1$s a blocat utilizatorul %2$s. Motivul: %3$s
+ Ai deblocat utilizatorul %1$s. Motivul: %2$s
+ %1$s a blocat pe %2$s. Motivul: %3$s
+ Ai dat afară utilizatorul %1$s. Motivul: %2$s
+ Ai respins invitaţia. Motivul: %1$s
+ %1$s a respins invitaţia. Motivul: %2$s
+ Ai ieşit. Motivul: %1$s
+ %1$s a părăsit camera. Motivul: %2$s
+ Ai părăsit camera. Motivul: %1$s
+ %1$s a părăsit camera. Motivul: %2$s
+ Tu te-ai alăturat. Motivul: %1$s
+ %1$s s-a alăturat. Motivul: %2$s
+ Tu te-ai alăturat camerei. Motivul: %1$s
+ %1$s s-a alăturat camerei. Motivul: %2$s
+ %1$s te-a invitat. Motivul: %2$s
+ %1$s la invitat pe %2$s. Motivul este: %3$s
\ No newline at end of file
diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml
index 379cd4b1f1..8ca771cdfd 100644
--- a/vector/src/main/res/values-sq/strings.xml
+++ b/vector/src/main/res/values-sq/strings.xml
@@ -62,22 +62,23 @@
Mesazh i hequr [arsye: %1$s]Mesazh i hequr nga %1$s [arsye: %2$s]%s e përmirësoi këtë dhomë.
- Njëkohësimi Fillestar:
+ Njëkohësimi fillestar:
\nPo importohet llogaria…
- Njëkohësimi Fillestar:
+ Njëkohësimi fillestar:
\nPo importohet kriptografi
- Njëkohësimi Fillestar:
+ Njëkohësimi fillestar:
\nPo importohen DhomaNjëkohësimi Fillestar:
-\nPo importohen Dhoma Ku Është Bërë Hyrje
- Njëkohësimi Fillestar:
-\nPo importohen Dhoma Me Ftesë
- Njëkohësimi Fillestar:
-\nPo importohen Dhoma të Braktisura
- Njëkohësimi Fillestar:
-\nPo importohen Bashkësi
- Njëkohësimi Fillestar:
-\nPo importohet të Dhëna Llogarie
+\nPo ngarkohen bisedat tuaja
+\nNëse keni hyrë në shumë dhoma, kjo mund të zgjasë ca
+ Njëkohësimi fillestar:
+\nPo importohen dhoma me ftesë
+ Njëkohësimi fillestar:
+\nPo importohen dhoma të braktisura
+ Njëkohësimi fillestar:
+\nPo importohen bashkësi
+ Njëkohësimi fillestar:
+\nPo importohet të dhëna llogariePo dërgohet mesazh…Spastro radhë pritjeje%1$s shfuqizoi ftesën për %2$s për pjesëmarrje te dhoma
@@ -289,7 +290,7 @@
Shihni Burim të ShfshehtëzuarFshijeRiemërtojeni
- Raportoni lëndë
+ Raportoni LëndëThirrje aktiveS’fillohet dot thirrja, ju lutemi, riprovoni më vonëS’fillohet dot thirrja
@@ -807,7 +808,7 @@
Dëbon përdoruesin me ID-në e dhënëI heq dëbimin përdoruesit me ID-në e dhënëPërcaktoni shkallë pushteti të një përdoruesi
- Hyn në dhomë me aliasin e dhënë
+ Hyn te dhoma me adresën e dhënëDilni nga dhomaCaktoni temë dhomePërzë përdoruesin me ID-në e dhënë
@@ -2657,9 +2658,9 @@
KthehuniNdërrojeMesazh i dërguar
- Njëkohësimi Fillestar:
+ Njëkohësimi fillestar:
\nPo shkarkohen të dhëna…
- Njëkohësimi Fillestar:
+ Njëkohësimi fillestar:
\nPo pritet për përgjigje nga shërbyesi…Hapësirat janë mënyra për të grupuar dhoma dhe persona.Sinjalizimi lyp mbulim nga shërbyesi dhe version eksperimental dhome
@@ -2846,13 +2847,13 @@
Që ta ndalni ose ta dëgjoni, prekni mbi incizimin tuajEdhe %1$dsMbajeni, që të incizojë, lëshojeni që të dërgohet
- Fshije mesazhin zanor të incizuar
+ Fshije inciziminPo incizohet mesazh zanorNdalni Mesazh ZanorLuani Mesazh ZanorKyçje Mesazhi ZanorRrëshqiteni që të anulohet
- Nisni Mesazh Zanor
+ Incizoni Mesazh ZanorLejo cilindo te %s ta gjejë dhe hyjë. Mund të përzgjidhni edhe hapësira të tjera.Lypset Domosdo PërmirësimZë
@@ -2993,4 +2994,7 @@
Shpërfill një përdorues, duke fshehur mesazhet e tij prej jushJo në linjëNë linjë
+ Zgjidhni shërbyes Home
+ Te URL-ja %s s’kapet dot një shërbyes Home. Ju lutemi, kontrolloni lidhjen tuaj, ose zgjidheni një shërbyes Home dorazi.
+ Po dëgjohet për njoftime
\ No newline at end of file
diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml
index 0ec5b0d820..a6969464a2 100644
--- a/vector/src/main/res/values-sv/strings.xml
+++ b/vector/src/main/res/values-sv/strings.xml
@@ -123,7 +123,8 @@
Inledande synk:
\nImporterar rumInledande synk:
-\nImporterar anslutna rum
+\nLaddar dina konversationer
+\nOm du har gått med i många rum så kan detta ta lite tidInledande synk:
\nImporterar inbjudna rumInledande synk:
@@ -2619,9 +2620,9 @@
RumsversionNytt värdeByt
- Initial synk:
+ Inledande synk:
\nLaddar ner data…
- Initial synk:
+ Inledande synk:
\nVäntar på serversvar…Visa läskvittonAvisera inte
@@ -3005,4 +3006,7 @@
OtillgängligOfflineOnline
+ Välj hemserver
+ Kan inte nå en hemserver på URL:en %s. Vänligen kontrollera din länk eller välj en hemserver manuellt.
+ Lyssnar efter aviseringar
\ No newline at end of file
diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml
index b6d52c59e4..c652c1d217 100644
--- a/vector/src/main/res/values-uk/strings.xml
+++ b/vector/src/main/res/values-uk/strings.xml
@@ -148,7 +148,8 @@
Початкова синхронізація:
\nІмпортування запрошень до кімнатПочаткова синхронізація:
-\nІмпортування кімнат, до яких ви приєдналися
+\nЗавантаження ваших бесід
+\nЯкщо ви приєдналися до багатьох кімнат, це може тривати досить довгоПочаткова синхронізація:
\nІмпортування кімнатПочаткова синхронізація:
@@ -764,7 +765,7 @@
Розмір шрифтуКрихітнийДрібний
- Нормальний
+ ЗвичайнийЧималийВеликийНайбільший
@@ -834,7 +835,7 @@
Без звукуДодати ярлик на головний екранПриватність сповіщень
- Нормальний
+ ЗвичайнийНадіслати наліпкуНадіслати наліпкуУ вас поки що не має наліпок.
@@ -1308,7 +1309,7 @@
ДодатиНаради використовують політику безпеки та дозволів Jitsi. Усі люди, які зараз перебувають у кімнаті, побачать запрошення приєднатися під час вашої зустрічі.Почати спілкування
- Додати спеціальну вкладку для непрочитаних сповіщень на головному екрані.
+ Додати спеціальну вкладку для непрочитаних сповіщень на головний екран.Показувати повну історію зашифрованих кімнатДані облікового записуІнструменти розробника
@@ -1463,7 +1464,7 @@
Опублікувати цю адресуДодати локальну адресуКімната не має локальної адреси
- Встановіть адреси для цієї кімнати, щоб користувачі могли знаходити цю кімнату через ваш домашній сервер (%1$s)
+ Укажіть адреси для цієї кімнати, щоб користувачі могли знаходити цю кімнату через ваш домашній сервер (%1$s)Локальні адресиНова опублікована адреса (наприклад, #alias:server)Інших опублікованих адрес поки що немає.
@@ -1476,7 +1477,7 @@
Інші опубліковані адреси:Основна адресаЦе основна адреса
- Загальнодоступні адреси може використовувати будь-хто на будь-якому сервері для приєднання до вашої кімнати. Щоб опублікувати адресу спочатку встановіть її локальною адресою.
+ Загальнодоступні адреси може використовувати будь-хто на будь-якому сервері для приєднання до вашої кімнати. Щоб опублікувати адресу спочатку вкажіть її локальною адресою.Загальнодоступні адресиАдреси кімнатПерегляньте та керуйте адресами цієї кімнати та їхньою видимістю у каталозі кімнат.
@@ -2871,4 +2872,10 @@
Змінює ваше показуване ім\'я лише у поточній кімнатіУстановлює назву кімнатиСервер ідентифікації не надав жодних правил
+ Попередження: вимагає підтримки сервера та експериментальної версії кімнати
+ Бажаєте поекспериментувати\?
+\nМожете додати наявні простори до простору.
+ Експериментальний простір - обмежена кімната.
+ Усі кімнати, у яких ви перебуваєте, буде показано на сторінці Домівка.
+ Показувати у Домівці
\ No newline at end of file
diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml
index feff737e27..72f86d24e5 100644
--- a/vector/src/main/res/values-zh-rCN/strings.xml
+++ b/vector/src/main/res/values-zh-rCN/strings.xml
@@ -67,13 +67,14 @@
初始化同步:
\n正在导入聊天室初始化同步:
-\n正在导入已加入的聊天室
+\n正在加载对话
+\n如果你加入了很多论聊天室,这可能需要一段时间初始化同步:
\n正在导入已邀请的聊天室初始化同步:
\n正在导入已离开的聊天室初始化同步:
-\n正在导入社群
+\n正在导入社区初始化同步:
\n正在导入账号数据%s 升级了此聊天室。
@@ -2959,4 +2960,7 @@
不可用离线在线
+ 选择主服务器
+ 无法访问 URL %s 上的主服务器。请检查您的链接或手动选择一个主服务器。
+ 侦听通知
\ No newline at end of file
diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml
index df611eceb7..27fd4910cf 100644
--- a/vector/src/main/res/values-zh-rTW/strings.xml
+++ b/vector/src/main/res/values-zh-rTW/strings.xml
@@ -67,7 +67,8 @@
初始化同步:
\n正在匯入聊天室初始化同步:
-\n正在匯入已加入的聊天室
+\n正在載入您的對話
+\n若您加入了很多聊天室,這可能會花一點時間初始化同步:
\n正在匯入已邀請的聊天室初始化同步:
@@ -2949,4 +2950,7 @@
不可用離線線上
+ 選擇家伺服器
+ 無法存取 URL %s 的家伺服器。請檢查您的連結或手動選擇家伺服器。
+ 監聽通知
\ No newline at end of file
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 1c0a1ba9f1..dec9e6e8db 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -2898,7 +2898,7 @@
QR code
- Almost there! Is %s showing the same shield?
+ Almost there! Is %s showing a tick?YesNo
@@ -3051,7 +3051,7 @@
Add a topic"Topic: "
- Almost there! Is the other device showing the same shield?
+ Almost there! Is the other device showing a tick?Almost there! Waiting for confirmation…Waiting for %s…
diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml
index 1f7c347ddd..14c7dc7b80 100644
--- a/vector/src/main/res/xml/vector_settings_preferences.xml
+++ b/vector/src/main/res/xml/vector_settings_preferences.xml
@@ -35,8 +35,8 @@
+ android:summary="@string/all_rooms_youre_in_will_be_shown_in_home"
+ android:title="@string/preference_show_all_rooms_in_home" />
@@ -121,20 +121,6 @@
android:summary="@string/settings_chat_effects_description"
android:title="@string/settings_chat_effects_title" />
-
-
-
-
) = NotificationEventQueue(events.toMutableList())
+ private fun givenQueue(events: List) = NotificationEventQueue(events.toMutableList(), seenEventIds = seenIdsCache)
}