diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index be175c0436..8832499c43 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,14 +25,10 @@ jobs: cancel-in-progress: true steps: - uses: actions/checkout@v3 - - uses: actions/cache@v3 + - name: Configure gradle + uses: gradle/gradle-build-action@v2 with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- + cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Assemble ${{ matrix.target }} debug apk run: ./gradlew assemble${{ matrix.target }}Debug $CI_GRADLE_ARG_PROPERTIES - name: Upload ${{ matrix.target }} debug APKs @@ -50,14 +46,10 @@ jobs: cancel-in-progress: ${{ github.ref != 'refs/head/main' }} steps: - uses: actions/checkout@v3 - - uses: actions/cache@v3 + - name: Configure gradle + uses: gradle/gradle-build-action@v2 with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- + cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Assemble GPlay unsigned apk run: ./gradlew clean assembleGplayRelease $CI_GRADLE_ARG_PROPERTIES - name: Upload Gplay unsigned APKs diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index 4901a84070..91352bb27b 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -11,7 +11,7 @@ jobs: - run: | npm install --save-dev @babel/plugin-transform-flow-strip-types - name: Danger - uses: danger/danger-js@11.2.1 + uses: danger/danger-js@11.2.2 with: args: "--dangerfile ./tools/danger/dangerfile.js" env: diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 51c1b32e82..2f53964ebb 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -19,14 +19,10 @@ jobs: uses: actions/setup-python@v4 with: python-version: 3.8 - - uses: actions/cache@v3 + - name: Configure gradle + uses: gradle/gradle-build-action@v2 with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- + cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Install towncrier run: | python3 -m pip install towncrier diff --git a/.github/workflows/post-pr.yml b/.github/workflows/post-pr.yml index af854bf371..0245fcdd34 100644 --- a/.github/workflows/post-pr.yml +++ b/.github/workflows/post-pr.yml @@ -44,14 +44,14 @@ jobs: uses: actions/setup-python@v4 with: python-version: 3.8 - - uses: actions/cache@v3 + - uses: actions/setup-java@v3 with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- + distribution: 'adopt' + java-version: '11' + - name: Configure gradle + uses: gradle/gradle-build-action@v2 + with: + cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Start synapse server uses: michaelkaye/setup-matrix-synapse@v1.0.4 with: @@ -59,10 +59,6 @@ jobs: httpPort: 8080 disableRateLimiting: true public_baseurl: "http://10.0.2.2:8080/" - - uses: actions/setup-java@v3 - with: - distribution: 'adopt' - java-version: '11' - name: Run sanity tests on API ${{ matrix.api-level }} uses: reactivecircus/android-emulator-runner@v2 with: diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index c32cb65c42..e8c56ba67f 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -66,7 +66,7 @@ jobs: yarn add danger-plugin-lint-report --dev - name: Danger lint if: always() - uses: danger/danger-js@11.2.1 + uses: danger/danger-js@11.2.2 with: args: "--dangerfile ./tools/danger/dangerfile-lint.js" env: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 931ec2da45..d9ae49a5f0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -139,14 +139,10 @@ jobs: # with: # distribution: 'adopt' # java-version: 11 -# - uses: actions/cache@v3 +# - name: Configure gradle +# uses: gradle/gradle-build-action@v2 # with: -# path: | -# ~/.gradle/caches -# ~/.gradle/wrapper -# key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} -# restore-keys: | -# ${{ runner.os }}-gradle- +# cache-read-only: ${{ github.ref != 'refs/heads/develop' }} # - name: Build Android Tests # run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES diff --git a/CHANGES.md b/CHANGES.md index 15b0a76b23..76b46bbbe7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,43 @@ +Changes in Element v1.5.22 (2023-01-25) +======================================= + +Features ✨ +---------- + - [Poll] Warning message on decryption failure of some events ([#7824](https://github.com/vector-im/element-android/issues/7824)) + - [Poll] Render ended polls ([#7900](https://github.com/vector-im/element-android/issues/7900)) + - [Rich text editor] Update list item bullet appearance ([#7930](https://github.com/vector-im/element-android/issues/7930)) + - [Voice Broadcast] Handle connection errors while recording ([#7890](https://github.com/vector-im/element-android/issues/7890)) + - [Voice Broadcast] Use MSC3912 to delete server side all the related events ([#7967](https://github.com/vector-im/element-android/issues/7967)) + +Bugfixes 🐛 +---------- +- Fix OOM crashes. ([#7962](https://github.com/vector-im/element-android/issues/7962)) +- Fix can't get out of a verification dialog ([#4025](https://github.com/vector-im/element-android/issues/4025)) +- Fix rendering of edited polls ([#7938](https://github.com/vector-im/element-android/issues/7938)) +- [Voice Broadcast] Fix unexpected "live broadcast" in the room list ([#7832](https://github.com/vector-im/element-android/issues/7832)) +- Send voice message should not be allowed during a voice broadcast recording ([#7895](https://github.com/vector-im/element-android/issues/7895)) +- Voice Broadcast - Fix playback scrubbing not working if the playback is in a stopped state ([#7961](https://github.com/vector-im/element-android/issues/7961)) +- Handle exceptions when listening a voice broadcast ([#7829](https://github.com/vector-im/element-android/issues/7829)) + +In development 🚧 +---------------- + - [Voice Broadcast] Only display a notification on the first voice chunk ([#7845](https://github.com/vector-im/element-android/issues/7845)) + - [Poll] History list: Load more UI mechanism ([#7864](https://github.com/vector-im/element-android/issues/7864)) + +SDK API changes ⚠️ +------------------ + - Implement [MSC3912](https://github.com/matrix-org/matrix-spec-proposals/pull/3912): Relation-based redactions ([#7988](https://github.com/vector-im/element-android/issues/7988)) + +Other changes +------------- + - Upgrade to Kotlin 1.8 ([#7936](https://github.com/vector-im/element-android/issues/7936)) + - Sentry: Report sync duration and metrics for initial sync and for sync after pause. Not for regular sync. ([#7960](https://github.com/vector-im/element-android/issues/7960)) + - [Voice Broadcast] Rework internal media players coordination ([#7979](https://github.com/vector-im/element-android/issues/7979)) + - Support reactions on Voice Broadcast ([#7807](https://github.com/vector-im/element-android/issues/7807)) + - Pause voice broadcast listening on new VB recording ([#7830](https://github.com/vector-im/element-android/issues/7830)) + - Tapping slightly left or right of the 30s buttons highlights the whole cell instead of registering as button presses ([#7929](https://github.com/vector-im/element-android/issues/7929)) + + Changes in Element v1.5.20 (2023-01-10) ======================================= diff --git a/build.gradle b/build.gradle index 850a4143c9..3e8233fa4d 100644 --- a/build.gradle +++ b/build.gradle @@ -24,12 +24,12 @@ buildscript { classpath libs.gradle.gradlePlugin classpath libs.gradle.kotlinPlugin classpath libs.gradle.hiltPlugin - classpath 'com.google.firebase:firebase-appdistribution-gradle:3.1.1' - classpath 'com.google.gms:google-services:4.3.14' + classpath 'com.google.firebase:firebase-appdistribution-gradle:3.2.0' + classpath 'com.google.gms:google-services:4.3.15' classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.6' classpath "com.likethesalad.android:stem-plugin:2.3.0" - classpath 'org.owasp:dependency-check-gradle:7.4.4' + classpath 'org.owasp:dependency-check-gradle:8.0.1' classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.20" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3' diff --git a/changelog.d/4025.bugfix b/changelog.d/4025.bugfix deleted file mode 100644 index 109da1c830..0000000000 --- a/changelog.d/4025.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix can't get out of a verification dialog diff --git a/changelog.d/7824.feature b/changelog.d/7824.feature deleted file mode 100644 index 3c8b416571..0000000000 --- a/changelog.d/7824.feature +++ /dev/null @@ -1 +0,0 @@ -[Poll] Warning message on decryption failure of some events diff --git a/changelog.d/7829.bugfix b/changelog.d/7829.bugfix deleted file mode 100644 index 705f7310f0..0000000000 --- a/changelog.d/7829.bugfix +++ /dev/null @@ -1 +0,0 @@ -Handle exceptions when listening a voice broadcast diff --git a/changelog.d/7832.bugfix b/changelog.d/7832.bugfix deleted file mode 100644 index 871f9aabb9..0000000000 --- a/changelog.d/7832.bugfix +++ /dev/null @@ -1 +0,0 @@ -[Voice Broadcast] Fix unexpected "live broadcast" in the room list diff --git a/changelog.d/7845.wip b/changelog.d/7845.wip deleted file mode 100644 index 8bce21499a..0000000000 --- a/changelog.d/7845.wip +++ /dev/null @@ -1 +0,0 @@ -[Voice Broadcast] Only display a notification on the first voice chunk diff --git a/changelog.d/7864.wip b/changelog.d/7864.wip deleted file mode 100644 index 9d719d92ff..0000000000 --- a/changelog.d/7864.wip +++ /dev/null @@ -1 +0,0 @@ -[Poll] History list: Load more UI mechanism diff --git a/changelog.d/7900.feature b/changelog.d/7900.feature deleted file mode 100644 index c3cce1e0e6..0000000000 --- a/changelog.d/7900.feature +++ /dev/null @@ -1 +0,0 @@ -Render ended polls diff --git a/changelog.d/7930.feature b/changelog.d/7930.feature deleted file mode 100644 index 7eb779e6ec..0000000000 --- a/changelog.d/7930.feature +++ /dev/null @@ -1 +0,0 @@ -"[Rich text editor] Update list item bullet appearance" \ No newline at end of file diff --git a/changelog.d/7936.misc b/changelog.d/7936.misc deleted file mode 100644 index 8480d9a6bf..0000000000 --- a/changelog.d/7936.misc +++ /dev/null @@ -1 +0,0 @@ -Upgrade to Kotlin 1.8 diff --git a/changelog.d/7938.bugfix b/changelog.d/7938.bugfix deleted file mode 100644 index 70218edf8a..0000000000 --- a/changelog.d/7938.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix rendering of edited polls diff --git a/dependencies.gradle b/dependencies.gradle index 4977543822..5d7286ab1a 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -11,7 +11,7 @@ def gradle = "7.3.1" def kotlin = "1.8.0" def kotlinCoroutines = "1.6.4" def dagger = "2.44.2" -def firebaseBom = "31.1.1" +def firebaseBom = "31.2.0" def appDistribution = "16.0.0-beta05" def retrofit = "2.9.0" def markwon = "4.6.2" @@ -27,7 +27,7 @@ def jjwt = "0.11.5" // Temporary version to unblock #6929. Once 0.16.0 is released we should use it, and revert // the whole commit which set version 0.16.0-SNAPSHOT def vanniktechEmoji = "0.16.0-SNAPSHOT" -def sentry = "6.11.0" +def sentry = "6.12.1" // Use 1.6.0 alpha to fix issue with test def fragment = "1.6.0-alpha04" // Testing @@ -88,7 +88,7 @@ ext.libs = [ 'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution", 'appdistribution' : "com.google.firebase:firebase-appdistribution:$appDistribution", // Phone number https://github.com/google/libphonenumber - 'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.4" + 'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.5" ], dagger : [ 'dagger' : "com.google.dagger:dagger:$dagger", diff --git a/fastlane/metadata/android/az/short_description.txt b/fastlane/metadata/android/az-AZ/short_description.txt similarity index 100% rename from fastlane/metadata/android/az/short_description.txt rename to fastlane/metadata/android/az-AZ/short_description.txt diff --git a/fastlane/metadata/android/az-AZ/title.txt b/fastlane/metadata/android/az-AZ/title.txt new file mode 100644 index 0000000000..907f907f99 --- /dev/null +++ b/fastlane/metadata/android/az-AZ/title.txt @@ -0,0 +1 @@ +Element diff --git a/fastlane/metadata/android/az/title.txt b/fastlane/metadata/android/az/title.txt deleted file mode 100644 index 4ca0ffb55b..0000000000 --- a/fastlane/metadata/android/az/title.txt +++ /dev/null @@ -1 +0,0 @@ -Element - Təhlükəsiz Mesajlaşma diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40105200.txt b/fastlane/metadata/android/cs-CZ/changelogs/40105200.txt new file mode 100644 index 0000000000..70ddac29a2 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Především opravy chyb! +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40105200.txt b/fastlane/metadata/android/de-DE/changelogs/40105200.txt new file mode 100644 index 0000000000..549880cafb --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Die wichtigsten Änderungen in dieser Version: Hauptsächlich Fehlerbeseitigungen! +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40105220.txt b/fastlane/metadata/android/en-US/changelogs/40105220.txt new file mode 100644 index 0000000000..5bf56d6289 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40105220.txt @@ -0,0 +1,2 @@ +Main changes in this version: Mainly improvements on voice broadcast feature. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40105200.txt b/fastlane/metadata/android/et/changelogs/40105200.txt new file mode 100644 index 0000000000..5d5a7fbbf2 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Olulisemad muutused selles versioonis: Põhiliselt veaparandused! +Ingliskeelne muudatuste logi täismahus: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40105200.txt b/fastlane/metadata/android/fa/changelogs/40105200.txt new file mode 100644 index 0000000000..9643f6cbdd --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40105200.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: عموماً رفع اشکال! +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40105200.txt b/fastlane/metadata/android/fr-FR/changelogs/40105200.txt new file mode 100644 index 0000000000..515dd1f882 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Principalement des corrections de bugs ! +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40105200.txt b/fastlane/metadata/android/hu-HU/changelogs/40105200.txt new file mode 100644 index 0000000000..339f7ce136 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Fő változás ebben a verzióban: Leginkább hibajavítások. +Teljes változásnapló: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40105200.txt b/fastlane/metadata/android/id/changelogs/40105200.txt new file mode 100644 index 0000000000..d80e0daa38 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Kebanyakan perbaikan kutu! +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40105200.txt b/fastlane/metadata/android/it-IT/changelogs/40105200.txt new file mode 100644 index 0000000000..6d3bda5395 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: correzione di errori! +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40105200.txt b/fastlane/metadata/android/sk/changelogs/40105200.txt new file mode 100644 index 0000000000..24c1221752 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Hlavne oprava chýb! +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40105160.txt b/fastlane/metadata/android/sq/changelogs/40105160.txt new file mode 100644 index 0000000000..06cbc077c7 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Rrjedhat tani janë të aktivizuara, si parazgjedhje. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40105180.txt b/fastlane/metadata/android/sq/changelogs/40105180.txt new file mode 100644 index 0000000000..216b9368f2 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Tanimë rrjedhat janë të aktivizuara si parazgjedhje. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40105200.txt b/fastlane/metadata/android/sq/changelogs/40105200.txt new file mode 100644 index 0000000000..59fc281c6d --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Kryesisht ndreqje të metash! +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40105130.txt b/fastlane/metadata/android/sv-SE/changelogs/40105130.txt new file mode 100644 index 0000000000..d0f9c996af --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40105130.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Trådar är nu aktivt som förval. +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40105140.txt b/fastlane/metadata/android/sv-SE/changelogs/40105140.txt new file mode 100644 index 0000000000..d0f9c996af --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40105140.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Trådar är nu aktivt som förval. +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40105160.txt b/fastlane/metadata/android/sv-SE/changelogs/40105160.txt new file mode 100644 index 0000000000..d0f9c996af --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Trådar är nu aktivt som förval. +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40105180.txt b/fastlane/metadata/android/sv-SE/changelogs/40105180.txt new file mode 100644 index 0000000000..d0f9c996af --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Trådar är nu aktivt som förval. +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40105200.txt b/fastlane/metadata/android/sv-SE/changelogs/40105200.txt new file mode 100644 index 0000000000..21c54d9fd3 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Huvudsakligen byggfixar! +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40105200.txt b/fastlane/metadata/android/uk/changelogs/40105200.txt new file mode 100644 index 0000000000..202037bfdc --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40105200.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Виправлення помилок! +Перелік усіх змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40105200.txt b/fastlane/metadata/android/zh-TW/changelogs/40105200.txt new file mode 100644 index 0000000000..960dc177be --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40105200.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:主要是臭蟲修復! +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/library/ui-strings/src/main/res/values-ar/strings.xml b/library/ui-strings/src/main/res/values-ar/strings.xml index a49ecc3d08..395b4c70a8 100644 --- a/library/ui-strings/src/main/res/values-ar/strings.xml +++ b/library/ui-strings/src/main/res/values-ar/strings.xml @@ -16,7 +16,7 @@ غيّر %1$s اسم الغُرفة إلى: %2$s أجابَ %s على المُكالمة. أنهى %s المُكالمة. - جعلَ %1$s التأريخ المُستقبلي للغُرفة مرئيًا لـ %2$s + جعلَ %1$s عند انشاء الغرف لاحقاً تكون مرئية ل%2$s جميع أعضاء الغُرفة، مِنَ اللَّحظة التي تمَّت دعوتهم. جميع أعضاء الغُرفة، مِن لحظة إنضمامهم. جميع أعضاء الغُرفة. @@ -63,7 +63,7 @@ أرسلتَ بيانات لإعداد مُكالمة. أجبتَ على المُكالمة. أنهيتَ المُكالمة. - جعلتَ التأريخ المُستقبلي للغُرفة مرئيًا لـ %1$s + لقدت جعلت الغرفة التي سيتم انشائها مرئيًا لـ %1$s رقّى %s هذه الغرفة. رقَّيتَ هذه الغرفة. أزلتَ اسم الغُرفة @@ -93,9 +93,9 @@ لا تغيير. • خوادِم مُطابقة IP الحرفية محظورة الآن. • الخوادِم المُطابقة لـ %s أُزيلت مِن قائمة السماح. - • الخوادِم المُطابقة لـ %s مسموحة الآن. + • الخوادِم المُطابقة لـ %s اصبحت مسموحة الآن. • الخوادِم المُطابقة لـ %s أُزيلت مِن قائمة الحظر. - • الخوادِم المُطابقة لـ %s محظورة الآن. + • الخوادِم مُتطابقة لـ %s واصبحت محظورة الآن. • خوادِم مُطابقة IP الحرفية مسموحة الآن. لقد غَيَّرت قائمة الوصول لهذه الغُرفة. غَيَّرَ %s قائمة التحكم بالوصول (ACL) لهذه الغُرفة. @@ -234,7 +234,7 @@ انتهت المكالمة مكالمة صورية واردة مكالمة صوتية واردة - المكالمة جارية + المكالمة جارية… معلومات نعم لا @@ -291,9 +291,7 @@ كلمة السر الجديدة فشل تحديث كلمة السر حُدّثت كلمة السر - أأعرض كل رسائل ⁨%s⁩؟ - -سيُعيد هذا الإجراء تشغيل التطبيق وقد يأخذ بعض الوقت. + أأعرض كل الرسائل من ⁨%s⁩؟ سيُعيد هذا الإجراء تشغيل التطبيق وقد يأخذ بعض الوقت. اختر دولة ٣ أيام أسبوع واحد @@ -471,7 +469,7 @@ رقم الهاتف مستخدم بالفعل معطّل مزعج - لا ترسل من هذا الجهاز الرسائل المعمّاة إلى الأجهزة غير المؤكّدة + لا ترسل من هذا الجهاز الرسائل المشفرة إلى الأجهزة غير الموثقة. عمِّ إلى الأجهزة المؤكّدة فقط <b>غير<b/> مؤكّدة مؤكّدة @@ -482,7 +480,7 @@ إن قال مدير الخادوم بأن هذا متوقع، فتأكد من أن البصمة أدناه تطابق البصمة التي وفّرها. تغيّرت الشهادة من شهادة كنت تثق بها إلى شهادة لا تثق بها. لربما جدّد الخادوم شهادته. راسل إدارة الخادوم واسألهم عن البصمة المتوقعة. أضِف اختصارا إلى الشاشة الرئيسية - شاشة معلومات التطبيق في النظام + أظهر معلومات التطبيق في إعدادات النظام. دعوات المكالمات ابدأ عن الإقلاع صدّر مفاتيح تعمية الطرفين لغرفة @@ -566,11 +564,7 @@ وصل هذا الخادم الحدّ الأقصى للمستخدمين النشطين شهريًا بذلك لن يستطيع بعض المستخدمين الولوج لحساباتهم. وصل خادوم المنزل هذا حدّ المستخدمين النشطين شهريًا. يجري وصل الاتصال… - سيجعل هذا حسابك محال الاستخدام للأبد. لن تقدر على الولوج ولن يقدر أحد على إعادة التسجيل بنفس معرّف المستخدم. سيتسبب هذا بأن يترك حسابك كل الغرف التي تشارك فيها، وستُزال تفاصيل الحساب من خادوم التعريف. هذا إجراء لا عودة فيه. - -حذفك لحسابك لا يتسبب بأن ننسى رسائلك التي أرسلتها (مبدئيًا). إن أردت ذلك فرجاءً أشّر على المربّع أدناه. - -ظهور الرسائل في «ماترِكس» شبيه كثيرًا بالبريد الإلكتروني. نسياننا لرسائلك يعني أن الرسائل التي أرسلتها لن تُشارك مع أي مستخدم جديد أو غير مسجّل، إلا أن المستخدمين المسجّلين الذي يقدرون على الوصول إليها سيمتلكون نسخة عنها. + سيجعل هذا حسابك محال الاستخدام للأبد. لن تقدر على تسجيل دخولك ولن يقدر أحد على إعادة التسجيل بنفس معرّف المستخدم. سيتسبب هذا بأن يترك حسابك كل الغرف التي تشارك فيها، وستُزال تفاصيل الحساب من خادم التعريف. هذا إجراء لا مجال الرجوع فيه. حذفك لحسابك لا يتسبب بأن ننسى رسائلك التي أرسلتها (مبدئيًا). إن أردت ذلك فرجاءً ضع علامة على المربّع أدناه. ظهور الرسائل في «ماترِكس» يشبه كثيرًا بالبريد الإلكتروني. نسياننا لرسائلك يعني أن الرسائل التي أرسلتها لن تُشارك مع أي مستخدم جديد أو غير مسجّل، إلا أن المستخدمين المسجّلين الذي يقدرون على الوصول إليها سيمتلكون نسخة عنها. الرسائل الآمنة تخطي تم @@ -580,7 +574,7 @@ الإعدادات المتقدمة للإشعارات عند تسجيل الخروج الآن ستخسر مفاتيحك النسخ الاحتياطي المفاتيح ما زال جاريا. في حال خروجك الآن لن تتمكن لاحقا من قراءة الرسائل المعماة. - تأكد من تفعيل النسخ الاحتياطي للمفاتيح على كل أجهزتك كي لا تخسر رسائلك المعماة + تأكد من تفعيل النسخ الاحتياطي للمفاتيح على كل أجهزتك كي لا تخسر رسائلك المشفرة. ينسخ احتياطيا المفاتيح… ليس لديك تصريح لبدء إجتماع ليس لديك تصريح لبدء إجتماع في هذه الغرفة @@ -913,7 +907,7 @@ تحكم في محادثاتك. تخط هذه الخطوة احفظ وتابع - حُفظت تفضيلاتك. + اذهب الى الاعدادات في اي وقت لتغير او تعديل ملفك الشخصي للتطبيق. كل شيئ جاهز! لننطلق يمكنك تغييرها في أي وقت. @@ -997,7 +991,7 @@ أنت تستعرض هذا النقاش سلفًا! أنت تستعرض هذه الغرفة سلفًا! منفصل عن الشبكة. تحقق من اتصالك. - حدث عالجه مدير الغرفة. + حدث تم تغيره من مدير الغرفة. ستعرض غرفك هنا. لانضمام لغرفة أو لإنشاء واحدة اضغط زر +. ستعرض رسائلك المباشرة هنا. لبدأ محادثات جديدة اضغط زر +. المحادثات @@ -1175,4 +1169,30 @@ كثيرة اخرى - \ No newline at end of file + + صفر + واحد + اثنان + القليل + العديد + أخرى + + ${app_name} يحتاج إلى حذف ذاكرة التخزين المؤقت حتى تكون مستحدثة، من أجل هذه الأسباب: +\n%s +\n +\nملاحظة: هذا الإجراء سيؤدي إلى إعادة تشغيل التطبيق ومن الممكن أن يستغرق بعضاً من الوقت. + استكشف غُرف + تغيير التجمع + انشئ غرفة + ابدأ محادثة + كل المحادثات + أنت الذي انهيت البث الصوتي. + + صفر + واحد + اثنان + القليل + العديد + أخرى + + diff --git a/library/ui-strings/src/main/res/values-bg/strings.xml b/library/ui-strings/src/main/res/values-bg/strings.xml index d3e9e599bc..5c147f59ef 100644 --- a/library/ui-strings/src/main/res/values-bg/strings.xml +++ b/library/ui-strings/src/main/res/values-bg/strings.xml @@ -1483,8 +1483,6 @@ Потвърди сесията Ръчно потвърждаване чрез текстово съобщение Потвърдете новия вход достъпващ профила ви: %1$s - Потвърдете всички сесии за да подсигурите, че профилът и съобщенията ви са в безопасност - Прегледайте от къде сте влезли Шифровано от непотвърдено устройство Нешифровано diff --git a/library/ui-strings/src/main/res/values-ca/strings.xml b/library/ui-strings/src/main/res/values-ca/strings.xml index b86a834a27..1f2cf1983c 100644 --- a/library/ui-strings/src/main/res/values-ca/strings.xml +++ b/library/ui-strings/src/main/res/values-ca/strings.xml @@ -1811,8 +1811,6 @@ No s\'ha pogut desar el fitxer multimèdia Confirma la teva identitat verificant aquest inici de sessió i, així, poder-li donar accés als missatges xifrats. Verifica l\'inici de sessió - Verifica totes les teves sessions per assegurar-te que el teu compte i missatges estan segurs - Comprova on has iniciat sessió Xifrat amb un dispositiu no verificat No xifrat @@ -2293,7 +2291,6 @@ Acaba l\'enquesta Això impedirà que la gent pugui votar i es mostraran els resultats finals de l\'enquesta. Vols acabar l\'enquesta\? - opció guanyadora Es necessita almenys %1$s opció Es necessiten almenys %1$s opcions @@ -2717,9 +2714,6 @@ Els usuaris dels xats directes i sales al les quals t\'hagis unit poden veure la llista completa de les teves sessions. \n \nAixò els pot proporcionar més confiança de que realment parlen amb tu però, poden veure el nom de sessió que introdueixis. - Les sessions verificades son sessions en què has iniciat sessió amb les teves credencials i s\'han verificat utilitzant una frase de seguretat o mitjançant la verificació creuada. -\n -\nAixò vol dir que contenen claus de xifrat dels teus missatges anteriors i confirmen als altres usuaris amb qui parles, que aquestes sessions son realment teves. Les sessions no verificades son sessions en què has iniciat sessió amb les teves credencials però s\'hi ha fet una verificació creuada. \n \nAssegura\'t que reconeixes aquestes sessions especialment, ja que podrien representar un ús no autoritzat del teu compte. @@ -2814,7 +2808,7 @@ Activa l\'editor de text enriquit Rep notificacions en aquesta sessió. Notificacions - Carregant + Carregant Pausa l\'emissió de veu Reprodueix o reprèn l\'emissió de veu Atura l\'enregistrament d\'emissió de veu @@ -2839,4 +2833,4 @@ Format de text Enrere 30 segons Avança 30 segons - \ No newline at end of file + diff --git a/library/ui-strings/src/main/res/values-cs/strings.xml b/library/ui-strings/src/main/res/values-cs/strings.xml index 2d2b91d645..c9d697f560 100644 --- a/library/ui-strings/src/main/res/values-cs/strings.xml +++ b/library/ui-strings/src/main/res/values-cs/strings.xml @@ -1469,8 +1469,6 @@ Přístup k zabezpečenému úložišti selhal Nezašifrováno Zašifrováno neověřeným zařízením - Přezkoumejte, kde jste se přihlásili - Ověřte všechny své relace za účelem bezpečí Vašeho účtu a zpráv Ověřte nové přihlášení s přístupem na Váš účet: %1$s Manuálně ověřit textem Ověřit přihlášení @@ -2313,7 +2311,6 @@ Ukončit hlasování Toto zastaví možnost hlasování a zobrazí se konečné výsledky. Ukončit toto hlasování\? - vítězná volba Ukončit hlasování Konečný výsledek na základě %1$d hlasu @@ -2767,9 +2764,6 @@ \n \nTo jim poskytuje jistotu, že s vámi skutečně mluví, ale také to znamená, že mohou vidět název relace, který zde zadáte. Přejmenování relací - Ověřené relace se přihlásily pomocí vašich přihlašovacích údajů a poté byly ověřeny buď pomocí vaší zabezpečené přístupové fráze, nebo křížovým ověřením. -\n -\nTo znamená, že uchovávají šifrovací klíče pro vaše předchozí zprávy a potvrzují ostatním uživatelům, se kterými komunikujete, že tyto relace jste skutečně vy. Ověřené relace Neověřené relace jsou relace, které se přihlásily pomocí vašich přihlašovacích údajů, ale nebyly křížově ověřeny. \n @@ -2868,7 +2862,7 @@ Druhé zařízení je již přihlášeno. Při nastavování zabezpečeného zasílání zpráv se vyskytl problém se zabezpečením. Může být napadena jedna z následujících věcí: váš domovský server; vaše internetové připojení; vaše zařízení; Žádost se nezdařila. - Ukládání do vyrovnávací paměti… + Ukládání do vyrovnávací paměti… Pozastavit hlasové vysílání Přehrát nebo obnovit hlasové vysílání Ukončit záznam hlasového vysílání @@ -2955,4 +2949,33 @@ V této místnosti nejsou žádné aktivní hlasování Aktivní hlasování Historie hlasování - \ No newline at end of file + Ukončené hlasování + Hlasování + ukončil(a) hlasování. + Hlasování bylo ukončeno. + Váš domovský server zatím nepodporuje zobrazení seznamu vláken. + Nelze přehrát toto hlasové vysílání. + Hlasové vysílání bylo zahájeno + Kvůli chybám při dešifrování nemusí být některé hlasy započítány + Chyba při načítání hlasování. + Načíst další hlasování + Zobrazení hlasování + + Za uplynulý den nejsou k dispozici žádná hlasování. +\nPro zobrazení hlasování z předchozích dnů načtěte další hlasování. + Za poslední %1$d dny nejsou k dispozici žádná hlasování. +\nPro zobrazení hlasování z předchozích dnů načtěte další hlasování. + Za posledních %1$d dní nejsou k dispozici žádná hlasování. +\nPro zobrazení hlasování z předchozích dnů načtěte další hlasování. + + + Za uplynulý den nejsou žádná aktivní hlasování. +\nPro zobrazení hlasování z předchozích dnů načtěte další ankety. + Za poslední %1$d dny nejsou žádná aktivní hlasování. +\nPro zobrazení hlasování z předchozích dnů načtěte další ankety. + Za posledních %1$d dní nejsou žádná aktivní hlasování. +\nPro zobrazení hlasování z předchozích dnů načtěte další ankety. + + Hlasovou zprávu nelze spustit, protože právě nahráváte živé vysílání. Ukončete prosím živé vysílání, abyste mohli začít nahrávat hlasovou zprávu + Nelze spustit hlasovou zprávu + diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml index 52b8f0c716..f0e5a7bb8d 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -1427,8 +1427,6 @@ Konnte nicht auf gesicherten Speicher zugreifen Unverschlüsselt Verschlüsselt von einem nicht verifiziertem Gerät - Überprüfe, wo du angemeldet bist - Verifiziere alle deine Sitzungen, um sicherzustellen, dass dein Konto und deine Nachrichten sicher sind Bestätige neue Anmeldung zu deinem Konto: %1$s Verifiziere manuell mit einem Text Anmeldung verifizieren @@ -2319,7 +2317,6 @@ Umfrage beenden Dies verhindert, dass andere Personen abstimmen können, und zeigt die Endergebnisse der Umfrage an. Diese Umfrage beenden\? - Gewinneroption Umfrage beenden Endgültiges Ergebnis basiert auf %1$d Stimme @@ -2711,9 +2708,6 @@ Andere Nutzer in Direktnachrichten und Räumen, in denen du dich befindest, können eine vollständige Liste deiner Sitzungen einsehen. \n \nDies gibt ihnen die Sicherheit, dass sie auch wirklich mit dir kommunizieren. Allerdings bedeutet es auch, dass sie die Sitzungsnamen sehen können, die du hier angibst. - Verifizierte Sitzungen wurden mit deinen Daten angemeldet und anschließend mit deiner Sicherheitspassphrase oder durch Quersignierung verifiziert. -\n -\nDies bedeutet, dass sie die Verschlüsselungs-Schlüssel für deine bisherigen Nachrichten besitzen und anderen Nutzern bestätigen können, dass diese Sitzungen tatsächlich von dir stammen. Sitzungen umbenennen Verifizierte Sitzungen Nicht verifizierte Sitzungen sind jene, die angemeldet, aber nicht quer signiert sind. @@ -2815,7 +2809,7 @@ Die Anfrage ist fehlgeschlagen. Abspielen oder fortsetzen der Sprachübertragung Fortsetzen der Sprachübertragung - Puffere … + Puffere … Pausiere Sprachübertragung Stoppe Aufzeichnung der Sprachübertragung Pausiere Aufzeichnung der Sprachübertragung @@ -2893,9 +2887,34 @@ Zugriffstoken Unsortierte Liste umschalten Nummerierte Liste umschalten - In diesem Raum gibt es noch keine abgeschlossenen Umfragen + In diesem Raum gibt es keine abgeschlossenen Umfragen Vergangene Umfragen In diesem Raum gibt es keine aktiven Umfragen Aktive Umfragen Umfrageverlauf - \ No newline at end of file + Beendete Umfrage + Umfrage + beendete eine Umfrage. + Umfrage beendet. + Dein Heim-Server unterstützt noch nicht das Auflisten von Threads. + Eine Sprachübertragung wurde begonnen + Wiedergabe der Sprachübertragung nicht möglich. + Evtl. werden infolge von Entschlüsselungsfehlern einige Stimmen nicht gezählt + Fehler beim Laden der Umfragen. + Weitere Umfragen laden + Zeige Umfragen an + + Für den vergangenen Tag sind keine aktiven Umfragen verfügbar. +\nLade weitere Umfragen, um die der vorherigen Tage zu sehen. + Für die vergangenen %1$d Tage sind keine aktiven Umfragen verfügbar. +\nLade weitere Umfragen, um die der vorherigen Tage zu sehen. + + + Für den vergangenen Tag sind keine beendeten Umfragen verfügbar. +\nLade weitere Umfragen, um die der vorherigen Tage zu sehen. + Für die vergangenen %1$d Tage sind keine beendeten Umfragen verfügbar. +\nLade weitere Umfragen, um die der vorherigen Tage zu sehen. + + Du kannst keine Sprachnachricht beginnen, da du im Moment eine Echtzeitübertragung aufzeichnest. Bitte beende deine Sprachübertragung, um ein Gespräch zu beginnen + Kann Sprachnachricht nicht beginnen + diff --git a/library/ui-strings/src/main/res/values-eo/strings.xml b/library/ui-strings/src/main/res/values-eo/strings.xml index e417d183bf..0aa395dcce 100644 --- a/library/ui-strings/src/main/res/values-eo/strings.xml +++ b/library/ui-strings/src/main/res/values-eo/strings.xml @@ -1483,8 +1483,6 @@ Aldoni ĉambranojn Konfirmu vian identecon per kontrolo de ĉi tiu saluto, donante al ĝi aliron al ĉifritaj mesaĝoj. Kontrolu la novan saluton, kiu aliras vian konton: %1$s - Rekontrolu ĉiujn viajn salutaĵojn por certigi, ke viaj konto kaj mesaĝoj estas sekuraj - Rekontrolu, kie vi salutis Montri la aparaton per kiu vi povas kontroli nun Montri %d aparatojn per kiuj vi povas kontroli nun @@ -2200,4 +2198,4 @@ Aroj - Iom uzantoj reatentita \@room - \ No newline at end of file + diff --git a/library/ui-strings/src/main/res/values-es/strings.xml b/library/ui-strings/src/main/res/values-es/strings.xml index c06442b5d0..f14464d957 100644 --- a/library/ui-strings/src/main/res/values-es/strings.xml +++ b/library/ui-strings/src/main/res/values-es/strings.xml @@ -1592,8 +1592,6 @@ No se pudo acceder al almacenamiento seguro Sin cifrar Cifrado por un dispositivo no verificado - Revise dónde inició sesión - Verifique todas sus sesiones para asegurarse de que su cuenta y sus mensajes estén seguros Verifique el nuevo inicio de sesión accediendo a su cuenta: %1$s Verificar manualmente por texto Verificación interactiva por emoji @@ -2386,7 +2384,6 @@ Finalizar encuesta Esto evitará que las personas puedan votar y mostrará los resultados finales de la encuesta. ¿Finalizar encuesta\? - opción ganadora Finalizar encuesta Resultado final basado en %1$d voto @@ -2688,4 +2685,4 @@ Mostrar chats recientes en el menú de compartir sistema No enviar nunca mensajes cifrados a sesiones sin verificar en esta sala. Restan %1$s - \ No newline at end of file + diff --git a/library/ui-strings/src/main/res/values-et/strings.xml b/library/ui-strings/src/main/res/values-et/strings.xml index 1e8e2b989e..1d7b96d2f9 100644 --- a/library/ui-strings/src/main/res/values-et/strings.xml +++ b/library/ui-strings/src/main/res/values-et/strings.xml @@ -1304,8 +1304,6 @@ Turvahoidla kasutamine ei õnnestu Krüptimata Krüptitud verifitseerimata seadme poolt - Vaata üle, kust sa oled Matrix\'i võrku loginud - Selleks et sinu konto ja sõnumid oleks turvatud, verifitseeri kõik oma sessioonid Verifitseeri uus kasutajasessioon, mis pruugib sinu kontot: %1$s Verifitseeri käsitsi etteantud teksti abil Verifitseeri sisselogimissessioon @@ -2318,7 +2316,6 @@ Laadi fail üles Saada pilte ja videosid Ava kaamera - populaarsem valik Kui sõnumite dekrüptimisel tekib viga, siis rakendus saadab selle kohta automaatse teate arendajatele Automaatselt teata dekrüptimise vigadest. Asenda kuvatava nime värvid @@ -2706,9 +2703,6 @@ \n \nSee tähendab, et nad võivad uskuda, et tegemist on tõesti sinuga. Samal ajal näevad ka siin sisestatud sessiooninime. Sessioonide nimede muutmine - Verifitseeritud sessioonid on sellised, kuhu sa oled oma kasutajanime ja salasõnaga sisse loginud ning mille puhul oled risttunnustamise läbi teinud või paroolifraasi abil ta turvaliseks märkinud. -\n -\nSee tähendab, et nendes sessioonides on olemas sinu varasemate sõnumite krüptovõtmed ja teistele osapooltele on nad tuvastatavad nii, et tegemist on tõesti sinuga. Verifitseeritud sessioonid Verifitseerimata sessioonid on sellised, kuhu sa oled oma kasutajanime ja salasõnaga sisse loginud, kuid mille puhul on risttunnustamine tegemata. \n @@ -2805,7 +2799,7 @@ Teine seade on juba võrku loginud. Turvalise sõnumivahetuse ülesseadmisel tekkis turvaviga. Üks kolmest võib olla sattunud vale osapoole kontrolli alla: sinu koduserver, sinu internetiühendus või sinu seade; Päring ei õnnestunud. - Andmed on puhverdamisel… + Andmed on puhverdamisel… Alusta või jätka ringhäälingukõne esitamist Lõpeta ringhäälingukõne salvestamine Peata ringhäälingukõne salvestamine @@ -2890,4 +2884,29 @@ Lülita täpploend sisse/välja Pääsuluba Sinu pääsuluba annab täismahulise ligipääsu sinu kasutajakontole. Palun ära jaga seda teistega. - \ No newline at end of file + Lõppenud küsitlus + Küsitlus on lõppenud. + lõpetas küsitluse. + Küsitlus + Sinu koduserver veel ei toeta jutulõngade loendit. + Alustasime ringhäälingukõnega + Selle ringhäälingukõne esitamine ei õnnestu. + Krüptimisvigade tõttu jääb osa hääli lugemata + + Möödunud päevas polnud ühtegi toimumas olnud küsitlust. +\nVarasemate päevade vaatamiseks laadi veel küsitlusi. + Möödunud %1$d päeva jooksul polnud ühtegi toimumas olnud küsitlust. +\nVarasemate päevade vaatamiseks laadi veel küsitlusi. + + + Möödunud päevas polnud ühtegi küsitlust. +\nVarasemate päevade vaatamiseks laadi veel küsitlusi. + Möödunud %1$d päeva jooksul polnud ühtegi küsitlust. +\nVarasemate päevade vaatamiseks laadi veel küsitlusi. + + Küsitluste kuvamise ootel + Laadi veel küsitlusi + Viga küsitluste laadimisel. + Häälsõnumi esitamine ei õnnestu + Kuna sa hetkel salvestad ringhäälingukõnet, siis häälsõnumi salvestamine või esitamine ei õnnestu. Selleks palun lõpeta ringhäälingukõne + diff --git a/library/ui-strings/src/main/res/values-eu/strings.xml b/library/ui-strings/src/main/res/values-eu/strings.xml index f1f834ee04..b045cc8c70 100644 --- a/library/ui-strings/src/main/res/values-eu/strings.xml +++ b/library/ui-strings/src/main/res/values-eu/strings.xml @@ -1785,8 +1785,6 @@ Errore hau ${app_name}-en kontroletik kanpo dago. Ez dago Google konturik gailua Zifratu gabe Egiaztatu gabeko gailu batek zifratua - Berrikusi non hasi duzun saioa - Egiaztatu zure saio guztiak kontua eta mezuak seguru daudela bermatzeko Egiaztatu zure kontuan hasitako saio berria: %1$s Egiaztatu eskuz testu bidez diff --git a/library/ui-strings/src/main/res/values-fa/strings.xml b/library/ui-strings/src/main/res/values-fa/strings.xml index 4db3812237..1b726a2428 100644 --- a/library/ui-strings/src/main/res/values-fa/strings.xml +++ b/library/ui-strings/src/main/res/values-fa/strings.xml @@ -1106,8 +1106,6 @@ یا دیگر کاره‌های ماتریکس دادای قابلیت ورود چندگانه تأیید دستی با متن تأیید ورود جدیدی که به حسابتان دسترسی دارد: %1$s - تأیید همهٔ نشست‌هایتان برای اطمینان از این که حساب و پیام‌هایتان امنند - بازبینی جاهایی که وارد شده‌اید تأیید برهم‌کنشی با اموجی تأیید ورود رمزنشده @@ -2275,7 +2273,6 @@ پایان نظرسنجی این کار اجازهٔ رأی دادن افراد را پایان داده و نتیجهٔ نهایی نظرسنجی را نمایش خواهد داد. پایان این نظرسنجی؟ - گزینهٔ غالب پایان نظرسنجی نتیجهٔ نهایی بر مبنای %1$d رأی @@ -2783,7 +2780,7 @@ نظرسنجی‌ها پیوست‌ها برچسب‌ها - میانگیری… + میانگیری… زنده تأیید ۳ @@ -2859,9 +2856,6 @@ نشست‌های تأیید شده آن‌هاییند که پس از ورود عبارت عبورتان یا تأیید هویتتان با نشست تأیید شده‌ای دیگر، واردشان شده‌اید. \n \nیعنی تمامی کلیدهای لارم برای رمزگشایی پیام‌های رمزنگاشته‌تان را داشته و این تأیید را به دیگران می‌دهند که به این نشست اطمینان دارید. - نشست‌های تأیید شده به حسابتان وارد و با عبارت عبور امنتان یا تأیید متقابل تأیید شده‌اند. -\n -\nیعنی کلیدهای رمزنگاری پیام‌های پیشینتان را داشته و به دیگر کاربران این تأیید را می‌دهند که این نشست، خودتان هستید. نشست‌های تأیید نشده نشست‌هاییند که به آن‌ها وارد شده‌اید، ولی تأیید متقبالشان نکرده‌اید. \n \nباید به طور خاص مطمئن شوید که این نشست‌ها را می‌شناسید؛ چرا که می‌توانند نشان‌دهندهٔ استفادهٔ تأییدنشده از حسابتان باشند. @@ -2899,4 +2893,29 @@ هیچ نظرسنجی فعّالی در این اتاق وجود ندارد نظرسنجی‌های فعّال تاریخچهٔ نظرسنجی‌ها - \ No newline at end of file + نظرسنجی پایان یافته + نظرسنجی + به نظرسنجی‌ای پایان داد. + به نظرسنجی پایان داد. + کارساز خانگیتان هنوز از سیاهه کردن رشته‌ها پشتیبانی نمی‌کند. + ناتوان در پخش این صدا. + پخش صوتی را آغاز کرد + به خاطر خطاهای رمزگشایی، ممکن است برخی رأی‌ها شمرده نشوند + خطا در واکشی نظرسنجی‌ها. + بار کردن نظرسنجی‌های بیش‌تر + نشان دادن نظرسنجی‌ها + + نظرسنجی گذشته‌ای برای روز گذشته وجود ندارد. +\nبرای دیدن نظرسنجی‌های روزهای پیش، نظرسنجی‌های بیش‌تری بار کنید. + نظرسنجی گذشته‌ای برای %1$d روز گذشته وجود ندارد. +\nبرای دیدن نظرسنجی‌های روزهای پیش، نظرسنجی‌های بیش‌تری بار کنید. + + + نظرسنجی فعّالی برای روز گذشته وجود ندارد. +\nبرای دیدن نظرسنجی‌های روزهای پیش، نظرسنجی‌های بیش‌تری بار کنید. + نظرسنجی فعّالی برای %1$d روز گذشته وجود ندارد. +\nبرای دیدن نظرسنجی‌های روزهای پیش، نظرسنجی‌های بیش‌تری بار کنید. + + از آن‌جا که در حال ضبط پخشی زنده‌اید، نمی‌توانید پیامی صوتی را آغاز کنید. لطفاً برای آغاز ضبط یک پیام صوتی، پخش زنده‌تان را پایان دهید + نمی‌توان پخش صوتی را آغاز کرد + diff --git a/library/ui-strings/src/main/res/values-fi/strings.xml b/library/ui-strings/src/main/res/values-fi/strings.xml index 4976f49a92..c1cc5da2c8 100644 --- a/library/ui-strings/src/main/res/values-fi/strings.xml +++ b/library/ui-strings/src/main/res/values-fi/strings.xml @@ -1717,8 +1717,6 @@ Valitse käyttäjänimi. Vahvista vuorovaikutteisesti emojilla Vahvista kirjautuminen - Vahvista kaikki istuntosi varmistaaksesi, että tilisi ja viestisi ovat turvassa - Katselmoi missä olet sisäänkirjautuneena Salattu vahvistamattomalla laitteella Salaamaton Käytä palautusavainta @@ -2309,4 +2307,4 @@ %1$d valittu %1$d valittu - \ No newline at end of file + diff --git a/library/ui-strings/src/main/res/values-fr-rCA/strings.xml b/library/ui-strings/src/main/res/values-fr-rCA/strings.xml index 94db2935a7..cd39fa3381 100644 --- a/library/ui-strings/src/main/res/values-fr-rCA/strings.xml +++ b/library/ui-strings/src/main/res/values-fr-rCA/strings.xml @@ -640,8 +640,6 @@ Vérifier la connexion Vérifier manuellement avec un texte Vérifiez la nouvelle connexion accédant à votre compte : %1$s - Vérifiez toutes les sessions pour vous assurer que votre compte et vos messages sont en sécurité - Vérifiez où vous vous êtes connecté Chiffré par un appareil non vérifié Non chiffré envoie de la neige ❄️ diff --git a/library/ui-strings/src/main/res/values-fr/strings.xml b/library/ui-strings/src/main/res/values-fr/strings.xml index cb1684f834..e45211b61a 100644 --- a/library/ui-strings/src/main/res/values-fr/strings.xml +++ b/library/ui-strings/src/main/res/values-fr/strings.xml @@ -1413,8 +1413,6 @@ Nous n’avons pas pu créer votre conversation privée. Vérifiez les utilisateurs que vous souhaitez inviter et réessayez. Non chiffré Chiffré par un appareil non vérifié - Vérifiez où vous vous êtes connecté - Vérifiez toutes les sessions pour vous assurer que votre compte et vos messages sont en sécurité Vérifiez la nouvelle connexion accédant à votre compte : %1$s %1$s : %2$s %1$s : %2$s %3$s @@ -2272,7 +2270,6 @@ Terminer le sondage Cela empêchera les gens de voter et affichera le résultat final du sondage. Terminer ce sondage \? - option gagnante Terminer le sondage Résultat final sur la base de %1$d vote @@ -2713,9 +2710,6 @@ \n \nCela leur fournit une preuve de confiance que c’est bien avec vous qu\'ils communiquent, mais cela veut également dire qu’ils peuvent voir le nom de la session que vous saisissez ici. Renommer les sessions - Les sessions vérifiées sont celles qui sont identifiées avec votre mot de passe puis vérifiée, soit à l’aide de votre phrase de sécurité, ou bien par la vérification croisée. -\n -\nCela signifie qu’elles possèdent les clés de chiffrement de vos messages passés, et certifient aux autres utilisateurs avec qui vous communiquez que ces sessions viennent vraiment de vous. Sessions vérifiées Les sessions non vérifiées sont celles qui sont identifiées avec votre mot de passe sans avoir fait de vérification croisée. \n @@ -2814,7 +2808,7 @@ Vous pouvez utiliser cet appareil pour connecter un appareil mobile ou un client web avec un QR code. Il y a deux façons de le faire : Se connecter avec un QR code Scanner le QR code - Mise en mémoire tampon… + Mise en mémoire tampon… Mettre en pause la diffusion audio Lire ou continuer la diffusion audio Arrêter l’enregistrement de la diffusion audio @@ -2899,4 +2893,29 @@ Il n’y a aucun sondage en cours dans ce salon Sondages actifs Historique des sondages - \ No newline at end of file + Sondage terminé + Sondage + a terminé un sondage. + A terminé le sondage. + Votre serveur d’accueil ne prend pas encore en charge l’affichage de la liste des fils de discussion. + Impossible de lire cette diffusion audio. + A démarré une diffusion audio + À cause d’erreurs de déchiffrement, certains votes pourraient ne pas avoir été pris en compte + Erreur lors de la récupération des sondages. + Charger plus de sondages + Affichage des sondages + + Il n’y a aucun sondage terminé depuis hier. +\nChargez plus de sondages pour voir les sondages des jours précédents. + Il n’y a aucun sondage terminé depuis %1$d jours. +\nChargez plus de sondages pour voir les sondages des jours précédents. + + + Il n’y a aucun sondage actif depuis hier. +\nChargez plus de sondages pour voir les sondages des jours précédents. + Il n’y a aucun sondage actif depuis %1$d jours. +\nChargez plus de sondages pour voir les sondages des jours précédents. + + Vous ne pouvez pas commencer un message vocal car vous êtes en train d’enregistrer une diffusion en direct. Veuillez terminer cette diffusion pour commencer un message vocal + Impossible de démarrer un message vocal + diff --git a/library/ui-strings/src/main/res/values-hu/strings.xml b/library/ui-strings/src/main/res/values-hu/strings.xml index 9fdad2dbf0..0aa70cea55 100644 --- a/library/ui-strings/src/main/res/values-hu/strings.xml +++ b/library/ui-strings/src/main/res/values-hu/strings.xml @@ -1361,8 +1361,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze A biztonsági tárolóhoz nem sikerült hozzáférni Titkosítatlan Ellenőrizetlen eszközzel titkosította - Tekintsd át hol vagy bejelentkezve - Ellenőrizd minden munkamenetedet, hogy a fiókod és az üzeneteid biztonságban legyenek Ellenőrizd ezt az új bejelentkezést ami hozzáfér a fiókodhoz: %1$s Manuális szöveges ellenőrzés Belépés ellenőrzése @@ -2308,7 +2306,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze \n \nElolvashatod a feltételeinket %s. Segíts az ${app_name}-et jobbá tenni - nyerő válasz Jogi dolgok A változások életbelépéséhez indítsd újra az alkalmazást. LaTeX matematikai szintaxis engedélyezése @@ -2712,9 +2709,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Más felhasználók akikkel közvetlenül vagy szobában beszélgetsz látják a teljes listát a munkameneteidről. \n \nEzzel ők biztosak lehetnek abban, hogy ténylegesen veled beszélgetnek. Ez azt is jelenti, hogy látják a munkamenet nevét amit itt megadsz. - Ellenőrzött munkamenetbe a neveddel és jelszavaddal léptek be és ellenőrizve lett vagy a biztonsági jelmondattal vagy másik munkamenetből. -\n -\nEz azt jelenti, hogy tartalmazzák a titkosítási kulcsokat az régi üzenetekhez, és biztosítja a többieket a kommunikációban, hogy ezt a munkamenetet tényleg te használod. Aláhúzott Áthúzott Dőlt @@ -2814,7 +2808,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze A kérés sikertelen. Hang közvetítés felvételéhez és a szoba idővonalára küldéséhez. Hang közvetítés engedélyezése - Pufferelés… + Pufferelés… Hang közvetítés szüneteltetése Hang közvetítés lejátszása vagy lejátszás folytatása Hang közvetítés felvétel leállítása @@ -2899,4 +2893,29 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Nincsenek aktív szavazások ebben a szobában Aktív szavazások Szavazás alakulása - \ No newline at end of file + Lezárt szavazások + Szavazás + befejezte a szavazást. + Szavazás vége. + A matrix szerver nem támogatja az üzenetszálak listázását. + A hang közvetítés nem játszható le. + Hang közvetítés indítva + Visszafejtési hibák miatt néhány szavazat nem kerül beszámításra + Szavazás betöltési hiba. + Még több szavazás betöltése + Szavazások megjelenítése + + Egy napja nincs aktív szavazás. +\nTovábbi szavazások betöltése a régi szavazások megjelenítéséhez. + %1$d napja nincs aktív szavazás. +\nTovábbi szavazások betöltése a régi szavazások megjelenítéséhez. + + + Egy napja nincs aktív szavazás. +\nTovábbi szavazások betöltése a régi szavazások megjelenítéséhez. + %1$d napja nincs aktív szavazás. +\nTovábbi szavazások betöltése a régi szavazások megjelenítéséhez. + + Nem lehet hang üzenetet indítani élő közvetítés felvétele közben. Az élő közvetítés bejezése szükséges a hang üzenet indításához + Hang üzenetet nem lehet elindítani + diff --git a/library/ui-strings/src/main/res/values-hy/strings.xml b/library/ui-strings/src/main/res/values-hy/strings.xml new file mode 100644 index 0000000000..a444d625fb --- /dev/null +++ b/library/ui-strings/src/main/res/values-hy/strings.xml @@ -0,0 +1,106 @@ + + + Դուք մերժել եք հրավերը։ Պատճառ` %1$s + %1$sը մերժել է հրավերը։ Պատճառը` %2$s + Դուք լքել եք։ Պատճառը` %1$s + %1$sը լքել է սենյակը։ Պատճառը` %2$s + %1$sը միացել է։ Պատճառը` %2$s + %1$sը լքել է։ Պատճառը` %2$s + Դուք լքել եք սենյակը։ Պատճառը` %1$s + Դուք միացել եք։ Պատճառը` %1$s + Դուք միացել եք սենյակին։ Պատճառը` %1$s + %1$sը միացել է սենյակին։ Պատճառը` %2$s + %1$sը հրավիրել է Ձեզ։ Պատճառը` %2$s + Հաղորդագրությունը ուղարկվում է… + Ուղարկողի սարքավորումը մեզ չի ուղարկել այս հաղորդագրության բանալիները։ + %sը ավարտել է զանգը։ + Դուք պատասխանել եք զանգին։ + %sը պատասխանել է զանգին։ + Դուք հրավիրել եք %1$sին։ Պատճառը` %2$s + %1$sը հրավիրել է %2$sին։ Պատճառը` %3$s + Հաղորդագրությունը ուղարկվել է + Դատարկ սենյակ + + %1$sը, %2$sը, %3$sը և %4$d ուրիշը + %1$sը, %2$sը, %3$sը և %4$d ուրիշները + + %1$sը, %2$sը, %3$sը և %4$sը + %1$sը, %2$sը և %3$sը + %1$sը և %2$sը + Սենյակի Հրավեր + Էլ-փոստի հասցե + Հեռախոսահամար + Դուք այս սենյակին միանալու թույլտվություն չունեք + Ուսումնասիրել Սենյակներ + Ստեղծել Տարածություն + Բոլոր Զրույցները + Սկսել Զրույց + Ստեղծել Սենյակ + Matrix-ի սխալ + Հնարավոր չէ ուղարկել հաղորդագրությունը + ** Հնարավոր չէ վերծանել %sը ** + Լռելյայն + Մոդերատոր + Ադմին + Դուք հրավիրել եք %1$sին + %1$s հրավիրել է %2$sին + Դուք սենյակին միանալու հրավեր եք ուղարկել %1$sին + %1$sը սենյակին միանալու հրավեր է ուղարկել%2$sին + Դուք հեռացրել եք սենյակի անունը + %1$sը հեռացրել է սենյակի անունը + Փոփոխություն չկա։ + ցանկացածը։ + սենյակի բոլոր անդամները։ + սենյակի բոլոր անդամները, իրենց միանալու պահից սկսած։ + սենյակի բոլոր անդամները, իրենց հրավիրման պահից սկսած։ + Դուք ապագա հաղորդագրությունները տեսանելի եք դարձրել %1$sին + %1$sը ապագա հաղորդագրությունները տեսանելի է դարձրել %2$sին + Դուք սենյակի ապագա պատմությունը տեսանելի եք դարձրել %1$sին + %1$sը սենյակի ապագա պատմությունը տեսանելի է դարձրել %2$sին + Դուք ավարտել եք զանգը։ + Դուք սենյակի անունը փոխել եք %1$s + %1$sը սենյակի անունը փոխել է %2$s + %1$s ստեղծել է այս քննարկումը + Դուք ստեղծել եք այս սենյակը + %1$s ստեղծել է այս սենյակը + + %1$d ընտրված է + %1$d ընտրված են + + Ձեր հրավերը + %sի հրավերը + Դուք հեռացրել եք %1$sին + %1$sը հեռացրել է %2$sին + Դուք մերժել եք հրավերը + %1$s մերժել է հրավերը + Դուք լքել եք սենյակը + %1$s լքել է սենյակը + Դուք լքել եք սենյակը + %1$s լքել է սենյակը + Դուք միացել եք + %1$sը միացել է + Դուք միացել եք սենյակին + %1$sը միացել է սենյակին + %1$sը հրավիրել է Ձեզ + Դուք հրավիրել եք %1$sին + %1$sը հրավիրել է %2$sին + Դուք ստեղծել եք այս քննարկումը + Դուք հեռացրել եք սենյակի գլխավոր հասցեն։ + %1$sը հեռացրել է սենյակի գլխավոր հասցեն։ + Դուք սենյակի գլխավոր հասցեն դրել եք %1$sը։ + %1$sը սենյակի գլխավոր հասցեն դրել է %2$sը։ + + Դուք ավելացրել եք %1$sին որպես սենյակի հասցե + Դուք ավելացրել եք %1$sին որպես սենյակի հասցեներ + + + %1$sը ավելացրել է %2$sին որպես սենյակի հասցե + %1$sը ավելացրել է %2$sին որպես սենյակի հասցեներ + + Դուք ետ եք կանչել %1$sի հրավերը։ Պատճառը` %2$s + %1$sը ետ է կանչել %2$sի հրավերը։ Պատճառը` %3$s + %1$sը ընդունել է %2$sի համար հրավերը։ Պատճառը` %3$s + Դուք ընդունել եք %1$sի համար հրավերը։ Պատճառը` %2$s + Դուք հեռացրել եք %1$sին։ Պատճառը` %2$s + %1$sը հեռացրել է %2$sին։ Պատճառը` %3$s + \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-in/strings.xml b/library/ui-strings/src/main/res/values-in/strings.xml index 8896037037..4c524df727 100644 --- a/library/ui-strings/src/main/res/values-in/strings.xml +++ b/library/ui-strings/src/main/res/values-in/strings.xml @@ -1957,8 +1957,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Verifikasi secara Manual oleh Teks Verifikasi login Verifikasi login baru yang mengakses akun Anda: %1$s - Verifikasi semua sesi Anda untuk memastikan akun & pesan Anda aman - Lihat mana Anda masuk Dienkripsi oleh perangkat yang tidak diverifikasi Tidak Dienkripsi mengirim salju ❄️ @@ -2233,7 +2231,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Akhiri poll Ini akan menghentikan orang-orang untuk dapat memberikan suara dan akan menampilkan hasil akhir poll. Akhiri poll ini\? - opsi pemenang Akhiri poll Hasil akhir berdasarkan oleh %1$d suara @@ -2661,9 +2658,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. \n \nIni memberikan mereka kepastian bahwa mereka berbicara dengan Anda, tetapi ini juga berarti bahwa mereka dapat melihat nama sesi yang Anda masukkan di sini. Mengubah nama sesi - Sesi yang terverifikasi telah masuk dengan kredensial Anda dan juga telah diverifikasi, menggunakan frasa sandi atau memverifikasi secara silang. -\n -\nIni berarti mereka memegang kunci enkripsi ke pesan Anda sebelumnya, dan mengonfirmasi pengguna lain yang Anda berkomunikasi bahwa sesi ini memang Anda. Sesi tidak aktif Sesi belum diverifikasi Sesi terverifikasi @@ -2762,7 +2756,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Permintaan gagal. Memungkinkan untuk merekam dan mengirim siaran suara dalam lini masa ruangan. Aktifkan siaran suara - Memuat… + Memuat… Jeda siaran suara Mainkan atau lanjutkan siaran suara Hentikan rekaman siaran suara @@ -2845,4 +2839,23 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Tidak ada pemungutan suara yang aktif di ruangan ini Pemungutan suara aktif Riwayat pemungutan suara - \ No newline at end of file + Pemungutan suara diakhiri + Pemungutan suara + mengakhiri pemungutan suara. + Mengakhiri pemungutan suara. + Homeserver Anda belum mendukung pendaftaran utasan. + Tidak dapat memutar siaran suara ini. + Memulai sebuah siaran suara + Karena kesalahan enkripsi, beberapa suara mungkin tidak terhitung + Terjadi kesalahan mendapatkan pemungutan suara. + Muat lebih banyak pemungutan suara + Menampilkan pemungutan suara + + Tidak ada pemungutan suara yang lalu %1$d hari terakhir. +\nMuat lebih banyak pemungutan suara untuk melihat pemungutan suara untuk hari sebelumnya. + + + Tidak ada pemungutan suara aktif %1$d hari terakhir. +\nMuat lebih banyak pemungutan suara untuk melihat pemungutan suara untuk hari sebelumnya. + + diff --git a/library/ui-strings/src/main/res/values-is/strings.xml b/library/ui-strings/src/main/res/values-is/strings.xml index ceb4d614de..ba505bc0a3 100644 --- a/library/ui-strings/src/main/res/values-is/strings.xml +++ b/library/ui-strings/src/main/res/values-is/strings.xml @@ -851,7 +851,6 @@ %1$d atkvæði greidd. Greiddu atkvæði til að sjá útkomuna Næ ekki að tengjast heimaþjóni á þessari slóð, athugaðu slóðina - réttur valkostur Spurning eða viðfangsefni Endurræstu forritið til að breytingin taki gildi. Virkja LaTeX-stærðfræði @@ -1672,10 +1671,10 @@ Settu inn tillögu Fáðu aðstoð við að nota ${app_name} Lagaleg atriði - session_name: - app_display_name: - push_key: - app_id: + Birtingarnafn setu: + Birtingarnafn forrits: + Push-lykill: + Auðkenni forrits: Þú ert nú þegar að skoða þennan spjallþráð! Þú ert nú þegar að skoða þessa spjallrás! Útgáfa Matrix SDK @@ -1775,7 +1774,6 @@ Þú ert núna ekki að nota neinn auðkennisþjón. Til að uppgötva og vera finnanleg/ur fyrir félaga þína í teyminu, skaltu bæta við auðkennisþjóni hér fyrir neðan. ${app_name} krefst þess að þú setjir inn auðkennin þín til að framkvæma þessa aðgerð. Endurauðkenning er nauðsynleg - Yfirfarðu hvar þú sért skráð/ur inn Nota endurheimtulykil Athuga öryggisafritunarlykil Þetta er ekki gildur endurheimtulykill @@ -2263,4 +2261,125 @@ Endilega lestu í gegnum stefnur og skilmála fyrir %s Stefnur netþjónsins Element Matrix Services (EMS) er afkastamikil og áreiðanleg hýsingarþjónusta fyrir hraðvirk og örugg samskipti í rauntíma. Skoðaðu hvernig við förum að því á element.io/ems - \ No newline at end of file + + %1$d valið + %1$d valið + + Aðgangsteiknið þitt gefur fullan aðgang að notandaaðgangnum þínum. Ekki deila því með neinum. + Aðgangsteikn + Lauk könnun + Könnun + lauk könnun. + bjó til könnun. + sendi límmerki. + sendi myndskeið. + sendi mynd. + sendi talskilaboð. + sendi hljóðskrá. + sendi skrá. + Sem svar til + Breyta tengli + Búa til tengil + Tengill + Texti + Staðfesta + Reyna aftur + Engin samsvörun\? + Skrái þig inn + Tengist við tæki + Skanna QR-kóða + Ertu að skrá inn farsíma/snjalltæki\? + Veldu \'Skanna QR-kóða\' + Veldu \'Skrá inn með QR-kóða\' + Veldu \'Birta QR-kóða\' + Þessi QR-kóði er ógildur. + Beiðnin mistókst. + Skrá inn með QR-kóða + Skanna QR-kóða + 3 + 2 + 1 + Aðgangur að svæðum + Sannreyndar setur + Óstaðfestar setur + Óvirkar setur + Skrá inn með QR-kóða + Nafn á setu + Endurnefna setu + Stýrikerfi + Gerð + Vafri + Slóð (URL) + Útgáfa + Heiti + Forrit + Taka á móti ýti-tilkynningum á þessu tæki. + Ýti-tilkynningar + Upplýsingar um forrit, tæki og aðgerðir. + Skrá út úr þessari setu + Fela IP-vistfang + Birta IP-vistfang + Skrá út úr öllum öðrum setum + Skrá út + Óvirkar setur + Ráðleggingar varðandi öryggi + Deiling staðsetningar í rauntíma + Sníðing texta + Tengiliður + Myndavél + Staðsetning + Kannanir + Útvörpun tals + Viðhengi + Límmerki + Myndasafn + Byrjaðu talútsendingu + Staðsetning í rauntíma + Þú hefur ekki heimildir til að deila rauntímastaðsetningum + Uppfært fyrir %1$s síðan + Virkja deilingu rauntímastaðsetninga + Í beinni til %1$s + Staðsetningu í rauntíma lauk + Tókst ekki að hlaða inn landakorti +\nÞessi heimaþjónn er mögulega ekki stilltur til að birta landakort. + Villa við að sækja kannanir. + Hlaða inn fleiri könnunum + Birting kannana + Fyrri kannanir + Virkar kannanir + Lauk könnuninni. + %1$s eftir + Fara áfram um 30 sekúndur + Fara afturábak um 30 sekúndur + Hleð í biðminni… + Bein útsending + Beint + Birta upplýsingar um síðasta notanda + Yfirfarðu þetta til að tryggja að aðgangurinn þinn sé öruggur + Þú ert með óstaðfestar setur + Breytingaskrá könnunar + Skilaboð hér eru enda-í-enda dulrituð. +\n +\nÖryggi skilaboðanna þinna er tryggt og einungis þú og viðtakendurnir hafa dulritunarlyklana til að opna skilaboðin. + Skilaboð á þessari spjallrás eru enda-í-enda dulrituð. +\n +\nÖryggi skilaboðanna þinna er tryggt og einungis þú og viðtakendurnir hafa dulritunarlyklana til að opna skilaboðin. + Hóf talútsendingu + Sumir stafir eru óleyfilegir + Setur (╯°□°)╯︵ ┻━┻ framan við hrein textaskilaboð + Skanna QR-kóða + Ekki er enn búið að útbúa notandaaðganginn þinn. Á að hætta skráningarferlinu\? + Útvörpun tals + Virkt: + Auðkenni setu: + Tilvitnanir + Svara til %s + Breytingar + Það lítur út fyrir að þú sért að reyna að tengjast öðrum heimaþjóni. Viltu skrá þig út\? + Já, stöðva + Afvelja allt + Velja allt + Náði því + Þú endaðir talútsendingu. + %1$s endaði talútsendingu. + diff --git a/library/ui-strings/src/main/res/values-it/strings.xml b/library/ui-strings/src/main/res/values-it/strings.xml index 729b826982..d8c81974b2 100644 --- a/library/ui-strings/src/main/res/values-it/strings.xml +++ b/library/ui-strings/src/main/res/values-it/strings.xml @@ -1418,8 +1418,6 @@ Accesso all\'archivio sicuro fallito Non criptato Criptato da un dispositivo non verificato - Controlla dove hai fatto l\'accesso - Verifica tutte le tue sessioni per assicurarti che il tuo account e i messaggi siano protetti Verifica il nuovo accesso entrando nel tuo account: %1$s Verifica manualmente con testo Verifica accesso @@ -2263,7 +2261,6 @@ Termina sondaggio Ciò impedirà alle persone di poter votare e mostrerà i risultati finali del sondaggio. Terminare questo sondaggio\? - opzione vincente Termina sondaggio Risultato finale basato su %1$d voto @@ -2704,9 +2701,6 @@ \n \nIn questo modo hanno la certezza che stanno parlando davvero con te, ma significa anche che possono vedere il nome della sessione che inserisci qui. Rinominare le sessioni - Le sessioni verificate hanno effettuato l\'accesso con le tue credenziali e sono state verificate, usando la frase di sicurezza o la verifica incrociata. -\n -\nCiò significa che hanno le tue chiavi di crittografia per i messaggi passati, e confermano agli altri utenti con cui comunichi che queste sessioni sono usate da te. Sessioni verificate Le sessioni non verificate sono quelle in cui è stato fatto l\'accesso con le tue credenziali, ma che non sono state verificate. \n @@ -2805,7 +2799,7 @@ L\'altro dispositivo ha già fatto l\'accesso. Si è verificato un problema di sicurezza configurando i messaggi sicuri. Una delle seguenti cose potrebbe essere compromessa: il tuo homeserver; la/e connessione/i internet; il/i dispositivo/i; La richiesta è fallita. - Buffer… + Buffer… Sospendi trasmissione vocale Avvia o riprendi trasmissione vocale Ferma registrazione trasmissione vocale @@ -2890,4 +2884,29 @@ In questa stanza non ci sono sondaggi attivi Sondaggi attivi Cronologia sondaggi - \ No newline at end of file + Sondaggio terminato + Sondaggio + terminato un sondaggio. + Sondaggio terminato. + Il tuo homeserver non supporta ancora l\'elenco di conversazioni. + A causa di errori di decifrazione, alcuni voti potrebbero non venire contati + Impossibile avviare questa trasmissione vocale. + Iniziata una trasmissione vocale + Errore di recupero dei sondaggi. + Carica più sondaggi + Visualizzazione sondaggi + + Non ci sono sondaggi passati nell\'ultimo giorno. +\nCarica più sondaggi per vedere quelli dei giorni precedenti. + Non ci sono sondaggi passati negli ultimi %1$d giorni. +\nCarica più sondaggi per vedere quelli dei giorni precedenti. + + + Non ci sono sondaggi attivi nell\'ultimo giorno. +\nCarica più sondaggi per vedere quelli dei giorni precedenti. + Non ci sono sondaggi attivi negli ultimi %1$d giorni. +\nCarica più sondaggi per vedere quelli dei giorni precedenti. + + Non puoi iniziare un messaggio vocale perché stai registrando una trasmissione in diretta. Termina la trasmissione per potere iniziare un messaggio vocale + Impossibile iniziare il messaggio vocale + diff --git a/library/ui-strings/src/main/res/values-iw/strings.xml b/library/ui-strings/src/main/res/values-iw/strings.xml index b9f81ae446..aabdc7371e 100644 --- a/library/ui-strings/src/main/res/values-iw/strings.xml +++ b/library/ui-strings/src/main/res/values-iw/strings.xml @@ -1672,8 +1672,6 @@ אמת ידנית באמצעות טקסט אמת את הכניסה החדשה שניגשת לחשבונך: %1$s - אמת את כל ההפעלות שלך כדי להבטיח שהחשבון וההודעות שלך בטוחים - בדוק היכן נכנסת מוצפן על ידי מכשיר לא מאומת לא מוצפן שולח שלג ❄️ @@ -2136,7 +2134,6 @@ סוף המשאל פעולה זו תעצור את האפשרות להצביע ותציג את תוצאות המשאל. סוף המשאל\? - אפשרות הזוכה סוף המשאל שאלה לא יכולה להיות ריקה צור משאל @@ -2506,4 +2503,4 @@ \nזה יהיה מעבר חד פעמי שכן שרשורים הם כעת חלק ממפרט Matrix. שיתוף מסך של ${app_name} המסך משותף כרגע - \ No newline at end of file + diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml index 11ab6ee857..d893156f6e 100644 --- a/library/ui-strings/src/main/res/values-ja/strings.xml +++ b/library/ui-strings/src/main/res/values-ja/strings.xml @@ -139,8 +139,8 @@ メールアドレスを追加 電話番号を追加 通知音 - このアカウントでは通知を有効にする - このセッションでは通知を有効にする + このアカウントで通知を有効にする + このセッションで通知を有効にする 1対1のチャットでのメッセージ グループチャットでのメッセージ ルームへ招待されたとき @@ -526,7 +526,7 @@ 暗号化を有効にする いったん有効にすると、暗号化を無効にすることはできません。 セキュリティー - 詳しく知る + 詳細を表示 その他の設定 管理者としての操作 ルームの設定 @@ -1252,7 +1252,7 @@ Element Matrix Servicesに接続 Matrix IDでサインイン Matrix IDでサインイン - 詳しく知る + 詳細を表示 その他 カスタムと高度な設定 組織向けのプレミアムホスティング @@ -1845,8 +1845,8 @@ %1$d個の投票があります。結果を見るには投票してください 未認証の端末で暗号化 - メッセージを紙吹雪と共に送る - メッセージを降雪と共に送る + メッセージを紙吹雪と共に送信 + メッセージを降雪と共に送信 紙吹雪🎉を送る 降雪❄️を送る あなたのチームのメッセージングに。 @@ -1930,7 +1930,6 @@ 非公開で招待が必要なルームは表示されていません。 \nルームを追加する権限はありません。 非公開で招待が必要なルームは表示されていません。 - 勝者 知人に見つけてもらえるように電話番号を設定できます。任意です。 メッセージキー 復旧用のパスフレーズ @@ -2223,8 +2222,6 @@ 指紋や顔画像など、端末に固有の生体認証を有効にする。 絵文字で認証 テキストで認証 - すべてのセッションを認証し、アカウントとメッセージが安全であることを確認してください - ログインしている場所を確認 復旧用の手段を全て無くしてしまいましたか?全てリセットする クロス署名に対応した他のMatrixのクライアントでも使用できます。 どのような議論を%sで行いたいですか? @@ -2443,7 +2440,7 @@ レイアウトの設定 了解 次へ - 詳しく知る + 詳細を表示 @@ -2473,4 +2470,21 @@ QRコードをスキャン QRコードをスキャン QRコードが不正です。 - \ No newline at end of file + スペースは、ルームと連絡先をまとめる新しい方法です。はじめに、スペースを作成しましょう。 + 最近の履歴を表示 + この暗号化されたメッセージの信頼性はこの端末では保証できません。 + アカウントが安全かどうか確認してください + 未認証のセッションがあります + 連絡先 + お気に入り + 未読あり + 全て + はい、停止 + 全ての選択を解除 + 全て選択 + 音声配信を終了しました。 + %1$sが音声配信を終了しました。 + + %1$dを選択しました + + diff --git a/library/ui-strings/src/main/res/values-kab/strings.xml b/library/ui-strings/src/main/res/values-kab/strings.xml index 353fb99f53..c16b5624a8 100644 --- a/library/ui-strings/src/main/res/values-kab/strings.xml +++ b/library/ui-strings/src/main/res/values-kab/strings.xml @@ -463,8 +463,6 @@ Senqed iman-ik•im d wiyaḍ akken ad qqimen yidiwenniyen-ik•im d iɣellsanen Seqdec tasarut n uɛeddi Ur yettwawgelhen ara - Senqed ansi i d-tkecmeḍ - Senqed akk tiqimiyin-ik·im i wakken ad tḍemneḍ amiḍan-ik·m & yiznan d iɣelsanen Senqed s ufus s ttawil n uḍris Azen Sbadu diff --git a/library/ui-strings/src/main/res/values-lo/strings.xml b/library/ui-strings/src/main/res/values-lo/strings.xml index a92adb0225..715f2894ef 100644 --- a/library/ui-strings/src/main/res/values-lo/strings.xml +++ b/library/ui-strings/src/main/res/values-lo/strings.xml @@ -1634,8 +1634,6 @@ ຢືນຢັນການເຂົ້າສູ່ລະບົບ ຢືນຢັນຂໍ້ຄວາມດ້ວຍຕົນເອງ ຢືນຢັນການເຂົ້າສູ່ລະບົບໃໝ່ທີ່ເຂົ້າເຖິງບັນຊີຂອງທ່ານ: %1$s - ຢັ້ງຢືນທຸກລະບົບຂອງທ່ານເພື່ອໃຫ້ແນ່ໃຈວ່າບັນຊີ ແລະ ຂໍ້ຄວາມຂອງທ່ານປອດໄພ - ກວດເບິ່ງບ່ອນທີ່ທ່ານເຂົ້າສູ່ລະບົບ ເຂົ້າລະຫັດໂດຍອຸປະກອນທີ່ບໍ່ໄດ້ຮັບການຢືນຢັນ ບໍ່ໄດ້ເຂົ້າລະຫັດ ສົ່ງຫິມະຕົກ ❄️ @@ -2371,7 +2369,6 @@ ສິ້ນສຸດແບບສຳຫຼວດ ອັນນີ້ຈະຢຸດບໍ່ໃຫ້ຜູ້ຄົນສາມາດລົງຄະແນນສຽງໄດ້ ແລະຈະສະແດງຜົນສຸດທ້າຍຂອງການສຳຫຼວດຄວາມຄິດເຫັນ. ສິ້ນສຸດແບບສຳຫຼວດນີ້ບໍ\? - ເລືອກຜູ້ຊະນະ ສິ້ນສຸດແບບສຳຫຼວດ ຜົນສຸດທ້າຍໂດຍອີງໃສ່ %1$d ຄະແນນສຽງ diff --git a/library/ui-strings/src/main/res/values-lt/strings.xml b/library/ui-strings/src/main/res/values-lt/strings.xml index aeba3d53e6..7ad901a9d2 100644 --- a/library/ui-strings/src/main/res/values-lt/strings.xml +++ b/library/ui-strings/src/main/res/values-lt/strings.xml @@ -1324,7 +1324,6 @@ Apklausa baigėsi Prabalsuota Baigti apklausą - laimėtojo parinktis Rezultatai bus matomi pasibaigus apklausai Nėra balsų Iš naujo paleiskite programą, kad pakeitimas įsigaliotų. @@ -2183,4 +2182,4 @@ Įjungti atidėtas AŽ Supaprastintas Element su nebūtinais skirtukais Įjungti naują išdėstymą - \ No newline at end of file + diff --git a/library/ui-strings/src/main/res/values-lv/strings.xml b/library/ui-strings/src/main/res/values-lv/strings.xml index 1787653fae..9201bf146a 100644 --- a/library/ui-strings/src/main/res/values-lv/strings.xml +++ b/library/ui-strings/src/main/res/values-lv/strings.xml @@ -559,8 +559,6 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Uzaicināt lietotājus Apstipriniet savu identitāti, verificējot šo pierakstīšanos no kādas citas savas sesijas, tādējādi ļaujot piekļūt šifrētajām ziņām. Manuāli verificēt ar tekstu - Verificējiet visas savas sesijas, lai nodrošinātos, ka jūsu konts un ziņas ir drošībā - Pārskatiet savas pierakstīšanās Nešifrēts vai kādu citu Matrix lietotni ar cross-signing atbalstu Šis konts ir deaktivizēts. diff --git a/library/ui-strings/src/main/res/values-nb-rNO/strings.xml b/library/ui-strings/src/main/res/values-nb-rNO/strings.xml index 067dbbbc28..13a4400c31 100644 --- a/library/ui-strings/src/main/res/values-nb-rNO/strings.xml +++ b/library/ui-strings/src/main/res/values-nb-rNO/strings.xml @@ -1001,8 +1001,6 @@ Hvis du tilbakestiller alt Du starter på nytt uten historikk, ingen meldinger, pålitelige enheter eller pålitelige brukere Kryptert av en ubekreftet enhet - Gjennomgå hvor du er logget inn - Bekreft alle øktene dine for å sikre at kontoen og meldingene dine er trygge Bekreft den nye påloggingen som får tilgang til kontoen din: %1$s Bekreft pålogging Bekreft identiteten din ved å bekrefte denne påloggingen fra en av de andre øktene dine, og gi den tilgang til krypterte meldinger. @@ -1253,4 +1251,4 @@ %1$s endret visningsnavnet sitt til %2$s %1$s utestengte %2$s %ss invitasjon - \ No newline at end of file + diff --git a/library/ui-strings/src/main/res/values-nl/strings.xml b/library/ui-strings/src/main/res/values-nl/strings.xml index 5bc5305df4..c6154ddb45 100644 --- a/library/ui-strings/src/main/res/values-nl/strings.xml +++ b/library/ui-strings/src/main/res/values-nl/strings.xml @@ -1858,8 +1858,6 @@ Interactief verifiëren door Emoji Handmatig verifiëren via tekst Verifieer de nieuwe login voor toegang tot je account: %1$s - Verifieer al je sessies om ervoor te zorgen dat je account en berichten veilig zijn - Bekijk waar je bent ingelogd Versleuteld door een niet-geverifieerd apparaat stuurt sneeuwval ❄️ stuurt confetti 🎉 @@ -2186,7 +2184,6 @@ Einde poll Hierdoor kunnen mensen niet meer stemmen en worden de definitieve resultaten van de poll weergegeven. Deze poll beëindigen\? - winnaar optie Einde poll Eindresultaat gebaseerd op %1$d stem @@ -2746,7 +2743,7 @@ Deze sessie is klaar voor veilige communicatie. Je huidige sessie is klaar voor veilige communicatie. Onbekende verificatiestatus - Bufferen + Bufferen Live De authenticiteit van dit versleutelde bericht kan niet worden gegarandeerd op dit apparaat. Incognito toetsenbord @@ -2787,9 +2784,6 @@ \n \nDit geeft ze het vertrouwen dat ze echt met jou praten, maar het betekent ook dat ze de sessienaam kunnen zien die je hier invoert. Sessies hernoemen - Geverifieerde sessies zijn ingelogd met jouw inloggegevens en vervolgens geverifieerd, hetzij met je veilige wachtwoordzin of door kruisverificatie. -\n -\nDit betekent dat ze coderingssleutels bevatten voor je eerdere berichten en bevestigen aan andere gebruikers waarmee je communiceert dat deze sessies echt van jou zijn. Niet-geverifieerde sessies zijn sessies die zijn aangemeld met jouw inloggegevens, maar niet zijn geverifieerd. \n \nJe moet er vooral zeker van zijn dat je deze sessies herkent, omdat ze een ongeoorloofd gebruik van je account kunnen vertegenwoordigen. @@ -2842,4 +2836,4 @@ Bewerking Recente gesprekken in het deelmenu van het systeem tonen Direct delen inschakelen - \ No newline at end of file + diff --git a/library/ui-strings/src/main/res/values-pl/strings.xml b/library/ui-strings/src/main/res/values-pl/strings.xml index 4b26562b06..0aad400340 100644 --- a/library/ui-strings/src/main/res/values-pl/strings.xml +++ b/library/ui-strings/src/main/res/values-pl/strings.xml @@ -665,7 +665,7 @@ Wycisz Ustawienia Nie ignorujesz żadnych użytkowników - Widziany przez + Wyświetlono przez Zaawansowane ustawienia Tryb programisty Ustawienia @@ -1553,8 +1553,6 @@ Interaktywna weryfikacja z wykorzystaniem emotikon Zweryfikuj logowanie Zweryfikuj nowe logowanie do swojego konta: %1$s - Sprawdź wszystkie swoje sesje żeby upewnić się, że Twoje konto oraz wiadomości są bezpieczne - Sprawdź gdzie jesteś zalogowany(-na) Zaszyfrowane przez niezweryfikowane urządzenie Niezaszyfrowane @@ -2514,7 +2512,6 @@ Otwarta ankieta Rodzaj ankiety Modyfikacja ankiety - opcja zwyciężająca Brak głosów Zaproszenie do tej przestrzeni zostało wysłane do %s, które nie jest powiązane z Twoim kontem Zaproszenie do tego pokoju zostało wysłane do %s, które nie jest powiązane z Twoim kontem @@ -2798,4 +2795,4 @@ Rozumiem Zwiń %s pokojów Rozwiń %s pokojów - \ No newline at end of file + diff --git a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml index a5aa778156..f6a2c94553 100644 --- a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml +++ b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml @@ -1526,8 +1526,6 @@ Falha para acessar armazenamento seguro Não-encripada Encriptada por um dispositivo não-verificado - Revisar onde você está com login feito - Verifique todas as suas sessões para assegurar que sua conta & mensagens estão seguras Verifique o novo login acessando sua conta: %1$s Verificar Manualmente por Texto Verificar login @@ -2272,7 +2270,6 @@ Terminar sondagem Isto vai parar pessoas de serem capazes de votar e vai exibir os resultados finais da sondagem. Terminar esta sondagem\? - opção vencedora Terminar sondagem Resultado final baseado em %1$d voto @@ -2713,9 +2710,6 @@ \n \nIsto as/os provê com confiança que elas(es) são estão realmente falando com você, mas também significa que elas(es) veem o nome da sessão que você entrar aqui. Renomeando sessões - Sessões verificadas têm feito login com suas credenciais e então têm sido verificadas, ou usando sua frasepasse segura ou por verificação cruzada. -\n -\nIsto significa que elas mantêm chaves de encriptação para suas mensagens anteriores, e confirmam a outras(os) usuárias(os) com quem você está comunicando que estas sessões são realmente você. Sessões verificadas Sessões não-verificadas são sessões que você tem feito login com suas credenciais mas não têm sido verificadas cruzado. \n @@ -2814,7 +2808,7 @@ A requisição falhou. Seja capaz de gravar e enviar broadcast de voz em timeline de sala. Broadcast de voz - Buffering… + Buffering… Pausar broadcast de voz Tocar ou retomar broadcast de voz Parar gravação de broadcast de voz @@ -2890,4 +2884,4 @@ Tem certeza que você quer parar seu broadcast ao vivo\? Isto vai terminar o broadcast e a gravação completa vai estar disponível na sala. Parar de fazer broadcasting ao vivo\? Sim, Parar - \ No newline at end of file + diff --git a/library/ui-strings/src/main/res/values-ru/strings.xml b/library/ui-strings/src/main/res/values-ru/strings.xml index 0d8f1103fe..1255776c1f 100644 --- a/library/ui-strings/src/main/res/values-ru/strings.xml +++ b/library/ui-strings/src/main/res/values-ru/strings.xml @@ -1577,8 +1577,6 @@ Не удалось получить доступ к защищенному хранилищу данных Не зашифровано Зашифровано неподтверждённой сессией - Посмотрите, где вы вошли - Подтвердите все свои сессии, чтобы убедиться в безопасности вашей учетной записи и сообщений Ручная проверка с помощью текста Перепроверьте эту ссылку Ссылка %1$s перенаправит вас на другой сайт: %2$s. @@ -2332,7 +2330,6 @@ Завершить опрос Это лишит людей возможности голосовать и отобразит окончательные результаты опроса. Завершить этот опрос\? - вариант-победитель Завершить опрос Окончательный результат на основании %1$d голоса @@ -2882,9 +2879,6 @@ Заверенные сеансы есть везде, где вы используете эту учётную запись после ввода своей мнемонической фразы или подтверждения своей личности с помощью другого заверенного сеанса. \n \nЭто означает, что у вас есть все ключи, необходимые для разблокировки ваших зашифрованных сообщений и подтверждения другим пользователям, что вы доверяете этому сеансу. - Заверенные сеансы — сеансы, которые вошли в систему с вашими учётными данными, а затем были заверены либо мнемонической фразой (бумажным ключом), либо путём перекрёстной сверки. -\n -\nЭто означает, что они хранят ключи шифрования от ваших предыдущих сообщений и подтверждают другим пользователям, с которыми вы общаетесь, что эти сеансы — действительно ваши. Незаверенные сеансы — это сеансы, которые вошли в систему с вашими учётными данными, но не были перекрёстно заверены. \n \nВы должны быть особенно уверены, что признаёте эти сеансы, поскольку они могут представлять собой несанкционированное использование вашей учётной записи. @@ -2921,7 +2915,7 @@ Не получилось начать новую голосовую трансляцию Перемотать вперёд на 30 секунд Перемотать назад на 30 секунд - Буферизация… + Буферизация… Приостановить голосовую трансляцию Проиграть или продолжить голосовую трансляцию Остановить запись голосовой трансляции @@ -2976,4 +2970,4 @@ Этот сеанс не поддерживает шифрование и поэтому не может быть заверен. %1$s завершил(а) голосовую трансляцию. Вы завершили голосовую трансляцию. - \ No newline at end of file + diff --git a/library/ui-strings/src/main/res/values-sk/strings.xml b/library/ui-strings/src/main/res/values-sk/strings.xml index 34155ba6a5..ed3f47f9d3 100644 --- a/library/ui-strings/src/main/res/values-sk/strings.xml +++ b/library/ui-strings/src/main/res/values-sk/strings.xml @@ -2001,8 +2001,6 @@ Interaktívne overte pomocou emotikonov Manuálne overte pomocou textu Overte nové prihlásenie s prístupom k vášmu účtu: %1$s - Overte všetky vaše relácie, aby ste si boli istý, že sú vaše správy a účet bezpečné - Skontrolujte, kde ste prihlásení Šifrované neovereným zariadením pošle sneženie ❄️ pošle konfety 🎉 @@ -2421,7 +2419,6 @@ Pokračovať pomocou jednotného prihlásenia SSO jednotné prihlásenie SSO Zatvoriť výzvu na zálohovanie kľúčov - Výťazná odpoveď Nezaškrtnuté Zaškrtnuté Rozpísaná správa @@ -2767,9 +2764,6 @@ \n \nTo im poskytuje istotu, že sa komunikujú naozaj s vami, ale zároveň to znamená, že vidia názov relácie, ktorý sem zadáte. Premenovanie relácií - Overené relácie, do ktorých ste sa prihlásili pomocou svojich prihlasovacích údajov a ktoré boli následne overené buď pomocou vašej bezpečnostnej prístupovej frázy, alebo krížovým overením. -\n -\nTo znamená, že majú šifrovacie kľúče pre vaše predchádzajúce správy a potvrdzujú ostatným používateľom, s ktorými komunikujete, že tieto relácie ste skutočne vy. Overené relácie Neoverené relácie sú relácie, do ktorých ste sa prihlásili pomocou svojich prístupových údajov, ale ktoré neboli krížovo overené. \n @@ -2868,7 +2862,7 @@ Žiadosť zlyhala. Možnosť nahrávania a odosielania hlasového vysielania v časovej osi miestnosti. Zapnúť hlasové vysielanie - Načítavanie do vyrovnávacej pamäte… + Načítavanie do vyrovnávacej pamäte… Pozastaviť hlasové vysielanie Prehrať alebo pokračovať v nahrávaní hlasového vysielania Zastaviť nahrávanie hlasového vysielania @@ -2955,4 +2949,33 @@ V tejto miestnosti nie sú žiadne aktívne ankety Aktívne ankety História ankety - \ No newline at end of file + Ukončená anketa + Anketa + ukončil/a anketu. + Ukončil/a anketu. + Váš domovský server zatiaľ nepodporuje zobrazovanie vlákien. + Toto hlasové vysielanie nie je možné prehrať. + Spustil/a hlasové vysielanie + Z dôvodu chýb v dešifrovaní sa niektoré hlasy nemusia započítať + Chyba pri načítavaní ankiet. + Načítať ďalšie ankety + Zobrazenie ankiet + + Za uplynulý deň nie sú k dispozícii žiadne ankety. +\nNačítaním ďalších ankiet zobrazíte ankety za predchádzajúce dni. + Za posledné %1$d dni nie sú aktívne žiadne ankety. +\nNačítaním ďalších ankiet zobrazíte ankety za predchádzajúce dni. + Za posledných %1$d dní nie sú aktívne žiadne ankety. +\nNačítaním ďalších ankiet zobrazíte ankety za predchádzajúce dni. + + + Za posledný deň nie sú aktívne žiadne ankety. +\nNačítaním ďalších ankiet zobrazíte ankety za predchádzajúce dni. + Za posledných %1$d dni nie sú aktívne žiadne ankety. +\nNačítaním ďalších ankiet zobrazíte ankety za predchádzajúce dni. + Za posledných %1$d dní nie sú aktívne žiadne ankety. +\nNačítaním ďalších ankiet zobrazíte ankety za predchádzajúce dni. + + Nemôžete spustiť hlasovú správu, pretože práve nahrávate živé vysielanie. Ukončite prosím živé vysielanie, aby ste mohli začať nahrávať hlasovú správu + Nemožno spustiť hlasovú správu + diff --git a/library/ui-strings/src/main/res/values-sq/strings.xml b/library/ui-strings/src/main/res/values-sq/strings.xml index c3f9d53c99..374080cb23 100644 --- a/library/ui-strings/src/main/res/values-sq/strings.xml +++ b/library/ui-strings/src/main/res/values-sq/strings.xml @@ -1413,8 +1413,6 @@ S’u arrit të hyhet në depozitë të sigurt Të pafshehtëzuara Fshehtëzuar nga një pajisje e paverifikuar - Shqyrtojini kur të jeni i futur - Verifikoni krejt sesionet tuaj që të siguroheni se llogaria & mesazhet tuaja janë të sigurt Verifikoni kredencialet e reja për hyrje te llogaria juaj: %1$s Verifikojeni Dorazi përmes Teksti Verifikoni kredenciale hyrjeje @@ -2512,9 +2510,6 @@ \n \nKjo u jep atyre besim se po flasin vërtet me ju, por do të thotë gjithashtu që mund shohin emrin e sesionit që jepni këtu. Riemërtim sesionesh - Sesionet e verifikuar përfaqësojnë sesione ku është bërë hyrja dhe janë verifikuar, ose duke përdorur togfjalëshin tuaj të sigurt, ose me verifikim. -\n -\nKjo do të thotë se zotërojnë kyçe fshehtëzimi për mesazhe tuajt të mëparshëm dhe u ripohojnë përdoruesve të tjerë, me të cilët po komunikoni, se këto sesione ju takojnë juve. Sesione të verifikuar Sesionet e paverifikuar janë sesione në të cilët është bërë hyrja me kredencialet tuaja, por pa u bërë verifikim. \n @@ -2659,7 +2654,7 @@ \nKy shërbyes Home mund të mos jetë formësuar të shfaqë harta. Përfundimet do të jenë të dukshme pasi të ketë përfunduar pyetësori Kur bëhet ftesë në një dhomë të fshehtëzuar që ka historik ndarjesh me të tjerët, historiku i fshehtëzuar do të jetë i dukshëm. - + Ndal transmetim zanor Luani ose vazhdoni luajtje transmetimi zanor Ndal incizim transmetimi zanor @@ -2876,10 +2871,20 @@ Tekst Tokeni juaj i hyrjeve jep hyrje të plotë në llogarinë tuaj. Mos ia jepni kujt. Token Hyrjesh - S’ka pyetësorë të kaluar në këtë dhomë - Pyetësorë të kaluar + Në këtë dhomë s’ka pyetësorë të dikurshëm + Pyetësorë të dikurshëm S’ka pyetësorë aktivë në këtë dhomë Pyetësorë aktivë - mundësia fituese Historik pyetësorësh - \ No newline at end of file + Përfundoi pyetësorin + Përfundoi pyetësorin. + Pyetësor + përfundoi një pyetësor. + Shfaq/fshi listë me toptha + Shfaq/fshi listë të numërtuar + Ujdisni lidhje + Për shkak gabimesh shfshehtëzimi, mund të mos jenë numëruar disa vota + S’arrihet të luhet ky transmetim zanor. + Nisni një transmetim zanor + Shërbyesi juaj Home s’mbulon ende paraqitje rrjedhash. + diff --git a/library/ui-strings/src/main/res/values-sv/strings.xml b/library/ui-strings/src/main/res/values-sv/strings.xml index 373165802a..877a95f2de 100644 --- a/library/ui-strings/src/main/res/values-sv/strings.xml +++ b/library/ui-strings/src/main/res/values-sv/strings.xml @@ -662,8 +662,6 @@ Kryptering inte aktiverat Aviseringskonfiguration Meddelanden som innehåller @room - Granska var du är inloggad - Verifiera alla dina sessioner för att se till att ditt konto och dina meddelanden är säkra Vi kunde inte skapa ditt DM. Vänligen kolla användarna du vill bjuda in och försök igen. BJUD IN Bjud in användare @@ -2281,7 +2279,6 @@ Avsluta omröstningen Det här kommer att stoppa personer från att rösta och visa det slutgiltiga resultatet av omröstningen. Avsluta den här omröstningen\? - vinnande alternativ Avsluta omröstning Slutgiltigt resultat baserat på %1$d röst @@ -2737,9 +2734,6 @@ \n \nDet försäkrar dem om att de verkligen pratar med dig, men det betyder också att de kan se sessionsnamnet du anger här. Döper om sessioner - Verifierade sessioner har loggat in med dina uppgifter och har sedan verifierats, antingen med din säkra lösenfras eller genom att kors-verifiera. -\n -\nDet betyder att det har krypteringsnycklar för dina tidigare meddelanden, bekräftar för andra användare du kommunicerar med att dessa sessioner verkligen är du. Verifierade sessioner Overifierade sessioner är sessioner som har loggat in med dina uppgifter men som inte har kors-verifierats. \n @@ -2804,7 +2798,7 @@ Använd din inloggade enhet för att skanna QR-koden nedan: Logga in med QR-kod Använd den här enhetens kamera för att skanna QR-koden på din andra enhet: - Buffrar… + Buffrar… Pausa röstsändning Spela eller återuppta röstsändning Avsluta inspelning av röstsändning @@ -2882,4 +2876,26 @@ Direktsändning Du avslutade en röstsändning. %1$s avslutade en röstsändning. - \ No newline at end of file + Din åtkomsttoken ger full åtkomst till ditt konto. Dela den inte med någon. + Åtkomsttoken + Avslutade omröstning + Omröstning + avslutade en omröstning. + Redigera länk + Skapa en länk + Länk + Text + Växla punktlista + Växla numrerad lista + Sätt länk + Det finns inga tidigare omröstningar i det här rummet + Tidigare omröstningar + Det finns inga aktiva omröstningar i det här rummet + Aktiva omröstningar + Avslutade omröstningen. + Är du säker på att du vill avsluta din direktsändning\? Detta kommer att avsluta sändningen och den fulla inspelningen kommer att bli tillgänglig i rummet. + Avsluta röstsändning\? + Omröstningshistorik + Din hemserver har inte stöd för att lista trådar än. + Ja, sluta + diff --git a/library/ui-strings/src/main/res/values-sw/strings.xml b/library/ui-strings/src/main/res/values-sw/strings.xml new file mode 100644 index 0000000000..d1938c5896 --- /dev/null +++ b/library/ui-strings/src/main/res/values-sw/strings.xml @@ -0,0 +1,14 @@ + + + umeondoa %1$s + %1$s kuondolewa %2$s + Ulikataa mwaliko + %1$s alikataa mwaliko + Ulijiunga + %1$s alijiunga + %1$s Amekualika + + %1$d Iliyochaguliwa + %1$d Ziliyochaguliwa + + diff --git a/library/ui-strings/src/main/res/values-uk/strings.xml b/library/ui-strings/src/main/res/values-uk/strings.xml index 2ee9685c76..6294526be2 100644 --- a/library/ui-strings/src/main/res/values-uk/strings.xml +++ b/library/ui-strings/src/main/res/values-uk/strings.xml @@ -1156,7 +1156,6 @@ Довірений Не довірений вхід Підтвердьте особу, звіривши цей вхід своїм іншим сеансом і надавши йому доступ до зашифрованих повідомлень. - Звірте усі свої сеанси, щоб переконатись у безпечності вашого облікового запису та повідомлень Сеанси Не вдалось отримати сеанси Ви не маєте доступу до цього повідомлення, бо відправник не довіряє вашому сеансу @@ -2012,7 +2011,6 @@ Додати людей Додати учасників Перевірте це посилання - Перевірте, де ви ввійшли Інтерактивна перевірка за допомогою емоджі Виберіть пароль. Виберіть ім\'я користувача. @@ -2360,7 +2358,6 @@ Завершити опитування Люди більше не зможуть голосувати, і будуть показані остаточні результати опитування. Завершити це опитування\? - варіант-переможець Завершити опитування Остаточний результат на підставі %1$d голосу @@ -2821,9 +2818,6 @@ \n \nЦе дає їм впевненість у тому, що вони дійсно розмовляють з вами, а також означає, що вони можуть бачити назву сеансу, яку ви ввели тут. Перейменування сеансів - Звірені сеанси — ті, до яких ви ввійшли за допомогою своїх облікових даних, а потім пройшли перевірку, використовуючи вашу захищену парольну фразу або шляхом перехресної перевірки. -\n -\nЦе означає, що вони мають ключі шифрування для ваших попередніх повідомлень і підтверджують іншим користувачам, з якими ви спілкуєтеся, що ці сеанси — це дійсно ви. Звірені сеанси Не звірені сеанси — це сеанси, до яких ви ввійшли в за допомогою своїх облікових даних, але не пройшли перехресну перевірку. \n @@ -2922,7 +2916,7 @@ Запит не виконаний. Можливість записувати та надсилати голосові трансляції до стрічки кімнати. Увімкнути голосові трансляції - Буферизація… + Буферизація… Призупинити голосову трансляцію Відтворити або поновити відтворення голосової трансляції Припинити запис голосової трансляції @@ -3011,4 +3005,37 @@ У цій кімнаті немає активних опитувань Активні опитування Історія опитувань - \ No newline at end of file + Завершене опитування + Опитування + завершує опитування. + Опитування завершено. + Ваш домашній сервер поки що не підтримує створення списків гілок. + Неможливо відтворити цю голосову трансляцію. + Розпочато голосову трансляцію + Через помилки розшифрування деякі голоси можуть бути не враховані + + За %1$d минулий день немає минулих опитувань. +\nЗавантажте більше опитувань, щоб переглянути опитування за попередні дні. + За минулі %1$d дні немає минулих опитувань. +\nЗавантажте більше опитувань, щоб переглянути опитування за попередні дні. + За минулі %1$d днів немає минулих опитувань. +\nЗавантажте більше опитувань, щоб переглянути опитування за попередні дні. + За минулі %1$d днів немає минулих опитувань. +\nЗавантажте більше опитувань, щоб переглянути опитування за попередні дні. + + + За %1$d останній день немає активних опитувань. +\nЗавантажте більше опитувань, щоб переглянути опитування за попередні дні. + За останні %1$d дні немає активних опитувань. +\nЗавантажте більше опитувань, щоб переглянути опитування за попередні дні. + За останні %1$d днів немає активних опитувань. +\nЗавантажте більше опитувань, щоб переглянути опитування за попередні дні. + За останні %1$d днів немає активних опитувань. +\nЗавантажте більше опитувань, щоб переглянути опитування за попередні дні. + + Помилка отримання опитувань. + Завантажити більше опитувань + Показ опитувань + Ви не можете розпочати запис голосового повідомлення, оскільки ви записуєте трансляцію наживо. Будь ласка, заверште її, щоб розпочати запис голосового повідомлення + Не вдалося розпочати запис голосового повідомлення + diff --git a/library/ui-strings/src/main/res/values-vi/strings.xml b/library/ui-strings/src/main/res/values-vi/strings.xml index c6dc97f782..70755f1394 100644 --- a/library/ui-strings/src/main/res/values-vi/strings.xml +++ b/library/ui-strings/src/main/res/values-vi/strings.xml @@ -1193,7 +1193,6 @@ Kết thúc cuộc thăm dò ý kiến Điều này sẽ ngăn mọi người có thể bỏ phiếu và sẽ hiển thị kết quả cuối cùng của cuộc thăm dò. Kết thúc cuộc thăm dò này\? - tùy chọn người chiến thắng Kết thúc cuộc thăm dò ý kiến Câu hỏi không thể trống TẠO CUỘC THĂM DÒ Ý KIẾN @@ -1472,8 +1471,6 @@ Xác minh đăng nhập Xác minh thủ công bằng Văn bản Xác minh thông tin đăng nhập mới truy cập vào tài khoản của bạn: %1$s - Xác minh tất cả các phiên của bạn để đảm bảo tài khoản và tin nhắn của bạn được an toàn - Xem lại nơi bạn đăng nhập Được mã hóa bởi một thiết bị chưa được xác minh Không được mã hóa gửi tuyết rơi ❄️ diff --git a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml index 9f975e61e4..1e75540acf 100644 --- a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml @@ -1459,8 +1459,6 @@ 访问安全存储失败 未加密 由未验证设备加密 - 查看你的登录位置 - 验证你的全部会话确保你的账户和消息安全 验证访问你的账户的新登录:%1$s 使用文本手动验证 验证登录 @@ -1692,8 +1690,8 @@ 一些字符不被允许 请提供一个房间地址 此地址已被使用 - 你可以启用此选项如果此房间将仅用于你的主服务器上的内部团队协作。此选项之后无法更改。 - 屏蔽不是 %s 一部分的任何人加入此房间 + 若房间仅用于与你的主服务器上的内部团队协作,则你可以启用此选项。此选项之后无法更改。 + 阻止任何不属于%s的人加入此房间 隐藏高级 显示高级 清除历史记录 @@ -2231,7 +2229,6 @@ 结束投票 这将使人们无法再投票,并将显示投票的最终结果。 结束此投票? - 赢家选项 结束投票 基于 %1$d 票的最终结果 @@ -2648,9 +2645,6 @@ \n \n这让他们确信他们真的在与你交谈,但这也意味着他们可以看到你在此处输入的会话名称。 重命名会话 - 已验证会话已使用你的凭据登录,然后使用你的安全密码或通过交叉验证进行验证。 -\n -\n这意味着他们持有你之前消息的加密密钥,并向你正在与之通信的其他用户确认这些会话确实是你。 闲置会话是你一段时间未使用的会话,但它们会继续接收加密密钥。 \n \n删除闲置会话可以提高安全性和性能,并使你更容易识别新会话是否可疑。 @@ -2694,7 +2688,7 @@ 验证你当前的会话以显示此会话的验证状态。 未知的验证状态 开始语音广播 - 正在缓冲…… + 正在缓冲…… 暂停语音广播 实时 知道了 @@ -2821,4 +2815,8 @@ Nightly构建 你结束了一个语音广播。 %1$s结束了一个语音广播。 - \ No newline at end of file + 停止实时广播? + 无法播放此语音广播。 + 你无法启动语音消息因为你正在录制实时广播。请终止实时广播以开始录制语音消息 + 无法启动语音消息 + diff --git a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml index 14729c5b44..c650a1e6b2 100644 --- a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml @@ -1412,8 +1412,6 @@ 我們無法建立您的直接訊息。請檢查您想要邀請的使用者,然後再試一次。 未加密 由未驗證的裝置加密 - 審閱您從何處登入 - 驗證您所有的工作階段以確保您的帳號與訊息都安全 驗證正在存取您帳號的新登入:%1$s %1$s:%2$s %1$s:%2$s %3$s @@ -2231,7 +2229,6 @@ 結束投票 這將阻止人們投票並顯示投票的最終結果。 結束此投票? - 獲勝選項 結束投票 以 %1$d 票為基礎的最終結果 @@ -2659,9 +2656,6 @@ \n \n這讓他們確信他們真的在與您交談,但這也意味著他們可以看到您在此處輸入的工作階段名稱。 正在重新命名工作階段 - 已驗證的工作階段代表使用您的憑證登入,然後使用您的安全通關密語或透過交叉驗證進行驗證。 -\n -\n這代表了它們持有您先前訊息的加密金鑰,並向您正在與之通訊的其他使用者確認這些工作階段確實是您。 已驗證的工作階段 未驗證的工作階段是使用您的憑證登入但未交叉驗證的工作階段。 \n @@ -2760,7 +2754,7 @@ 請求失敗。 可以在聊天室時間軸中錄製並傳送語音廣播。 啟用語音廣播 - 正在緩衝…… + 正在緩衝…… 暫停語音廣播 播放或繼續語音廣播 停止語音廣播錄製 @@ -2843,4 +2837,23 @@ 此聊天室沒有正在進行的投票 進行中的投票 投票歷史紀錄 - \ No newline at end of file + 已結束投票 + 投票 + 已結束投票。 + 已結束投票。 + 您的家伺服器還不支援列出討論串。 + 無法播放此語音廣播。 + 已開始語音廣播 + 因為解密錯誤,部份投票可能並未計算 + 擷取投票時發生錯誤。 + 載入更多投票 + 顯示投票 + + 過去 %1$d 天沒有投票。 +\n載入更多投票以檢視過去幾天的投票。 + + + 過去 %1$d 天沒有活躍的投票。 +\n載入更多投票以檢視過去幾天的投票。 + + diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 49ec137b0a..2bb9c31520 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -1692,7 +1692,6 @@ Create New Room Create New Space No network. Please check your Internet connection. - Something went wrong. Please check your network connection and try again. "Change network" "Please wait…" @@ -2662,10 +2661,6 @@ Unencrypted Encrypted by an unverified device The authenticity of this encrypted message can\'t be guaranteed on this device. - - Review where you’re logged in - - Verify all your sessions to ensure your account & messages are safe You have unverified sessions Review to ensure your account is safe @@ -3100,6 +3095,8 @@ Cannot play this voice message Cannot record a voice message Cannot reply or edit while voice message is active + Cannot start voice message + You can’t start a voice message as you are currently recording a live broadcast. Please end your live broadcast in order to start recording a voice message Voice Message (%1$s) %1$s, %2$s, %3$s @@ -3112,8 +3109,7 @@ Live Live broadcast - - Buffering… + Buffering… Resume voice broadcast record Pause voice broadcast record Stop voice broadcast record @@ -3126,6 +3122,7 @@ Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one. You are already recording a voice broadcast. Please end your current voice broadcast to start a new one. Unable to play this voice broadcast. + Connection error - Recording paused %1$s left Stop live broadcasting? @@ -3183,8 +3180,6 @@ Final result based on %1$d votes End poll - - winner option End this poll? This will stop people from being able to vote and will display the final results of the poll. End poll @@ -3430,8 +3425,6 @@ Unverified sessions Unverified sessions are sessions that have logged in with your credentials but not been cross-verified.\n\nYou should make especially certain that you recognise these sessions as they could represent an unauthorised use of your account. Verified sessions - - Verified sessions have logged in with your credentials and then been verified, either using your secure passphrase or by cross-verifying.\n\nThis means they hold encryption keys for your previous messages, and confirm to other users you are communicating with that these sessions are really you. Verified sessions are anywhere you are using this account after entering your passphrase or confirming your identity with another verified session.\n\nThis means that you have all the keys needed to unlock your encrypted messages and confirm to other users that you trust this session. This session doesn\'t support encryption, so it can\'t be verified.\n\nYou won\'t be able to participate in rooms where encryption is enabled when using this session.\n\nFor best security and privacy, it is recommended to use Matrix clients that support encryption. Renaming sessions diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 9c9d2dd0dc..33afbfe02c 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -62,7 +62,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.5.22\"" + buildConfigField "String", "SDK_VERSION", "\"1.5.24\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/MetricsExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/MetricsExtensions.kt index 7f0e828f62..5aa175f994 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/MetricsExtensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/MetricsExtensions.kt @@ -43,6 +43,27 @@ inline fun List.measureMetric(block: () -> Unit) { } } +/** + * Executes the given [block] while measuring the transaction. + * + * @param block Action/Task to be executed within this span. + */ +@OptIn(ExperimentalContracts::class) +inline fun List.measureSpannableMetric(block: List.() -> Unit) { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + try { + this.forEach { plugin -> plugin.startTransaction() } // Start the transaction. + block() + } catch (throwable: Throwable) { + this.forEach { plugin -> plugin.onError(throwable) } // Capture if there is any exception thrown. + throw throwable + } finally { + this.forEach { plugin -> plugin.finishTransaction() } // Finally, finish this transaction. + } +} + /** * Executes the given [block] while measuring a span. * diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/metrics/SyncDurationMetricPlugin.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/metrics/SyncDurationMetricPlugin.kt index 79ece002e9..e54eb3cccf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/metrics/SyncDurationMetricPlugin.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/metrics/SyncDurationMetricPlugin.kt @@ -29,4 +29,6 @@ interface SyncDurationMetricPlugin : SpannableMetricPlugin { override fun logTransaction(message: String?) { Timber.tag(loggerTag.value).v("## syncResponseHandler() : $message") } + + fun shouldReport(isInitialSync: Boolean, isAfterPause: Boolean): Boolean = true } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt index a0f76bad21..4968df775a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt @@ -75,6 +75,12 @@ data class HomeServerCapabilities( * True if the home server supports remote toggle of Pusher for a given device. */ val canRemotelyTogglePushNotificationsOfDevices: Boolean = false, + + /** + * True if the home server supports event redaction with relations. + */ + var canRedactEventWithRelations: Boolean = false, + /** * External account management url for use with MSC3824 delegated OIDC, provided in Wellknown. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index 6a6fadc95a..07036f4b65 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -156,11 +156,12 @@ interface SendService { /** * Redact (delete) the given event. - * @param event The event to redact - * @param reason Optional reason string + * @param event the event to redact + * @param reason optional reason string + * @param withRelations the list of relation types to redact with this event * @param additionalContent additional content to put in the event content */ - fun redactEvent(event: Event, reason: String?, additionalContent: Content? = null): Cancelable + fun redactEvent(event: Event, reason: String?, withRelations: List? = null, additionalContent: Content? = null): Cancelable /** * Schedule this message to be resent. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt index f4de6a9ae9..4d8e90cf35 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt @@ -58,6 +58,8 @@ private const val FEATURE_QR_CODE_LOGIN = "org.matrix.msc3882" private const val FEATURE_THREADS_MSC3771 = "org.matrix.msc3771" private const val FEATURE_THREADS_MSC3773 = "org.matrix.msc3773" private const val FEATURE_REMOTE_TOGGLE_PUSH_NOTIFICATIONS_MSC3881 = "org.matrix.msc3881" +private const val FEATURE_EVENT_REDACTION_WITH_RELATIONS = "org.matrix.msc3912" +private const val FEATURE_EVENT_REDACTION_WITH_RELATIONS_STABLE = "org.matrix.msc3912.stable" /** * Return true if the SDK supports this homeserver version. @@ -153,3 +155,13 @@ private fun Versions.getMaxVersion(): HomeServerVersion { internal fun Versions.doesServerSupportRemoteToggleOfPushNotifications(): Boolean { return unstableFeatures?.get(FEATURE_REMOTE_TOGGLE_PUSH_NOTIFICATIONS_MSC3881).orFalse() } + +/** + * Indicate if the server supports MSC3912: https://github.com/matrix-org/matrix-spec-proposals/pull/3912. + * + * @return true if event redaction with relations is supported + */ +internal fun Versions.doesServerSupportRedactEventWithRelations(): Boolean { + return unstableFeatures?.get(FEATURE_EVENT_REDACTION_WITH_RELATIONS).orFalse() || + unstableFeatures?.get(FEATURE_EVENT_REDACTION_WITH_RELATIONS_STABLE).orFalse() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt index 56bdc8cae8..b060748a61 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt @@ -15,9 +15,12 @@ */ package org.matrix.android.sdk.internal.crypto.tasks +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.send.model.EventRedactBody import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -26,22 +29,34 @@ internal interface RedactEventTask : Task { val txID: String, val roomId: String, val eventId: String, - val reason: String? + val reason: String?, + val withRelations: List?, ) } internal class DefaultRedactEventTask @Inject constructor( private val roomAPI: RoomAPI, - private val globalErrorReceiver: GlobalErrorReceiver + private val globalErrorReceiver: GlobalErrorReceiver, + private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource, ) : RedactEventTask { override suspend fun execute(params: RedactEventTask.Params): String { + val withRelations = if (homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.canRedactEventWithRelations.orFalse() && + !params.withRelations.isNullOrEmpty()) { + params.withRelations + } else { + null + } + val response = executeRequest(globalErrorReceiver) { roomAPI.redactEvent( txId = params.txID, roomId = params.roomId, eventId = params.eventId, - reason = if (params.reason == null) emptyMap() else mapOf("reason" to params.reason) + body = EventRedactBody( + reason = params.reason, + withRelations = withRelations, + ) ) } return response.eventId diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 2b7e9a04a1..fe55beb997 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -65,6 +65,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo045 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo046 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo047 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo048 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo049 import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -73,7 +74,7 @@ internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer ) : MatrixRealmMigration( dbName = "Session", - schemaVersion = 48L, + schemaVersion = 49L, ) { /** * Forces all RealmSessionStoreMigration instances to be equal. @@ -131,5 +132,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 46) MigrateSessionTo046(realm).perform() if (oldVersion < 47) MigrateSessionTo047(realm).perform() if (oldVersion < 48) MigrateSessionTo048(realm).perform() + if (oldVersion < 49) MigrateSessionTo049(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt index 40b33cd13b..1c7a0591a1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt @@ -47,6 +47,7 @@ internal object HomeServerCapabilitiesMapper { canLoginWithQrCode = entity.canLoginWithQrCode, canUseThreadReadReceiptsAndNotifications = entity.canUseThreadReadReceiptsAndNotifications, canRemotelyTogglePushNotificationsOfDevices = entity.canRemotelyTogglePushNotificationsOfDevices, + canRedactEventWithRelations = entity.canRedactEventWithRelations, externalAccountManagementUrl = entity.externalAccountManagementUrl, ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo049.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo049.kt index 827bd7ae15..31a5305777 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo049.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo049.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * Copyright (c) 2023 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. @@ -25,7 +25,10 @@ internal class MigrateSessionTo049(realm: DynamicRealm) : RealmMigrator(realm, 4 override fun doMigrate(realm: DynamicRealm) { realm.schema.get("HomeServerCapabilitiesEntity") - ?.addField(HomeServerCapabilitiesEntityFields.EXTERNAL_ACCOUNT_MANAGEMENT_URL, String::class.java) + ?.addField(HomeServerCapabilitiesEntityFields.CAN_REDACT_EVENT_WITH_RELATIONS, Boolean::class.java) + ?.transform { obj -> + obj.set(HomeServerCapabilitiesEntityFields.CAN_REDACT_EVENT_WITH_RELATIONS, false) + } ?.forceRefreshOfHomeServerCapabilities() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo050.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo050.kt new file mode 100644 index 0000000000..c3777e776e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo050.kt @@ -0,0 +1,42 @@ +/* +<<<<<<< HEAD + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. +======= + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. +>>>>>>> develop + * + * 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.database.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields +import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo049(realm: DynamicRealm) : RealmMigrator(realm, 49) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("HomeServerCapabilitiesEntity") +<<<<<<< HEAD + ?.addField(HomeServerCapabilitiesEntityFields.EXTERNAL_ACCOUNT_MANAGEMENT_URL, String::class.java) +======= + ?.addField(HomeServerCapabilitiesEntityFields.CAN_REDACT_EVENT_WITH_RELATIONS, Boolean::class.java) + ?.transform { obj -> + obj.set(HomeServerCapabilitiesEntityFields.CAN_REDACT_EVENT_WITH_RELATIONS, false) + } +>>>>>>> develop + ?.forceRefreshOfHomeServerCapabilities() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt index c081535cf4..35a5c654de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt @@ -34,6 +34,7 @@ internal open class HomeServerCapabilitiesEntity( var canLoginWithQrCode: Boolean = false, var canUseThreadReadReceiptsAndNotifications: Boolean = false, var canRemotelyTogglePushNotificationsOfDevices: Boolean = false, + var canRedactEventWithRelations: Boolean = false, var externalAccountManagementUrl: String? = null, ) : RealmObject() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt index 72b638481b..ec12695ecd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.internal.auth.version.Versions import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices import org.matrix.android.sdk.internal.auth.version.doesServerSupportQrCodeLogin +import org.matrix.android.sdk.internal.auth.version.doesServerSupportRedactEventWithRelations import org.matrix.android.sdk.internal.auth.version.doesServerSupportRemoteToggleOfPushNotifications import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreadUnreadNotifications import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreads @@ -154,6 +155,8 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( getVersionResult.doesServerSupportQrCodeLogin() homeServerCapabilitiesEntity.canRemotelyTogglePushNotificationsOfDevices = getVersionResult.doesServerSupportRemoteToggleOfPushNotifications() + homeServerCapabilitiesEntity.canRedactEventWithRelations = + getVersionResult.doesServerSupportRedactEventWithRelations() } if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index 34b6ee525d..aa4bdb1dd4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt @@ -37,6 +37,7 @@ import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse import org.matrix.android.sdk.internal.session.room.relation.threads.ThreadSummariesResponse import org.matrix.android.sdk.internal.session.room.reporting.ReportContentBody import org.matrix.android.sdk.internal.session.room.send.SendResponse +import org.matrix.android.sdk.internal.session.room.send.model.EventRedactBody import org.matrix.android.sdk.internal.session.room.tags.TagBody import org.matrix.android.sdk.internal.session.room.timeline.EventContextResponse import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse @@ -61,7 +62,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "publicRooms") suspend fun publicRooms( @Query("server") server: String?, - @Body publicRoomsParams: PublicRoomsParams + @Body publicRoomsParams: PublicRoomsParams, ): PublicRoomsResponse /** @@ -91,7 +92,7 @@ internal interface RoomAPI { @Query("from") from: String, @Query("dir") dir: String, @Query("limit") limit: Int?, - @Query("filter") filter: String? + @Query("filter") filter: String?, ): PaginationResponse /** @@ -107,7 +108,7 @@ internal interface RoomAPI { @Path("roomId") roomId: String, @Query("at") syncToken: String?, @Query("membership") membership: Membership?, - @Query("not_membership") notMembership: Membership? + @Query("not_membership") notMembership: Membership?, ): RoomMembersResponse /** @@ -123,7 +124,7 @@ internal interface RoomAPI { @Path("txId") txId: String, @Path("roomId") roomId: String, @Path("eventType") eventType: String, - @Body content: Content? + @Body content: Content?, ): SendResponse /** @@ -139,7 +140,7 @@ internal interface RoomAPI { @Path("roomId") roomId: String, @Path("eventId") eventId: String, @Query("limit") limit: Int, - @Query("filter") filter: String? = null + @Query("filter") filter: String? = null, ): EventContextResponse /** @@ -151,7 +152,7 @@ internal interface RoomAPI { @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/event/{eventId}") suspend fun getEvent( @Path("roomId") roomId: String, - @Path("eventId") eventId: String + @Path("eventId") eventId: String, ): Event /** @@ -163,7 +164,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/read_markers") suspend fun sendReadMarker( @Path("roomId") roomId: String, - @Body markers: Map + @Body markers: Map, ) /** @@ -174,7 +175,7 @@ internal interface RoomAPI { @Path("roomId") roomId: String, @Path("receiptType") receiptType: String, @Path("eventId") eventId: String, - @Body body: ReadBody + @Body body: ReadBody, ) /** @@ -187,7 +188,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite") suspend fun invite( @Path("roomId") roomId: String, - @Body body: InviteBody + @Body body: InviteBody, ) /** @@ -199,7 +200,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite") suspend fun invite3pid( @Path("roomId") roomId: String, - @Body body: ThreePidInviteBody + @Body body: ThreePidInviteBody, ) /** @@ -213,7 +214,7 @@ internal interface RoomAPI { suspend fun sendStateEvent( @Path("roomId") roomId: String, @Path("state_event_type") stateEventType: String, - @Body params: JsonDict + @Body params: JsonDict, ): SendResponse /** @@ -229,7 +230,7 @@ internal interface RoomAPI { @Path("roomId") roomId: String, @Path("state_event_type") stateEventType: String, @Path("state_key") stateKey: String, - @Body params: JsonDict + @Body params: JsonDict, ): SendResponse /** @@ -257,7 +258,7 @@ internal interface RoomAPI { @Path("eventType") eventType: String, @Query("from") from: String? = null, @Query("to") to: String? = null, - @Query("limit") limit: Int? = null + @Query("limit") limit: Int? = null, ): RelationsResponse /** @@ -277,7 +278,7 @@ internal interface RoomAPI { @Path("relationType") relationType: String, @Query("from") from: String? = null, @Query("to") to: String? = null, - @Query("limit") limit: Int? = null + @Query("limit") limit: Int? = null, ): RelationsResponse /** @@ -291,7 +292,7 @@ internal interface RoomAPI { suspend fun join( @Path("roomIdOrAlias") roomIdOrAlias: String, @Query("server_name") viaServers: List, - @Body params: JsonDict + @Body params: JsonDict, ): JoinRoomResponse /** @@ -303,7 +304,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/leave") suspend fun leave( @Path("roomId") roomId: String, - @Body params: Map + @Body params: Map, ) /** @@ -315,7 +316,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/ban") suspend fun ban( @Path("roomId") roomId: String, - @Body userIdAndReason: UserIdAndReason + @Body userIdAndReason: UserIdAndReason, ) /** @@ -327,7 +328,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/unban") suspend fun unban( @Path("roomId") roomId: String, - @Body userIdAndReason: UserIdAndReason + @Body userIdAndReason: UserIdAndReason, ) /** @@ -339,7 +340,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/kick") suspend fun kick( @Path("roomId") roomId: String, - @Body userIdAndReason: UserIdAndReason + @Body userIdAndReason: UserIdAndReason, ) /** @@ -350,14 +351,14 @@ internal interface RoomAPI { * @param txId the transaction Id * @param roomId the room id * @param eventId the event to delete - * @param reason json containing reason key {"reason": "Indecent material"} + * @param body body containing reason key {"reason": "Indecent material"} and with_relations */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/redact/{eventId}/{txnId}") suspend fun redactEvent( @Path("txnId") txId: String, @Path("roomId") roomId: String, @Path("eventId") eventId: String, - @Body reason: Map + @Body body: EventRedactBody, ): SendResponse /** @@ -371,7 +372,7 @@ internal interface RoomAPI { suspend fun reportContent( @Path("roomId") roomId: String, @Path("eventId") eventId: String, - @Body body: ReportContentBody + @Body body: ReportContentBody, ) /** @@ -388,7 +389,7 @@ internal interface RoomAPI { suspend fun sendTypingState( @Path("roomId") roomId: String, @Path("userId") userId: String, - @Body body: TypingBody + @Body body: TypingBody, ) /* @@ -403,7 +404,7 @@ internal interface RoomAPI { @Path("userId") userId: String, @Path("roomId") roomId: String, @Path("tag") tag: String, - @Body body: TagBody + @Body body: TagBody, ) /** @@ -413,7 +414,7 @@ internal interface RoomAPI { suspend fun deleteTag( @Path("userId") userId: String, @Path("roomId") roomId: String, - @Path("tag") tag: String + @Path("tag") tag: String, ) /** @@ -424,7 +425,7 @@ internal interface RoomAPI { @Path("userId") userId: String, @Path("roomId") roomId: String, @Path("type") type: String, - @Body content: JsonDict + @Body content: JsonDict, ) /** @@ -437,7 +438,7 @@ internal interface RoomAPI { suspend fun deleteRoomAccountData( @Path("userId") userId: String, @Path("roomId") roomId: String, - @Path("type") type: String + @Path("type") type: String, ) /** @@ -450,7 +451,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/upgrade") suspend fun upgradeRoom( @Path("roomId") roomId: String, - @Body body: RoomUpgradeBody + @Body body: RoomUpgradeBody, ): RoomUpgradeResponse /** @@ -462,7 +463,7 @@ internal interface RoomAPI { @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "im.nheko.summary/rooms/{roomIdOrAlias}/summary") suspend fun getRoomSummary( @Path("roomIdOrAlias") roomidOrAlias: String, - @Query("via") viaServers: List? + @Query("via") viaServers: List?, ): RoomStrippedState @GET(NetworkConstants.URI_API_PREFIX_PATH_V1 + "rooms/{roomId}/threads") @@ -470,6 +471,6 @@ internal interface RoomAPI { @Path("roomId") roomId: String, @Query("include") include: String? = "all", @Query("from") from: String? = null, - @Query("limit") limit: Int? = null + @Query("limit") limit: Int? = null, ): ThreadSummariesResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index 9cdbc7ff46..d29e7d8f36 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -140,11 +140,11 @@ internal class DefaultSendService @AssistedInject constructor( .let { sendEvent(it) } } - override fun redactEvent(event: Event, reason: String?, additionalContent: Content?): Cancelable { + override fun redactEvent(event: Event, reason: String?, withRelations: List?, additionalContent: Content?): Cancelable { // TODO manage media/attachements? - val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason, additionalContent) + val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason, withRelations, additionalContent) .also { createLocalEcho(it) } - return eventSenderProcessor.postRedaction(redactionEcho, reason) + return eventSenderProcessor.postRedaction(redactionEcho, reason, withRelations) } override fun resendTextMessage(localEcho: TimelineEvent): Cancelable { 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 d974c597ac..38024b7aa8 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 @@ -70,6 +70,7 @@ import org.matrix.android.sdk.api.util.TextContent import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory +import org.matrix.android.sdk.internal.session.room.send.model.EventRedactBody import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils import org.matrix.android.sdk.internal.util.time.Clock import java.util.UUID @@ -795,8 +796,16 @@ internal class LocalEchoEventFactory @Inject constructor( } } */ - fun createRedactEvent(roomId: String, eventId: String, reason: String?, additionalContent: Content? = null): Event { + fun createRedactEvent(roomId: String, eventId: String, reason: String?, withRelations: List? = null, additionalContent: Content? = null): Event { val localId = LocalEcho.createLocalEchoId() + val content = if (reason != null || withRelations != null) { + EventRedactBody( + reason = reason, + withRelations = withRelations, + ).toContent().plus(additionalContent.orEmpty()) + } else { + additionalContent + } return Event( roomId = roomId, originServerTs = dummyOriginServerTs(), @@ -804,7 +813,7 @@ internal class LocalEchoEventFactory @Inject constructor( eventId = localId, type = EventType.REDACTION, redacts = eventId, - content = reason?.let { mapOf("reason" to it).toContent().plus(additionalContent.orEmpty()) } ?: additionalContent, + content = content, unsignedData = UnsignedData(age = null, transactionId = localId) ) } @@ -830,10 +839,10 @@ internal class LocalEchoEventFactory @Inject constructor( createMessageEvent( roomId, textContent.toThreadTextContent( - rootThreadEventId = rootThreadEventId, - latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId), - msgType = MessageType.MSGTYPE_TEXT - ), + rootThreadEventId = rootThreadEventId, + latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId), + msgType = MessageType.MSGTYPE_TEXT + ), additionalContent, ) } else { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt index 765c282b65..576f31c64c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt @@ -20,8 +20,8 @@ import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.internal.SessionManager +import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask import org.matrix.android.sdk.internal.network.GlobalErrorReceiver -import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker @@ -43,27 +43,29 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters, ses val roomId: String, val eventId: String, val reason: String?, + val withRelations: List? = null, override val lastFailureMessage: String? = null ) : SessionWorkerParams @Inject lateinit var roomAPI: RoomAPI @Inject lateinit var globalErrorReceiver: GlobalErrorReceiver + @Inject lateinit var redactEventTask: RedactEventTask override fun injectWith(injector: SessionComponent) { injector.inject(this) } override suspend fun doSafeWork(params: Params): Result { - val eventId = params.eventId return runCatching { - executeRequest(globalErrorReceiver) { - roomAPI.redactEvent( - params.txID, - params.roomId, - eventId, - if (params.reason == null) emptyMap() else mapOf("reason" to params.reason) - ) - } + redactEventTask.execute( + RedactEventTask.Params( + txID = params.txID, + roomId = params.roomId, + eventId = params.eventId, + reason = params.reason, + withRelations = params.withRelations, + ) + ) }.fold( { Result.success() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/model/EventRedactBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/model/EventRedactBody.kt new file mode 100644 index 0000000000..cf2bc0dc4f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/model/EventRedactBody.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2023 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.room.send.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class EventRedactBody( + @Json(name = "reason") + val reason: String? = null, + + @Json(name = "org.matrix.msc3912.with_relations") + val withRelations: List? = null, +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt index 050e321b9c..b285e90c9a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt @@ -26,9 +26,9 @@ internal interface EventSenderProcessor : SessionLifecycleObserver { fun postEvent(event: Event, encrypt: Boolean): Cancelable - fun postRedaction(redactionLocalEcho: Event, reason: String?): Cancelable + fun postRedaction(redactionLocalEcho: Event, reason: String?, withRelations: List? = null): Cancelable - fun postRedaction(redactionLocalEchoId: String, eventToRedactId: String, roomId: String, reason: String?): Cancelable + fun postRedaction(redactionLocalEchoId: String, eventToRedactId: String, roomId: String, reason: String?, withRelations: List? = null): Cancelable fun postTask(task: QueuedTask): Cancelable diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt index 2c7eea1e54..929fe7b9a6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt @@ -101,12 +101,18 @@ internal class EventSenderProcessorCoroutine @Inject constructor( return postTask(task) } - override fun postRedaction(redactionLocalEcho: Event, reason: String?): Cancelable { - return postRedaction(redactionLocalEcho.eventId!!, redactionLocalEcho.redacts!!, redactionLocalEcho.roomId!!, reason) + override fun postRedaction(redactionLocalEcho: Event, reason: String?, withRelations: List?): Cancelable { + return postRedaction(redactionLocalEcho.eventId!!, redactionLocalEcho.redacts!!, redactionLocalEcho.roomId!!, reason, withRelations) } - override fun postRedaction(redactionLocalEchoId: String, eventToRedactId: String, roomId: String, reason: String?): Cancelable { - val task = queuedTaskFactory.createRedactTask(redactionLocalEchoId, eventToRedactId, roomId, reason) + override fun postRedaction( + redactionLocalEchoId: String, + eventToRedactId: String, + roomId: String, + reason: String?, + withRelations: List? + ): Cancelable { + val task = queuedTaskFactory.createRedactTask(redactionLocalEchoId, eventToRedactId, roomId, reason, withRelations) return postTask(task) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt index 0eedd4bd4d..a900e4ae5d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt @@ -19,9 +19,11 @@ package org.matrix.android.sdk.internal.session.room.send.queue import android.content.Context import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.crypto.CryptoService +import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository +import org.matrix.android.sdk.internal.session.room.send.model.EventRedactBody import timber.log.Timber import javax.inject.Inject @@ -107,10 +109,18 @@ internal class QueueMemento @Inject constructor( info.redactionLocalEcho?.let { localEchoRepository.getUpToDateEcho(it) }?.let { localEchoRepository.updateSendState(it.eventId!!, it.roomId, SendState.UNSENT) // try to get reason - val reason = it.content?.get("reason") as? String + val body = it.content.toModel() if (it.redacts != null && it.roomId != null) { Timber.d("## Send -Reschedule redact $info") - eventProcessor.postTask(queuedTaskFactory.createRedactTask(it.eventId, it.redacts, it.roomId, reason)) + eventProcessor.postTask( + queuedTaskFactory.createRedactTask( + redactionLocalEcho = it.eventId, + eventId = it.redacts, + roomId = it.roomId, + reason = body?.reason, + withRelations = body?.withRelations, + ) + ) } } // postTask(queuedTaskFactory.createRedactTask(info.eventToRedactId, info.) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTaskFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTaskFactory.kt index 90bb47c435..46df7e29f3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTaskFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTaskFactory.kt @@ -43,12 +43,13 @@ internal class QueuedTaskFactory @Inject constructor( ) } - fun createRedactTask(redactionLocalEcho: String, eventId: String, roomId: String, reason: String?): QueuedTask { + fun createRedactTask(redactionLocalEcho: String, eventId: String, roomId: String, reason: String?, withRelations: List? = null): QueuedTask { return RedactQueuedTask( redactionLocalEchoId = redactionLocalEcho, toRedactEventId = eventId, roomId = roomId, reason = reason, + withRelations = withRelations, redactEventTask = redactEventTask, localEchoRepository = localEchoRepository, cancelSendTracker = cancelSendTracker diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/RedactQueuedTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/RedactQueuedTask.kt index 0e3d88aa79..f484c24aae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/RedactQueuedTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/RedactQueuedTask.kt @@ -26,13 +26,14 @@ internal class RedactQueuedTask( val redactionLocalEchoId: String, private val roomId: String, private val reason: String?, + private val withRelations: List?, private val redactEventTask: RedactEventTask, private val localEchoRepository: LocalEchoRepository, private val cancelSendTracker: CancelSendTracker ) : QueuedTask(queueIdentifier = roomId, taskIdentifier = redactionLocalEchoId) { override suspend fun doExecute() { - redactEventTask.execute(RedactEventTask.Params(redactionLocalEchoId, roomId, toRedactEventId, reason)) + redactEventTask.execute(RedactEventTask.Params(redactionLocalEchoId, roomId, toRedactEventId, reason, withRelations)) } override fun onTaskFailed() { 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 3ce8ea658d..08ed59adc7 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 @@ -102,7 +102,6 @@ internal class DefaultTimeline( realm = backgroundRealm, eventDecryptor = eventDecryptor, paginationTask = paginationTask, - realmConfiguration = realmConfiguration, fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, fetchThreadTimelineTask = fetchThreadTimelineTask, getContextOfEventTask = getEventTask, 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 9faf301fe0..6654eeadfc 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 @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.room.timeline import io.realm.OrderedCollectionChangeSet import io.realm.OrderedRealmCollectionChangeListener import io.realm.Realm -import io.realm.RealmConfiguration import io.realm.RealmResults import io.realm.kotlin.createObject import io.realm.kotlin.executeTransactionAwait @@ -97,7 +96,6 @@ internal class LoadTimelineStrategy constructor( val realm: AtomicReference, val eventDecryptor: TimelineEventDecryptor, val paginationTask: PaginationTask, - val realmConfiguration: RealmConfiguration, val fetchThreadTimelineTask: FetchThreadTimelineTask, val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, val getContextOfEventTask: GetContextOfEventTask, @@ -351,7 +349,6 @@ internal class LoadTimelineStrategy constructor( fetchThreadTimelineTask = dependencies.fetchThreadTimelineTask, eventDecryptor = dependencies.eventDecryptor, paginationTask = dependencies.paginationTask, - realmConfiguration = dependencies.realmConfiguration, fetchTokenAndPaginateTask = dependencies.fetchTokenAndPaginateTask, timelineEventMapper = dependencies.timelineEventMapper, uiEchoManager = uiEchoManager, @@ -360,7 +357,6 @@ internal class LoadTimelineStrategy constructor( initialEventId = mode.originEventId(), onBuiltEvents = dependencies.onEventsUpdated, onEventsDeleted = dependencies.onEventsDeleted, - realm = dependencies.realm, localEchoEventFactory = dependencies.localEchoEventFactory, decorator = createTimelineEventDecorator() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt index 637267a9b1..7b5fa4fe01 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt @@ -42,12 +42,12 @@ internal class RealmSendingEventsDataSource( private var roomEntity: RoomEntity? = null private var sendingTimelineEvents: RealmList? = null - private var frozenSendingTimelineEvents: RealmList? = null + private var mappedSendingTimelineEvents: List = emptyList() private val sendingTimelineEventsListener = RealmChangeListener> { events -> if (events.isValid) { uiEchoManager.onSentEventsInDatabase(events.map { it.eventId }) - updateFrozenResults(events) + mapSendingEvents(events) onEventsUpdated(false) } } @@ -57,37 +57,29 @@ internal class RealmSendingEventsDataSource( roomEntity = RoomEntity.where(safeRealm, roomId = roomId).findFirst() sendingTimelineEvents = roomEntity?.sendingTimelineEvents sendingTimelineEvents?.addChangeListener(sendingTimelineEventsListener) - updateFrozenResults(sendingTimelineEvents) + mapSendingEvents(sendingTimelineEvents) } override fun stop() { sendingTimelineEvents?.removeChangeListener(sendingTimelineEventsListener) - updateFrozenResults(null) + mapSendingEvents(null) sendingTimelineEvents = null roomEntity = null } - private fun updateFrozenResults(sendingEvents: RealmList?) { - // Makes sure to close the previous frozen realm - if (frozenSendingTimelineEvents?.isValid == true) { - frozenSendingTimelineEvents?.realm?.close() - } - frozenSendingTimelineEvents = sendingEvents?.freeze() + private fun mapSendingEvents(sendingEvents: RealmList?) { + mappedSendingTimelineEvents = sendingEvents?.map { timelineEventMapper.map(it) }.orEmpty() } override fun buildSendingEvents(): List { val builtSendingEvents = mutableListOf() uiEchoManager.getInMemorySendingEvents() .addWithUiEcho(builtSendingEvents) - if (frozenSendingTimelineEvents?.isValid == true) { - frozenSendingTimelineEvents - ?.filter { timelineEvent -> - builtSendingEvents.none { it.eventId == timelineEvent.eventId } - } - ?.map { - timelineEventMapper.map(it) - }?.addWithUiEcho(builtSendingEvents) - } + mappedSendingTimelineEvents + .filter { timelineEvent -> + builtSendingEvents.none { it.eventId == timelineEvent.eventId } + } + .addWithUiEcho(builtSendingEvents) return builtSendingEvents } 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 c9785e7ea1..d04b98ef76 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 @@ -18,8 +18,6 @@ package org.matrix.android.sdk.internal.session.room.timeline import io.realm.OrderedCollectionChangeSet import io.realm.OrderedRealmCollectionChangeListener -import io.realm.Realm -import io.realm.RealmConfiguration import io.realm.RealmObjectChangeListener import io.realm.RealmQuery import io.realm.RealmResults @@ -48,7 +46,6 @@ import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenes import timber.log.Timber import java.util.Collections import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicReference /** * This is a wrapper around a ChunkEntity in the database. @@ -63,7 +60,6 @@ internal class TimelineChunk( private val fetchThreadTimelineTask: FetchThreadTimelineTask, private val eventDecryptor: TimelineEventDecryptor, private val paginationTask: PaginationTask, - private val realmConfiguration: RealmConfiguration, private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, private val timelineEventMapper: TimelineEventMapper, private val uiEchoManager: UIEchoManager?, @@ -72,7 +68,6 @@ internal class TimelineChunk( private val initialEventId: String?, private val onBuiltEvents: (Boolean) -> Unit, private val onEventsDeleted: () -> Unit, - private val realm: AtomicReference, private val decorator: TimelineEventDecorator, val localEchoEventFactory: LocalEchoEventFactory, ) { @@ -605,7 +600,6 @@ internal class TimelineChunk( timelineId = timelineId, eventDecryptor = eventDecryptor, paginationTask = paginationTask, - realmConfiguration = realmConfiguration, fetchThreadTimelineTask = fetchThreadTimelineTask, fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, timelineEventMapper = timelineEventMapper, @@ -616,7 +610,6 @@ internal class TimelineChunk( onBuiltEvents = this.onBuiltEvents, onEventsDeleted = this.onEventsDeleted, decorator = this.decorator, - realm = realm, localEchoEventFactory = localEchoEventFactory ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/decorator/UiEchoDecorator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/decorator/UiEchoDecorator.kt index 778a9d27d9..8a347ed35b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/decorator/UiEchoDecorator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/decorator/UiEchoDecorator.kt @@ -19,9 +19,9 @@ package org.matrix.android.sdk.internal.session.room.timeline.decorator import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.internal.session.room.timeline.UIEchoManager -internal class UiEchoDecorator(private val uiEchoManager: UIEchoManager?) : TimelineEventDecorator { +internal class UiEchoDecorator(private val uiEchoManager: UIEchoManager) : TimelineEventDecorator { override fun decorate(timelineEvent: TimelineEvent): TimelineEvent { - return uiEchoManager?.decorateEventWithReactionUiEcho(timelineEvent) ?: timelineEvent + return uiEchoManager.decorateEventWithReactionUiEcho(timelineEvent) } } 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 cb407bb1cb..a9de4d3a3b 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 @@ -19,8 +19,9 @@ package org.matrix.android.sdk.internal.session.sync import com.zhuinden.monarchy.Monarchy import io.realm.Realm import org.matrix.android.sdk.api.MatrixConfiguration -import org.matrix.android.sdk.api.extensions.measureMetric import org.matrix.android.sdk.api.extensions.measureSpan +import org.matrix.android.sdk.api.extensions.measureSpannableMetric +import org.matrix.android.sdk.api.metrics.SpannableMetricPlugin import org.matrix.android.sdk.api.metrics.SyncDurationMetricPlugin import org.matrix.android.sdk.api.session.pushrules.PushRuleService import org.matrix.android.sdk.api.session.pushrules.RuleScope @@ -67,12 +68,13 @@ internal class SyncResponseHandler @Inject constructor( suspend fun handleResponse( syncResponse: SyncResponse, fromToken: String?, + afterPause: Boolean, reporter: ProgressReporter? ) { val isInitialSync = fromToken == null Timber.v("Start handling sync, is InitialSync: $isInitialSync") - relevantPlugins.measureMetric { + relevantPlugins.filter { it.shouldReport(isInitialSync, afterPause) }.measureSpannableMetric { startCryptoService(isInitialSync) // Handle the to device events before the room ones @@ -101,8 +103,8 @@ internal class SyncResponseHandler @Inject constructor( } } - private fun startCryptoService(isInitialSync: Boolean) { - relevantPlugins.measureSpan("task", "start_crypto_service") { + private fun List.startCryptoService(isInitialSync: Boolean) { + measureSpan("task", "start_crypto_service") { measureTimeMillis { if (!cryptoService.isStarted()) { Timber.v("Should start cryptoService") @@ -115,8 +117,8 @@ internal class SyncResponseHandler @Inject constructor( } } - private suspend fun handleToDevice(syncResponse: SyncResponse, reporter: ProgressReporter?) { - relevantPlugins.measureSpan("task", "handle_to_device") { + private suspend fun List.handleToDevice(syncResponse: SyncResponse, reporter: ProgressReporter?) { + measureSpan("task", "handle_to_device") { measureTimeMillis { Timber.v("Handle toDevice") reportSubtask(reporter, InitialSyncStep.ImportingAccountCrypto, 100, 0.1f) { @@ -130,14 +132,14 @@ internal class SyncResponseHandler @Inject constructor( } } - private suspend fun startMonarchyTransaction( + private suspend fun List.startMonarchyTransaction( syncResponse: SyncResponse, isInitialSync: Boolean, reporter: ProgressReporter?, aggregator: SyncResponsePostTreatmentAggregator ) { // Start one big transaction - relevantPlugins.measureSpan("task", "monarchy_transaction") { + measureSpan("task", "monarchy_transaction") { monarchy.awaitTransaction { realm -> // IMPORTANT nothing should be suspend here as we are accessing the realm instance (thread local) handleRooms(reporter, syncResponse, realm, isInitialSync, aggregator) @@ -149,14 +151,14 @@ internal class SyncResponseHandler @Inject constructor( } } - private fun handleRooms( + private fun List.handleRooms( reporter: ProgressReporter?, syncResponse: SyncResponse, realm: Realm, isInitialSync: Boolean, aggregator: SyncResponsePostTreatmentAggregator ) { - relevantPlugins.measureSpan("task", "handle_rooms") { + measureSpan("task", "handle_rooms") { measureTimeMillis { Timber.v("Handle rooms") reportSubtask(reporter, InitialSyncStep.ImportingAccountRoom, 1, 0.8f) { @@ -170,8 +172,8 @@ internal class SyncResponseHandler @Inject constructor( } } - private fun handleAccountData(reporter: ProgressReporter?, realm: Realm, syncResponse: SyncResponse) { - relevantPlugins.measureSpan("task", "handle_account_data") { + private fun List.handleAccountData(reporter: ProgressReporter?, realm: Realm, syncResponse: SyncResponse) { + measureSpan("task", "handle_account_data") { measureTimeMillis { reportSubtask(reporter, InitialSyncStep.ImportingAccountData, 1, 0.1f) { Timber.v("Handle accountData") @@ -183,8 +185,8 @@ internal class SyncResponseHandler @Inject constructor( } } - private fun handlePresence(realm: Realm, syncResponse: SyncResponse) { - relevantPlugins.measureSpan("task", "handle_presence") { + private fun List.handlePresence(realm: Realm, syncResponse: SyncResponse) { + measureSpan("task", "handle_presence") { measureTimeMillis { Timber.v("Handle Presence") presenceSyncHandler.handle(realm, syncResponse.presence) @@ -194,8 +196,8 @@ internal class SyncResponseHandler @Inject constructor( } } - private suspend fun aggregateSyncResponse(aggregator: SyncResponsePostTreatmentAggregator) { - relevantPlugins.measureSpan("task", "aggregator_management") { + private suspend fun List.aggregateSyncResponse(aggregator: SyncResponsePostTreatmentAggregator) { + measureSpan("task", "aggregator_management") { // Everything else we need to do outside the transaction measureTimeMillis { aggregatorHandler.handle(aggregator) @@ -205,8 +207,8 @@ internal class SyncResponseHandler @Inject constructor( } } - private suspend fun postTreatmentSyncResponse(syncResponse: SyncResponse, isInitialSync: Boolean) { - relevantPlugins.measureSpan("task", "sync_response_post_treatment") { + private suspend fun List.postTreatmentSyncResponse(syncResponse: SyncResponse, isInitialSync: Boolean) { + measureSpan("task", "sync_response_post_treatment") { measureTimeMillis { syncResponse.rooms?.let { checkPushRules(it, isInitialSync) @@ -219,8 +221,8 @@ internal class SyncResponseHandler @Inject constructor( } } - private fun markCryptoSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) { - relevantPlugins.measureSpan("task", "crypto_sync_handler_onSyncCompleted") { + private fun List.markCryptoSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) { + measureSpan("task", "crypto_sync_handler_onSyncCompleted") { measureTimeMillis { cryptoSyncHandler.onSyncCompleted(syncResponse, cryptoStoreAggregator) }.also { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt index 8a287fb0b4..86346cabcf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt @@ -151,7 +151,7 @@ internal class DefaultSyncTask @Inject constructor( syncStatisticsData.requestInitSyncTime = SystemClock.elapsedRealtime() syncStatisticsData.downloadInitSyncTime = syncStatisticsData.requestInitSyncTime logDuration("INIT_SYNC Database insertion", loggerTag, clock) { - syncResponseHandler.handleResponse(syncResponse, token, syncRequestStateTracker) + syncResponseHandler.handleResponse(syncResponse, null, afterPause = true, syncRequestStateTracker) } syncResponseToReturn = syncResponse } @@ -184,7 +184,7 @@ internal class DefaultSyncTask @Inject constructor( toDevice = nbToDevice, ) ) - syncResponseHandler.handleResponse(syncResponse, token, null) + syncResponseHandler.handleResponse(syncResponse, token, afterPause = params.afterPause, null) syncResponseToReturn = syncResponse Timber.tag(loggerTag.value).d("Incremental sync done") syncRequestStateTracker.setSyncRequestState(SyncRequestState.IncrementalSyncDone) @@ -264,7 +264,7 @@ internal class DefaultSyncTask @Inject constructor( Timber.tag(loggerTag.value).d("INIT_SYNC $nbOfJoinedRooms rooms, $nbOfJoinedRoomsInFile ephemeral stored into files") logDuration("INIT_SYNC Database insertion", loggerTag, clock) { - syncResponseHandler.handleResponse(syncResponse, null, syncRequestStateTracker) + syncResponseHandler.handleResponse(syncResponse, null, afterPause = true, syncRequestStateTracker) } initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS) syncResponse diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt index c749f77fff..85bc8b0f97 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt @@ -105,7 +105,8 @@ internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor( .enqueue() } - private fun handleUserIdsForCheckingTrustAndAffectedRoomShields(userIdsWithDeviceUpdate: Iterable) { + private fun handleUserIdsForCheckingTrustAndAffectedRoomShields(userIdsWithDeviceUpdate: Collection) { + if (userIdsWithDeviceUpdate.isEmpty()) return crossSigningService.checkTrustAndAffectedRoomShields(userIdsWithDeviceUpdate.toList()) } } diff --git a/tools/release/pushPlayStoreMetaData.sh b/tools/release/pushPlayStoreMetaData.sh index 2d8fd9b36a..cc24786441 100755 --- a/tools/release/pushPlayStoreMetaData.sh +++ b/tools/release/pushPlayStoreMetaData.sh @@ -77,6 +77,15 @@ else removeFullDes_th=1 fi +if [[ -f "./fastlane/metadata/android/az-AZ/full_description.txt" ]]; then + echo "It appears that file ./fastlane/metadata/android/az-AZ/full_description.txt now exists. This can be removed." + removeFullDes_az=0 +else + echo "Copy default full description to ./fastlane/metadata/android/az-AZ" + cp ./fastlane/metadata/android/en-US/full_description.txt ./fastlane/metadata/android/az-AZ + removeFullDes_az=1 +fi + # Run fastlane echo "Run fastlane to push to the PlaysStore" fastlane deployMeta @@ -107,4 +116,8 @@ if [[ ${removeFullDes_th} -eq 1 ]]; then rm ./fastlane/metadata/android/th/full_description.txt fi +if [[ ${removeFullDes_az} -eq 1 ]]; then + rm ./fastlane/metadata/android/az-AZ/full_description.txt +fi + echo "Success!" diff --git a/tools/release/releaseScript.sh b/tools/release/releaseScript.sh index f9f5303546..cf9671c1dc 100755 --- a/tools/release/releaseScript.sh +++ b/tools/release/releaseScript.sh @@ -167,7 +167,7 @@ printf "Building the app...\n" ./gradlew assembleGplayDebug printf "\n================================================================================\n" -printf "Uninstalling previous test app if any...\n" +printf "Uninstalling previous debug app if any...\n" adb -e uninstall im.vector.app.debug printf "\n================================================================================\n" diff --git a/vector-app/build.gradle b/vector-app/build.gradle index 824f651b4d..b2c2e79bcb 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -37,7 +37,7 @@ ext.versionMinor = 5 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -ext.versionPatch = 22 +ext.versionPatch = 24 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' diff --git a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt index c1e201cfc4..0966227917 100644 --- a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt @@ -151,6 +151,7 @@ class DefaultErrorFormatter @Inject constructor( return when (throwable) { is VoiceFailure.UnableToPlay -> stringProvider.getString(R.string.error_voice_message_unable_to_play) is VoiceFailure.UnableToRecord -> stringProvider.getString(R.string.error_voice_message_unable_to_record) + is VoiceFailure.VoiceBroadcastInProgress -> stringProvider.getString(R.string.error_voice_message_broadcast_in_progress) } } @@ -159,8 +160,7 @@ class DefaultErrorFormatter @Inject constructor( RecordingError.BlockedBySomeoneElse -> stringProvider.getString(R.string.error_voice_broadcast_blocked_by_someone_else_message) RecordingError.NoPermission -> stringProvider.getString(R.string.error_voice_broadcast_permission_denied_message) RecordingError.UserAlreadyBroadcasting -> stringProvider.getString(R.string.error_voice_broadcast_already_in_progress_message) - is VoiceBroadcastFailure.ListeningError.UnableToPlay, - is VoiceBroadcastFailure.ListeningError.DownloadError -> stringProvider.getString(R.string.error_voice_broadcast_unable_to_play) + is VoiceBroadcastFailure.ListeningError -> stringProvider.getString(R.string.error_voice_broadcast_unable_to_play) } } diff --git a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt index 89bd28fc93..49dd74d16f 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt @@ -18,6 +18,9 @@ package im.vector.app.core.extensions import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.model.isVoiceBroadcast import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageContent @@ -26,8 +29,9 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent fun TimelineEvent.canReact(): Boolean { - // Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment - return root.getClearType() in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START.values + EventType.POLL_END.values && + // Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START, and started voice broadcast are supported for the moment + return (root.getClearType() in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START.values + EventType.POLL_END.values || + root.asVoiceBroadcastEvent()?.content?.voiceBroadcastState == VoiceBroadcastState.STARTED) && root.sendState == SendState.SYNCED && !root.isRedacted() } @@ -46,3 +50,7 @@ fun TimelineEvent.getVectorLastMessageContent(): MessageContent? { else -> getLastMessageContent() } } + +fun TimelineEvent.isVoiceBroadcast(): Boolean { + return root.isVoiceBroadcast() +} diff --git a/vector/src/main/java/im/vector/app/features/analytics/metrics/sentry/SentrySyncDurationMetrics.kt b/vector/src/main/java/im/vector/app/features/analytics/metrics/sentry/SentrySyncDurationMetrics.kt index d69ed01526..04f4d38769 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/metrics/sentry/SentrySyncDurationMetrics.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/metrics/sentry/SentrySyncDurationMetrics.kt @@ -34,6 +34,11 @@ class SentrySyncDurationMetrics @Inject constructor() : SyncDurationMetricPlugin // Stacks to keep spans in LIFO order. private var spans: Stack = Stack() + override fun shouldReport(isInitialSync: Boolean, isAfterPause: Boolean): Boolean { + // Report only for initial sync and for sync after pause + return isInitialSync || isAfterPause + } + /** * Starts the span for a sub-task. * @@ -69,6 +74,7 @@ class SentrySyncDurationMetrics @Inject constructor() : SyncDurationMetricPlugin override fun finishTransaction() { transaction?.finish() + transaction = null logTransaction("Sentry transaction finished") } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 980e8ebac5..7b94508b37 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -797,7 +797,7 @@ class TimelineFragment : } // We use a custom layout for this menu item, so we need to set a ClickListener menu.findItem(R.id.open_matrix_apps)?.let { menuItem -> - menuItem.actionView?.debouncedClicks { + menuItem.actionView?.setOnClickListener { handleMenuItemSelected(menuItem) } } @@ -808,7 +808,7 @@ class TimelineFragment : // Custom thread notification menu item menu.findItem(R.id.menu_timeline_thread_list)?.let { menuItem -> - menuItem.actionView?.debouncedClicks { + menuItem.actionView?.setOnClickListener { handleMenuItemSelected(menuItem) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index ff24872ab8..72d9fc8a16 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -32,6 +32,7 @@ import im.vector.app.R import im.vector.app.SpaceStateHandler import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.extensions.isVoiceBroadcast import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.BuildMeta @@ -626,13 +627,17 @@ class TimelineViewModel @AssistedInject constructor( viewModelScope.launch { when (action) { VoiceBroadcastAction.Recording.Start -> { + voiceBroadcastHelper.pausePlayback() voiceBroadcastHelper.startVoiceBroadcast(room.roomId).fold( { _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action)) }, { _viewEvents.post(RoomDetailViewEvents.ActionFailure(action, it)) }, ) } VoiceBroadcastAction.Recording.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId) - VoiceBroadcastAction.Recording.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId) + VoiceBroadcastAction.Recording.Resume -> { + voiceBroadcastHelper.pausePlayback() + voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId) + } VoiceBroadcastAction.Recording.Stop -> _viewEvents.post(RoomDetailViewEvents.DisplayPromptToStopVoiceBroadcast) VoiceBroadcastAction.Recording.StopConfirmed -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId) is VoiceBroadcastAction.Listening.PlayOrResume -> voiceBroadcastHelper.playOrResumePlayback(action.voiceBroadcast) @@ -855,12 +860,18 @@ class TimelineViewModel @AssistedInject constructor( private fun handleRedactEvent(action: RoomDetailAction.RedactAction) { val event = room?.getTimelineEvent(action.targetEventId) ?: return - if (event.isLiveLocation()) { - viewModelScope.launch { - redactLiveLocationShareEventUseCase.execute(event.root, room, action.reason) + when { + event.isLiveLocation() -> { + viewModelScope.launch { + redactLiveLocationShareEventUseCase.execute(event.root, room, action.reason) + } + } + event.isVoiceBroadcast() -> { + room.sendService().redactEvent(event.root, action.reason, listOf(RelationType.REFERENCE)) + } + else -> { + room.sendService().redactEvent(event.root, action.reason) } - } else { - room.sendService().redactEvent(event.root, action.reason) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 4849e20b6d..28c8757e6c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -191,6 +191,8 @@ class MessageComposerFragment : VectorBaseFragment(), A is MessageComposerViewEvents.VoicePlaybackOrRecordingFailure -> { if (it.throwable is VoiceFailure.UnableToRecord) { onCannotRecord() + } else if (it.throwable is VoiceFailure.VoiceBroadcastInProgress) { + displayErrorVoiceBroadcastInProgress() } showErrorInSnackbar(it.throwable) } @@ -526,6 +528,14 @@ class MessageComposerFragment : VectorBaseFragment(), A messageComposerViewModel.handle(MessageComposerAction.OnVoiceRecordingUiStateChanged(VoiceMessageRecorderView.RecordingUiState.Idle)) } + private fun displayErrorVoiceBroadcastInProgress() { + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.error_voice_message_broadcast_in_progress) + .setMessage(getString(R.string.error_voice_message_broadcast_in_progress_message)) + .setPositiveButton(android.R.string.ok, null) + .show() + } + private fun handleJoinedToAnotherRoom(action: MessageComposerViewEvents.JoinRoomCommandSuccess) { composer.setTextIfDifferent("") lockSendButton = false diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index 56ee9ffb5a..fc79c069fe 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.composer import android.text.SpannableString import androidx.lifecycle.asFlow import com.airbnb.mvrx.MavericksViewModelFactory +import com.airbnb.mvrx.withState import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -28,6 +29,7 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.extensions.getVectorLastMessageContent import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.core.time.Clock import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.extensions.toAnalyticsComposer import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom @@ -42,12 +44,19 @@ import im.vector.app.features.home.room.detail.toMessageType import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.voice.VoiceFailure import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper +import im.vector.app.features.voicebroadcast.model.VoiceBroadcast +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase +import im.vector.app.features.voicebroadcast.voiceBroadcastId import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session @@ -74,6 +83,7 @@ import org.matrix.android.sdk.api.session.room.send.UserDraft import org.matrix.android.sdk.api.session.room.timeline.getRelationContent import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent import org.matrix.android.sdk.api.session.space.CreateSpaceParams +import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap import timber.log.Timber @@ -88,6 +98,8 @@ class MessageComposerViewModel @AssistedInject constructor( private val audioMessageHelper: AudioMessageHelper, private val analyticsTracker: AnalyticsTracker, private val voiceBroadcastHelper: VoiceBroadcastHelper, + private val clock: Clock, + private val getVoiceBroadcastStateEventLiveUseCase: GetVoiceBroadcastStateEventLiveUseCase, ) : VectorViewModel(initialState) { private val room = session.getRoom(initialState.roomId) @@ -203,8 +215,11 @@ class MessageComposerViewModel @AssistedInject constructor( private fun observeVoiceBroadcast(room: Room) { room.stateService().getStateEventLive(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId)) .asFlow() - .unwrap() - .mapNotNull { it.asVoiceBroadcastEvent()?.content?.voiceBroadcastState } + .map { it.getOrNull()?.asVoiceBroadcastEvent()?.voiceBroadcastId } + .flatMapLatest { voiceBroadcastId -> + voiceBroadcastId?.let { getVoiceBroadcastStateEventLiveUseCase.execute(VoiceBroadcast(it, room.roomId)) } ?: flowOf(Optional.empty()) + } + .map { it.getOrNull()?.content?.voiceBroadcastState } .setOnEach { copy(voiceBroadcastState = it) } @@ -916,10 +931,16 @@ class MessageComposerViewModel @AssistedInject constructor( } private fun handleStartRecordingVoiceMessage(room: Room) { - try { - audioMessageHelper.startRecording(room.roomId) - } catch (failure: Throwable) { - _viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(failure)) + val voiceBroadcastState = withState(this) { it.voiceBroadcastState } + if (voiceBroadcastState != null && voiceBroadcastState != VoiceBroadcastState.STOPPED) { + _viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(VoiceFailure.VoiceBroadcastInProgress)) + } else { + try { + audioMessageHelper.startRecording(room.roomId) + setState { copy(voiceRecordingUiState = VoiceMessageRecorderView.RecordingUiState.Recording(clock.epochMillis())) } + } catch (failure: Throwable) { + _viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(failure)) + } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceRecorderFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceRecorderFragment.kt index 25764f3654..90b813d347 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceRecorderFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceRecorderFragment.kt @@ -125,7 +125,6 @@ class VoiceRecorderFragment : VectorBaseFragment() if (checkPermissions(PERMISSIONS_FOR_VOICE_MESSAGE, requireActivity(), permissionVoiceMessageLauncher)) { messageComposerViewModel.handle(MessageComposerAction.StartRecordingVoiceMessage) vibrate(requireContext()) - updateRecordingUiState(VoiceMessageRecorderView.RecordingUiState.Recording(clock.epochMillis())) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt index 39d2d73c68..abf14c0867 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt @@ -17,6 +17,8 @@ package im.vector.app.features.home.room.detail.timeline.item import android.widget.ImageButton +import android.widget.TextView +import androidx.constraintlayout.widget.Group import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R @@ -55,11 +57,11 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem } override fun renderLiveIndicator(holder: Holder) { - when (voiceBroadcastState) { - VoiceBroadcastState.STARTED, - VoiceBroadcastState.RESUMED -> renderPlayingLiveIndicator(holder) - VoiceBroadcastState.PAUSED -> renderPausedLiveIndicator(holder) - VoiceBroadcastState.STOPPED, null -> renderNoLiveIndicator(holder) + when (recorder?.recordingState) { + VoiceBroadcastRecorder.State.Recording -> renderPlayingLiveIndicator(holder) + VoiceBroadcastRecorder.State.Error, + VoiceBroadcastRecorder.State.Paused -> renderPausedLiveIndicator(holder) + VoiceBroadcastRecorder.State.Idle, null -> renderNoLiveIndicator(holder) } } @@ -85,7 +87,9 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem VoiceBroadcastRecorder.State.Recording -> renderRecordingState(holder) VoiceBroadcastRecorder.State.Paused -> renderPausedState(holder) VoiceBroadcastRecorder.State.Idle -> renderStoppedState(holder) + VoiceBroadcastRecorder.State.Error -> renderErrorState(holder, true) } + renderLiveIndicator(holder) } private fun renderVoiceBroadcastState(holder: Holder) { @@ -101,6 +105,7 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem private fun renderRecordingState(holder: Holder) = with(holder) { stopRecordButton.isEnabled = true recordButton.isEnabled = true + renderErrorState(holder, false) val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) val drawable = drawableProvider.getDrawable(R.drawable.ic_play_pause_pause, drawableColor) @@ -113,6 +118,7 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem private fun renderPausedState(holder: Holder) = with(holder) { stopRecordButton.isEnabled = true recordButton.isEnabled = true + renderErrorState(holder, false) recordButton.setImageResource(R.drawable.ic_recording_dot) recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_resume_voice_broadcast_record) @@ -123,6 +129,12 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem private fun renderStoppedState(holder: Holder) = with(holder) { recordButton.isEnabled = false stopRecordButton.isEnabled = false + renderErrorState(holder, false) + } + + private fun renderErrorState(holder: Holder, isOnError: Boolean) = with(holder) { + controlsGroup.isVisible = !isOnError + errorView.isVisible = isOnError } override fun unbind(holder: Holder) { @@ -142,6 +154,8 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem val remainingTimeMetadata by bind(R.id.remainingTimeMetadata) val recordButton by bind(R.id.recordButton) val stopRecordButton by bind(R.id.stopRecordButton) + val errorView by bind(R.id.errorView) + val controlsGroup by bind(R.id.controlsGroup) } companion object { diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceFailure.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceFailure.kt index 9c4b345dc4..0a837581fd 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceFailure.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceFailure.kt @@ -19,4 +19,5 @@ package im.vector.app.features.voice sealed class VoiceFailure(cause: Throwable? = null) : Throwable(cause = cause) { data class UnableToPlay(val throwable: Throwable) : VoiceFailure(throwable) data class UnableToRecord(val throwable: Throwable) : VoiceFailure(throwable) + object VoiceBroadcastInProgress : VoiceFailure() } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastFailure.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastFailure.kt index 75863dc042..1f9529a966 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastFailure.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastFailure.kt @@ -31,6 +31,6 @@ sealed class VoiceBroadcastFailure : Throwable() { * @property extra an extra code, specific to the error, see [MediaPlayer.OnErrorListener.onError]. */ data class UnableToPlay(val what: Int, val extra: Int) : ListeningError() - data class DownloadError(override val cause: Throwable?) : ListeningError() + data class PrepareMediaPlayerError(override val cause: Throwable? = null) : ListeningError() } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index 2e1600e4e2..2559f1a7d6 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -18,7 +18,6 @@ package im.vector.app.features.voicebroadcast.listening import android.media.AudioAttributes import android.media.MediaPlayer -import android.media.MediaPlayer.OnPreparedListener import androidx.annotation.MainThread import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.onFirst @@ -33,10 +32,13 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcast import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase import im.vector.lib.core.utils.timer.CountUpTimer +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Job import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import timber.log.Timber @@ -63,8 +65,29 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private var voiceBroadcastStateObserver: Job? = null private var currentMediaPlayer: MediaPlayer? = null + private set(value) { + Timber.d("## Voice Broadcast | currentMediaPlayer changed: old=${field.hashCode()}, new=${value.hashCode()}") + field = value + } private var nextMediaPlayer: MediaPlayer? = null - private var isPreparingNextPlayer: Boolean = false + private set(value) { + Timber.d("## Voice Broadcast | nextMediaPlayer changed: old=${field.hashCode()}, new=${value.hashCode()}") + field = value + } + + private var prepareCurrentPlayerJob: Job? = null + set(value) { + if (field?.isActive.orFalse()) field?.cancel() + field = value + } + private var prepareNextPlayerJob: Job? = null + set(value) { + if (field?.isActive.orFalse()) field?.cancel() + field = value + } + + private val isPreparingCurrentPlayer: Boolean get() = prepareCurrentPlayerJob?.isActive.orFalse() + private val isPreparingNextPlayer: Boolean get() = prepareNextPlayerJob?.isActive.orFalse() private var mostRecentVoiceBroadcastEvent: VoiceBroadcastEvent? = null @@ -83,7 +106,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( @MainThread set(value) { if (field != value) { - Timber.d("## Voice Broadcast | playingState: $field -> $value") + Timber.d("## Voice Broadcast | playingState: ${field::class.java.simpleName} -> ${value::class.java.simpleName}") field = value onPlayingStateChanged(value) } @@ -174,23 +197,25 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } private fun onPlaylistUpdated() { + if (isPreparingCurrentPlayer || isPreparingNextPlayer) return when (playingState) { State.Playing, State.Paused -> { - if (nextMediaPlayer == null && !isPreparingNextPlayer) { + if (nextMediaPlayer == null) { prepareNextMediaPlayer() } } State.Buffering -> { - val nextItem = if (isLiveListening && playlist.currentSequence == null) { - // live listening, jump to the last item if playback has not started - playlist.lastOrNull() - } else { - // not live or playback already started, request next item - playlist.getNextItem() - } - if (nextItem != null) { - startPlayback(nextItem.startTime) + val savedPosition = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPlaybackTime(it) } + when { + // resume playback from the next sequence item + playlist.currentSequence != null -> playlist.getNextItem()?.let { startPlayback(it.startTime) } + // resume playback from the saved position, if any + savedPosition != null -> startPlayback(savedPosition) + // live listening, jump to the last item + isLiveListening -> playlist.lastOrNull()?.let { startPlayback(it.startTime) } + // start playback from the beginning + else -> startPlayback(0) } } is State.Error -> Unit @@ -205,19 +230,16 @@ class VoiceBroadcastPlayerImpl @Inject constructor( val content = playlistItem?.audioEvent?.content ?: run { Timber.w("## Voice Broadcast | No content to play at position $position"); return } val sequence = playlistItem.sequence ?: run { Timber.w("## Voice Broadcast | Playlist item has no sequence"); return } val sequencePosition = position - playlistItem.startTime - sessionScope.launch { + prepareCurrentPlayerJob = sessionScope.launch { try { - prepareMediaPlayer(content) { mp -> - currentMediaPlayer = mp - playlist.currentSequence = sequence - mp.start() - if (sequencePosition > 0) { - mp.seekTo(sequencePosition) - } - playingState = State.Playing - prepareNextMediaPlayer() + val mp = prepareMediaPlayer(content) + playlist.currentSequence = sequence - 1 // will be incremented in onNextMediaPlayerStarted + mp.start() + if (sequencePosition > 0) { + mp.seekTo(sequencePosition) } - } catch (failure: VoiceBroadcastFailure.ListeningError.DownloadError) { + onNextMediaPlayerStarted(mp) + } catch (failure: VoiceBroadcastFailure.ListeningError) { playingState = State.Error(failure) } } @@ -248,7 +270,6 @@ class VoiceBroadcastPlayerImpl @Inject constructor( playbackTracker.updatePausedAtPlaybackTime(voiceBroadcast.voiceBroadcastId, positionMillis, positionMillis.toFloat() / duration) } playingState == State.Playing || playingState == State.Buffering -> { - updateLiveListeningMode(positionMillis) startPlayback(positionMillis) } playingState == State.Idle || playingState == State.Paused -> { @@ -260,28 +281,24 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun prepareNextMediaPlayer() { val nextItem = playlist.getNextItem() - if (nextItem != null) { - isPreparingNextPlayer = true - sessionScope.launch { + if (!isPreparingNextPlayer && nextMediaPlayer == null && nextItem != null) { + prepareNextPlayerJob = sessionScope.launch { try { - prepareMediaPlayer(nextItem.audioEvent.content) { mp -> - isPreparingNextPlayer = false - nextMediaPlayer = mp - when (playingState) { - State.Playing, - State.Paused -> { - currentMediaPlayer?.setNextMediaPlayer(mp) - } - State.Buffering -> { - mp.start() - onNextMediaPlayerStarted(mp) - } - is State.Error, - State.Idle -> stopPlayer() + val mp = prepareMediaPlayer(nextItem.audioEvent.content) + nextMediaPlayer = mp + when (playingState) { + State.Playing, + State.Paused -> { + currentMediaPlayer?.setNextMediaPlayer(mp) } + State.Buffering -> { + mp.start() + onNextMediaPlayerStarted(mp) + } + is State.Error, + State.Idle -> stopPlayer() } - } catch (failure: VoiceBroadcastFailure.ListeningError.DownloadError) { - isPreparingNextPlayer = false + } catch (failure: VoiceBroadcastFailure.ListeningError) { // Do not change the playingState if the current player is still valid, // the error will be thrown again when switching to the next player if (playingState == State.Buffering || tryOrNull { currentMediaPlayer?.isPlaying } != true) { @@ -292,18 +309,30 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } } - private suspend fun prepareMediaPlayer(messageAudioContent: MessageAudioContent, onPreparedListener: OnPreparedListener): MediaPlayer { + /** + * Create and prepare a [MediaPlayer] instance for the given [messageAudioContent]. + * This methods takes care of downloading the audio file and returns the player when it is ready to use. + * + * Do not forget to release the resulting player when you don't need it anymore, in case you cancel the job related to this method, the player will be + * automatically released. + */ + private suspend fun prepareMediaPlayer(messageAudioContent: MessageAudioContent): MediaPlayer { // Download can fail val audioFile = try { session.fileService().downloadFile(messageAudioContent) } catch (failure: Throwable) { Timber.e(failure, "Voice Broadcast | Download has failed: $failure") - throw VoiceBroadcastFailure.ListeningError.DownloadError(failure) + throw VoiceBroadcastFailure.ListeningError.PrepareMediaPlayerError(failure) } - return audioFile.inputStream().use { fis -> - MediaPlayer().apply { - setOnErrorListener(mediaPlayerListener) + val latch = CompletableDeferred() + val mp = MediaPlayer() + return try { + mp.apply { + setOnErrorListener { mp, what, extra -> + mediaPlayerListener.onError(mp, what, extra) + latch.completeExceptionally(VoiceBroadcastFailure.ListeningError.PrepareMediaPlayerError()) + } setAudioAttributes( AudioAttributes.Builder() // Do not use CONTENT_TYPE_SPEECH / USAGE_VOICE_COMMUNICATION because we want to play loud here @@ -311,12 +340,16 @@ class VoiceBroadcastPlayerImpl @Inject constructor( .setUsage(AudioAttributes.USAGE_MEDIA) .build() ) - setDataSource(fis.fd) + audioFile.inputStream().use { fis -> setDataSource(fis.fd) } setOnInfoListener(mediaPlayerListener) - setOnPreparedListener(onPreparedListener) + setOnPreparedListener(latch::complete) setOnCompletionListener(mediaPlayerListener) prepareAsync() } + latch.await() + } catch (e: CancellationException) { + mp.release() + throw e } } @@ -327,7 +360,9 @@ class VoiceBroadcastPlayerImpl @Inject constructor( nextMediaPlayer?.release() nextMediaPlayer = null - isPreparingNextPlayer = false + + prepareCurrentPlayerJob = null + prepareNextPlayerJob = null } private fun onPlayingStateChanged(playingState: State) { @@ -357,36 +392,12 @@ class VoiceBroadcastPlayerImpl @Inject constructor( /** * Update the live listening state according to: * - the voice broadcast state (started/paused/resumed/stopped), - * - the playing state (IDLE, PLAYING, PAUSED, BUFFERING), - * - the potential seek position (backward/forward). + * - the playing state (IDLE, PLAYING, PAUSED, BUFFERING). */ - private fun updateLiveListeningMode(seekPosition: Int? = null) { - isLiveListening = when { - // the current voice broadcast is not live (ended) - mostRecentVoiceBroadcastEvent?.isLive != true -> false - // the player is stopped or paused - playingState == State.Idle || playingState == State.Paused -> false - seekPosition != null -> { - val seekDirection = seekPosition.compareTo(getCurrentPlaybackPosition() ?: 0) - val newSequence = playlist.findByPosition(seekPosition)?.sequence - // the user has sought forward - if (seekDirection >= 0) { - // stay in live or latest sequence reached - isLiveListening || newSequence == playlist.lastOrNull()?.sequence - } - // the user has sought backward - else { - // was in live and stay in the same sequence - isLiveListening && newSequence == playlist.currentSequence - } - } - // if there is no saved position, go in live - getCurrentPlaybackPosition() == null -> true - // if we reached the latest sequence, go in live - playlist.currentSequence == playlist.lastOrNull()?.sequence -> true - // otherwise, do not change - else -> isLiveListening - } + private fun updateLiveListeningMode() { + val isLiveVoiceBroadcast = mostRecentVoiceBroadcastEvent?.isLive.orFalse() + val isPlaying = playingState == State.Playing || playingState == State.Buffering + isLiveListening = isLiveVoiceBroadcast && isPlaying } private fun onLiveListeningChanged(isLiveListening: Boolean) { @@ -439,7 +450,10 @@ class VoiceBroadcastPlayerImpl @Inject constructor( if (currentMediaPlayer == mp) { currentMediaPlayer = null } else { - error("The media player which has completed mismatches the current media player instance.") + Timber.w( + "## Voice Broadcast | onCompletion: The media player which has completed mismatches the current media player instance.\n" + + "currentMediaPlayer=${currentMediaPlayer.hashCode()}, mp=${mp.hashCode()}" + ) } // Next media player is already attached to this player and will start playing automatically @@ -458,7 +472,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean { - Timber.d("## Voice Broadcast | onError: what=$what, extra=$extra") + Timber.w("## Voice Broadcast | onError: what=$what, extra=$extra") // Do not change the playingState if the current player is still valid, // the error will be thrown again when switching to the next player if (playingState == State.Buffering || tryOrNull { currentMediaPlayer?.isPlaying } != true) { @@ -503,7 +517,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } } State.Idle -> { - if (playbackTime == null || percentage == null || (playlist.duration - playbackTime) < 50) { + // restart the playback time if player completed with less than 250 ms remaining time + if (playbackTime == null || percentage == null || (playlist.duration - playbackTime) < 250) { playbackTracker.stopPlayback(id) } else { playbackTracker.updatePausedAtPlaybackTime(id, playbackTime, percentage) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt index d464a253d3..bcc0b39f8e 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt @@ -49,7 +49,9 @@ value class VoiceBroadcastEvent(val root: Event) { get() = root.content.toModel() } +fun Event.isVoiceBroadcast() = type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO + /** * Map a [VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO] state event to a [VoiceBroadcastEvent]. */ -fun Event.asVoiceBroadcastEvent() = if (type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO) VoiceBroadcastEvent(this) else null +fun Event.asVoiceBroadcastEvent() = if (isVoiceBroadcast()) VoiceBroadcastEvent(this) else null diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/VoiceBroadcastRecorder.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/VoiceBroadcastRecorder.kt index 00e4bb17dd..4f8b614e3a 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/VoiceBroadcastRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/VoiceBroadcastRecorder.kt @@ -33,6 +33,8 @@ interface VoiceBroadcastRecorder : VoiceRecorder { val currentRemainingTime: Long? fun startRecordVoiceBroadcast(voiceBroadcast: VoiceBroadcast, chunkLength: Int, maxLength: Int) + + fun pauseOnError() fun addListener(listener: Listener) fun removeListener(listener: Listener) @@ -46,5 +48,6 @@ interface VoiceBroadcastRecorder : VoiceRecorder { Recording, Paused, Idle, + Error, } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/VoiceBroadcastRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/VoiceBroadcastRecorderQ.kt index 2da807293f..7ca6ab3c9c 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/VoiceBroadcastRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/VoiceBroadcastRecorderQ.kt @@ -29,10 +29,14 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase import im.vector.lib.core.utils.timer.CountUpTimer import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import org.matrix.android.sdk.api.session.sync.SyncState +import org.matrix.android.sdk.flow.flow import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.TimeUnit @@ -47,6 +51,7 @@ class VoiceBroadcastRecorderQ( private val sessionScope get() = session.coroutineScope private var voiceBroadcastStateObserver: Job? = null + private var syncStateObserver: Job? = null private var maxFileSize = 0L // zero or negative for no limit private var currentVoiceBroadcast: VoiceBroadcast? = null @@ -96,21 +101,36 @@ class VoiceBroadcastRecorderQ( observeVoiceBroadcastStateEvent(voiceBroadcast) } - override fun pauseRecord() { + override fun startRecord(roomId: String) { + super.startRecord(roomId) + observeConnectionState() + } + + override fun pauseOnError() { if (recordingState != VoiceBroadcastRecorder.State.Recording) return - tryOrNull { mediaRecorder?.stop() } - mediaRecorder?.reset() + + pauseRecorder() + stopObservingConnectionState() + recordingState = VoiceBroadcastRecorder.State.Error + } + + override fun pauseRecord() { + if (recordingState !in arrayOf(VoiceBroadcastRecorder.State.Recording, VoiceBroadcastRecorder.State.Error)) return + + pauseRecorder() + stopObservingConnectionState() recordingState = VoiceBroadcastRecorder.State.Paused - recordingTicker.pause() notifyOutputFileCreated() } override fun resumeRecord() { if (recordingState != VoiceBroadcastRecorder.State.Paused) return + currentSequence++ currentVoiceBroadcast?.let { startRecord(it.roomId) } recordingState = VoiceBroadcastRecorder.State.Recording recordingTicker.resume() + observeConnectionState() } override fun stopRecord() { @@ -128,6 +148,8 @@ class VoiceBroadcastRecorderQ( voiceBroadcastStateObserver?.cancel() voiceBroadcastStateObserver = null + stopObservingConnectionState() + // Reset data currentSequence = 0 currentMaxLength = 0 @@ -197,6 +219,27 @@ class VoiceBroadcastRecorderQ( } } + private fun pauseRecorder() { + if (recordingState != VoiceBroadcastRecorder.State.Recording) return + + tryOrNull { mediaRecorder?.stop() } + mediaRecorder?.reset() + recordingTicker.pause() + } + + private fun observeConnectionState() { + syncStateObserver = session.flow().liveSyncState() + .distinctUntilChanged() + .filter { it is SyncState.NoNetwork } + .onEach { pauseOnError() } + .launchIn(sessionScope) + } + + private fun stopObservingConnectionState() { + syncStateObserver?.cancel() + syncStateObserver = null + } + private inner class RecordingTicker( private var recordingTicker: CountUpTimer? = null, ) { diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/PauseVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/PauseVoiceBroadcastUseCase.kt index 0b22d7adf5..ee51f8280b 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/PauseVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/PauseVoiceBroadcastUseCase.kt @@ -16,17 +16,25 @@ package im.vector.app.features.voicebroadcast.recording.usecase +import im.vector.app.features.session.coroutineScope import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.take +import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent +import org.matrix.android.sdk.api.session.sync.SyncState +import org.matrix.android.sdk.flow.flow import timber.log.Timber import javax.inject.Inject @@ -51,25 +59,35 @@ class PauseVoiceBroadcastUseCase @Inject constructor( } } - private suspend fun pauseVoiceBroadcast(room: Room, reference: RelationDefaultContent?) { + private suspend fun pauseVoiceBroadcast(room: Room, reference: RelationDefaultContent?, remainingRetry: Int = 3) { Timber.d("## PauseVoiceBroadcastUseCase: Send new voice broadcast info state event") - // save the last sequence number and immediately pause the recording - val lastSequence = voiceBroadcastRecorder?.currentSequence - pauseRecording() + try { + // save the last sequence number and immediately pause the recording + val lastSequence = voiceBroadcastRecorder?.currentSequence - room.stateService().sendStateEvent( - eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, - stateKey = session.myUserId, - body = MessageVoiceBroadcastInfoContent( - relatesTo = reference, - voiceBroadcastStateStr = VoiceBroadcastState.PAUSED.value, - lastChunkSequence = lastSequence, - ).toContent(), - ) - } + room.stateService().sendStateEvent( + eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, + stateKey = session.myUserId, + body = MessageVoiceBroadcastInfoContent( + relatesTo = reference, + voiceBroadcastStateStr = VoiceBroadcastState.PAUSED.value, + lastChunkSequence = lastSequence, + ).toContent(), + ) - private fun pauseRecording() { - voiceBroadcastRecorder?.pauseRecord() + voiceBroadcastRecorder?.pauseRecord() + } catch (e: Failure) { + if (remainingRetry > 0) { + voiceBroadcastRecorder?.pauseOnError() + // Retry if there is no network issue (sync is running well) + session.flow().liveSyncState() + .filter { it is SyncState.Running } + .take(1) + .onEach { pauseVoiceBroadcast(room, reference, remainingRetry - 1) } + .launchIn(session.coroutineScope) + } + throw e + } } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt index 87ea49cece..d807c67f74 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt @@ -58,6 +58,7 @@ class StartVoiceBroadcastUseCase @Inject constructor( private val buildMeta: BuildMeta, private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase, private val stopVoiceBroadcastUseCase: StopVoiceBroadcastUseCase, + private val pauseVoiceBroadcastUseCase: PauseVoiceBroadcastUseCase, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -103,6 +104,14 @@ class StartVoiceBroadcastUseCase @Inject constructor( session.coroutineScope.launch { stopVoiceBroadcastUseCase.execute(room.roomId) } } } + + override fun onStateUpdated(state: VoiceBroadcastRecorder.State) { + if (state == VoiceBroadcastRecorder.State.Error) { + session.coroutineScope.launch { + pauseVoiceBroadcastUseCase.execute(room.roomId) + } + } + } }) voiceBroadcastRecorder?.startRecordVoiceBroadcast(voiceBroadcast, chunkLength, maxLength) } diff --git a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml index deec85e2ed..de9b92884c 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml @@ -40,51 +40,59 @@ - + app:layout_constraintTop_toBottomOf="@id/titleText"> - + - + - + - + + + app:constraint_referenced_ids="roomAvatarImageView,titleText,metadataGroup" /> + tools:text="@sample/rooms.json/data/name" /> - + app:layout_constraintTop_toBottomOf="@id/titleText"> - + - + + + app:constraint_referenced_ids="roomAvatarImageView,titleText,metadataGroup" /> + + + + diff --git a/vector/src/main/res/layout/view_voice_broadcast_buffering.xml b/vector/src/main/res/layout/view_voice_broadcast_buffering.xml index e292169537..2d62388288 100644 --- a/vector/src/main/res/layout/view_voice_broadcast_buffering.xml +++ b/vector/src/main/res/layout/view_voice_broadcast_buffering.xml @@ -21,5 +21,5 @@ style="@style/Widget.Vector.TextView.Caption" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/a11y_voice_broadcast_buffering" /> + android:text="@string/voice_broadcast_buffering" /> diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt index 5dfdd379e0..9aa0ddf3b2 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt @@ -60,7 +60,8 @@ class StartVoiceBroadcastUseCaseTest { context = FakeContext().instance, buildMeta = mockk(), getRoomLiveVoiceBroadcastsUseCase = fakeGetRoomLiveVoiceBroadcastsUseCase, - stopVoiceBroadcastUseCase = mockk() + stopVoiceBroadcastUseCase = mockk(), + pauseVoiceBroadcastUseCase = mockk(), ) )