diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b6746c77d3..597f78f541 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,7 @@ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: -# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: @@ -13,7 +13,7 @@ updates: reviewers: - "vector-im/element-android-reviewers" ignore: - - dependency-name: "*github-script*" + - dependency-name: "*" # Updates for Gradle dependencies used in the app - package-ecosystem: gradle directory: "/" @@ -22,5 +22,6 @@ updates: open-pull-requests-limit: 200 reviewers: - "vector-im/element-android-reviewers" - ignore: - - dependency-name: com.google.zxing:core + allow: + - dependency-name: "io.element.android:wysiwyg" + - dependency-name: "org.matrix.rustcomponents:crypto-android" diff --git a/CHANGES.md b/CHANGES.md index 39ea3f1477..0fa9d9bb15 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,26 @@ +Changes in Element v1.6.3 (2023-06-27) +====================================== + +Features ✨ +---------- + - **Element Android is now using the Crypto Rust SDK**. Migration of user's data should be done at first launch after application upgrade. ([#8390](https://github.com/vector-im/element-android/issues/8390)) + - [Rich text editor] Add mentions and slash commands ([#8440](https://github.com/vector-im/element-android/issues/8440)) + +Bugfixes 🐛 +---------- + - Update rich text editor library to support pasting of images. ([#8270](https://github.com/vector-im/element-android/issues/8270)) + - Fix | Got asked twice about verification #8353 (and other verification banners problems) ([#8353](https://github.com/vector-im/element-android/issues/8353)) + - Prompt the user when the invited MatrixId is not recognized ([#8468](https://github.com/vector-im/element-android/issues/8468)) + - The correct title and options are now displayed When a poll that was edited is ended. ([#8471](https://github.com/vector-im/element-android/issues/8471)) + - In some conditions the room shield is not refreshed correctly ([#8507](https://github.com/vector-im/element-android/issues/8507)) + - Fix crypto config fallback key sharing strategy ([#8541](https://github.com/vector-im/element-android/issues/8541)) + +Other changes +------------- + - MSC3987 implementation: the 'dont_notify' action for a push_rule is now deprecated and replaced by an empty action list. ([#8503](https://github.com/vector-im/element-android/issues/8503)) + - Update crypto rust sdk version to 0.3.10 ([#8554](https://github.com/vector-im/element-android/issues/8554)) + + Changes in Element v1.6.2 (2023-06-02) ====================================== diff --git a/dependencies.gradle b/dependencies.gradle index f4d32c4894..355f5de661 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -101,7 +101,7 @@ ext.libs = [ ], element : [ 'opusencoder' : "io.element.android:opusencoder:1.1.0", - 'wysiwyg' : "io.element.android:wysiwyg:2.2.1" + 'wysiwyg' : "io.element.android:wysiwyg:2.2.2" ], squareup : [ 'moshi' : "com.squareup.moshi:moshi:$moshi", @@ -172,6 +172,7 @@ ext.libs = [ 'kluent' : "org.amshove.kluent:kluent-android:1.73", 'timberJunitRule' : "net.lachlanmckee:timber-junit-rule:1.0.1", 'junit' : "junit:junit:4.13.2", + 'robolectric' : "org.robolectric:robolectric:4.9", ] ] diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 6292b5d231..66d07f258b 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -189,6 +189,7 @@ ext.groups = [ 'org.codehaus.groovy', 'org.codehaus.mojo', 'org.codehaus.woodstox', + 'org.conscrypt', 'org.eclipse.ee4j', 'org.ec4j.core', 'org.freemarker', @@ -221,6 +222,7 @@ ext.groups = [ 'org.ow2.asm', 'org.ow2.asm', 'org.reactivestreams', + 'org.robolectric', 'org.slf4j', 'org.sonatype.oss', 'org.testng', diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40106000.txt b/fastlane/metadata/android/cs-CZ/changelogs/40106000.txt new file mode 100644 index 0000000000..797d2af0ae --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40106000.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Element Android nyní používá Crypto Rust SDK. +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40106010.txt b/fastlane/metadata/android/cs-CZ/changelogs/40106010.txt new file mode 100644 index 0000000000..797d2af0ae --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40106010.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Element Android nyní používá Crypto Rust SDK. +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40106020.txt b/fastlane/metadata/android/cs-CZ/changelogs/40106020.txt new file mode 100644 index 0000000000..797d2af0ae --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40106020.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Element Android nyní používá Crypto Rust SDK. +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40106000.txt b/fastlane/metadata/android/de-DE/changelogs/40106000.txt new file mode 100644 index 0000000000..80ee810eec --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40106000.txt @@ -0,0 +1,2 @@ +Die wichtigsten Änderungen in dieser Version: Element Android nutzt nun das Crypto-Rust-SDK. +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40106010.txt b/fastlane/metadata/android/de-DE/changelogs/40106010.txt new file mode 100644 index 0000000000..80ee810eec --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40106010.txt @@ -0,0 +1,2 @@ +Die wichtigsten Änderungen in dieser Version: Element Android nutzt nun das Crypto-Rust-SDK. +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40106020.txt b/fastlane/metadata/android/de-DE/changelogs/40106020.txt new file mode 100644 index 0000000000..80ee810eec --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40106020.txt @@ -0,0 +1,2 @@ +Die wichtigsten Änderungen in dieser Version: Element Android nutzt nun das Crypto-Rust-SDK. +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40106030.txt b/fastlane/metadata/android/en-US/changelogs/40106030.txt new file mode 100644 index 0000000000..badf979955 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40106030.txt @@ -0,0 +1,2 @@ +Main changes in this version: Element Android is now using the Crypto Rust SDK. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40106000.txt b/fastlane/metadata/android/et/changelogs/40106000.txt new file mode 100644 index 0000000000..233a4a0cdd --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40106000.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: Element Android krüptoteekideks on nüüd Crypto Rust SDK. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40106010.txt b/fastlane/metadata/android/et/changelogs/40106010.txt new file mode 100644 index 0000000000..233a4a0cdd --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40106010.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: Element Android krüptoteekideks on nüüd Crypto Rust SDK. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40106020.txt b/fastlane/metadata/android/et/changelogs/40106020.txt new file mode 100644 index 0000000000..233a4a0cdd --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40106020.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: Element Android krüptoteekideks on nüüd Crypto Rust SDK. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40106000.txt b/fastlane/metadata/android/fa/changelogs/40106000.txt new file mode 100644 index 0000000000..1482b03042 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40106000.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: المنت اندروید اکنون از SDK راست Crypto استفاده می‌کند. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40106010.txt b/fastlane/metadata/android/fa/changelogs/40106010.txt new file mode 100644 index 0000000000..1482b03042 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40106010.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: المنت اندروید اکنون از SDK راست Crypto استفاده می‌کند. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40106020.txt b/fastlane/metadata/android/fa/changelogs/40106020.txt new file mode 100644 index 0000000000..1482b03042 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40106020.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: المنت اندروید اکنون از SDK راست Crypto استفاده می‌کند. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40106000.txt b/fastlane/metadata/android/fr-FR/changelogs/40106000.txt new file mode 100644 index 0000000000..7e04ed8f3c --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40106000.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Element Android utilise désormais le SDK cryptographique en Rust. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40106010.txt b/fastlane/metadata/android/fr-FR/changelogs/40106010.txt new file mode 100644 index 0000000000..7e04ed8f3c --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40106010.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Element Android utilise désormais le SDK cryptographique en Rust. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40106020.txt b/fastlane/metadata/android/fr-FR/changelogs/40106020.txt new file mode 100644 index 0000000000..7e04ed8f3c --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40106020.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Element Android utilise désormais le SDK cryptographique en Rust. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40106000.txt b/fastlane/metadata/android/id/changelogs/40106000.txt new file mode 100644 index 0000000000..b69e7fc7b9 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40106000.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Element Android sekarang menggunakan SDK Kripto Rust. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40106010.txt b/fastlane/metadata/android/id/changelogs/40106010.txt new file mode 100644 index 0000000000..b69e7fc7b9 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40106010.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Element Android sekarang menggunakan SDK Kripto Rust. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40106020.txt b/fastlane/metadata/android/id/changelogs/40106020.txt new file mode 100644 index 0000000000..b69e7fc7b9 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40106020.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Element Android sekarang menggunakan SDK Kripto Rust. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40106000.txt b/fastlane/metadata/android/it-IT/changelogs/40106000.txt new file mode 100644 index 0000000000..2e8ef554e1 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40106000.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: Element Android ora utilizza l'SDK Rust Crypto. +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40106010.txt b/fastlane/metadata/android/it-IT/changelogs/40106010.txt new file mode 100644 index 0000000000..2e8ef554e1 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40106010.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: Element Android ora utilizza l'SDK Rust Crypto. +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40106020.txt b/fastlane/metadata/android/it-IT/changelogs/40106020.txt new file mode 100644 index 0000000000..2e8ef554e1 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40106020.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: Element Android ora utilizza l'SDK Rust Crypto. +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/nb/short_description.txt b/fastlane/metadata/android/nb/short_description.txt index b7cad4c849..ebe1e02ede 100644 --- a/fastlane/metadata/android/nb/short_description.txt +++ b/fastlane/metadata/android/nb/short_description.txt @@ -1 +1 @@ -Sikker desentralisert chat & VoIP. Beskytt dataene dine fra tredjeparter. +Gruppe-meldingsapp - krypterte meldinger, gruppechat og videosamtaler diff --git a/fastlane/metadata/android/nb/title.txt b/fastlane/metadata/android/nb/title.txt index aacee5be54..fff0c3fe11 100644 --- a/fastlane/metadata/android/nb/title.txt +++ b/fastlane/metadata/android/nb/title.txt @@ -1 +1 @@ -Element (tidligere Riot.im) +Element - Sikker Meldingsapp diff --git a/fastlane/metadata/android/pl-PL/changelogs/40106000.txt b/fastlane/metadata/android/pl-PL/changelogs/40106000.txt new file mode 100644 index 0000000000..c91b48c168 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40106000.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Element Android teraz korzysta z Crypto Rust SDK. +Pełna lista zmian: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pl-PL/changelogs/40106010.txt b/fastlane/metadata/android/pl-PL/changelogs/40106010.txt new file mode 100644 index 0000000000..c91b48c168 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40106010.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Element Android teraz korzysta z Crypto Rust SDK. +Pełna lista zmian: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pl-PL/changelogs/40106020.txt b/fastlane/metadata/android/pl-PL/changelogs/40106020.txt new file mode 100644 index 0000000000..c91b48c168 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40106020.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Element Android teraz korzysta z Crypto Rust SDK. +Pełna lista zmian: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40106020.txt b/fastlane/metadata/android/ru-RU/changelogs/40106020.txt new file mode 100644 index 0000000000..11550d3bb1 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40106020.txt @@ -0,0 +1,2 @@ +Главные изменения этой версии: Element для Android теперь использует Crypto Rust SDK. +Полный список изменений: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40106000.txt b/fastlane/metadata/android/sk/changelogs/40106000.txt new file mode 100644 index 0000000000..bb76d77ad5 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40106000.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Element Android teraz používa Crypto Rust SDK. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40106010.txt b/fastlane/metadata/android/sk/changelogs/40106010.txt new file mode 100644 index 0000000000..bb76d77ad5 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40106010.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Element Android teraz používa Crypto Rust SDK. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40106020.txt b/fastlane/metadata/android/sk/changelogs/40106020.txt new file mode 100644 index 0000000000..bb76d77ad5 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40106020.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Element Android teraz používa Crypto Rust SDK. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40106000.txt b/fastlane/metadata/android/sq/changelogs/40106000.txt new file mode 100644 index 0000000000..5b8003c310 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40106000.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Element Android tani përdor Crypto Rust SDK. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40106010.txt b/fastlane/metadata/android/sq/changelogs/40106010.txt new file mode 100644 index 0000000000..0c2470c653 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40106010.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Element Android tanimë përdor SDK Rust Fshehtëzimesh. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40106020.txt b/fastlane/metadata/android/sq/changelogs/40106020.txt new file mode 100644 index 0000000000..0cb7a00683 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40106020.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Element Android tanimë përdor SDK Rust për Kripto. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40106000.txt b/fastlane/metadata/android/sv-SE/changelogs/40106000.txt new file mode 100644 index 0000000000..215691cf4b --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40106000.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Element Android använder nu Rust-krypto-SDK:t +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40106010.txt b/fastlane/metadata/android/sv-SE/changelogs/40106010.txt new file mode 100644 index 0000000000..215691cf4b --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40106010.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Element Android använder nu Rust-krypto-SDK:t +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40106020.txt b/fastlane/metadata/android/sv-SE/changelogs/40106020.txt new file mode 100644 index 0000000000..215691cf4b --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40106020.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Element Android använder nu Rust-krypto-SDK:t +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40106000.txt b/fastlane/metadata/android/uk/changelogs/40106000.txt new file mode 100644 index 0000000000..0066e0c597 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40106000.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Element Android тепер використовує Crypto Rust SDK. +Перелік усіх змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40106010.txt b/fastlane/metadata/android/uk/changelogs/40106010.txt new file mode 100644 index 0000000000..0066e0c597 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40106010.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Element Android тепер використовує Crypto Rust SDK. +Перелік усіх змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40106020.txt b/fastlane/metadata/android/uk/changelogs/40106020.txt new file mode 100644 index 0000000000..0066e0c597 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40106020.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Element Android тепер використовує Crypto Rust SDK. +Перелік усіх змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/vi/changelogs/40103020.txt b/fastlane/metadata/android/vi/changelogs/40103020.txt index 33a81f4a5d..d2eafcb2c6 100644 --- a/fastlane/metadata/android/vi/changelogs/40103020.txt +++ b/fastlane/metadata/android/vi/changelogs/40103020.txt @@ -1,2 +1,2 @@ -Những thay đổi chính trong phiên bản này: Thêm hỗ trợ Android Auto. Sửa rất nhiều lỗi! -Log thay đổi đầy đủ: https://github.com/vector-im/element-android/releases/tag/v1.3.2 +Thay đổi chính trong phiên bản này: Hỗ trợ Android Auto. Sửa rất nhiều lỗi! +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases/tag/v1.3.2 diff --git a/fastlane/metadata/android/vi/changelogs/40103030.txt b/fastlane/metadata/android/vi/changelogs/40103030.txt index a36a3bb46d..91bab8d655 100644 --- a/fastlane/metadata/android/vi/changelogs/40103030.txt +++ b/fastlane/metadata/android/vi/changelogs/40103030.txt @@ -1,2 +1,2 @@ -Những thay đổi chính trong phiên bản này: Hiển thị (các) chính sách máy chủ xác thực trong phần cài đặt. Tạm thời bỏ hỗ trợ Android Auto. -Log thay đổi đầy đủ: https://github.com/vector-im/element-android/releases/tag/v1.3.3 +Thay đổi chính trong phiên bản này: Hiển thị (các) chính sách máy chủ định danh trong phần cài đặt. Tạm thời bỏ hỗ trợ Android Auto. +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases/tag/v1.3.3 diff --git a/fastlane/metadata/android/vi/changelogs/40103040.txt b/fastlane/metadata/android/vi/changelogs/40103040.txt index a1f6a8b22e..f6217c143b 100644 --- a/fastlane/metadata/android/vi/changelogs/40103040.txt +++ b/fastlane/metadata/android/vi/changelogs/40103040.txt @@ -1,2 +1,2 @@ -Những thay đổi chính trong phiên bản này: Thêm hỗ trợ hiển thị, cho phòng Tin nhắn Trực tiếp (lưu ý: hiển thị bị vô hiệu hóa trên matrix.org. Hỗ trợ Android Auto trở lại. -Nhật ký thay đổi: https://github.com/vector-im/element-android/releases/tag/v1.3.4 +Thay đổi chính trong phiên bản này: Hỗ trợ trạng thái, cho các phòng nhắn tin trực tiếp (ghi chú: trạng thái bị vô hiệu trên matrix.org). Hỗ trợ Android Auto trở lại. +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases/tag/v1.3.4 diff --git a/fastlane/metadata/android/vi/changelogs/40103050.txt b/fastlane/metadata/android/vi/changelogs/40103050.txt new file mode 100644 index 0000000000..64f0b89929 --- /dev/null +++ b/fastlane/metadata/android/vi/changelogs/40103050.txt @@ -0,0 +1,2 @@ +Thay đổi chính trong phiên bản này: Hỗ trợ trạng thái, cho các phòng nhắn tin trực tiếp (ghi chú: trạng thái bị vô hiệu trên matrix.org). Hỗ trợ Android Auto trở lại. +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases/tag/v1.3.5 diff --git a/fastlane/metadata/android/vi/changelogs/40103060.txt b/fastlane/metadata/android/vi/changelogs/40103060.txt new file mode 100644 index 0000000000..7ee925059e --- /dev/null +++ b/fastlane/metadata/android/vi/changelogs/40103060.txt @@ -0,0 +1,2 @@ +Thay đổi chính trong phiên bản này: Hỗ trợ trạng thái, cho các phòng nhắn tin trực tiếp (ghi chú: trạng thái bị vô hiệu trên matrix.org). Hỗ trợ Android Auto trở lại. +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases/tag/v1.3.6 diff --git a/fastlane/metadata/android/vi/changelogs/40105080.txt b/fastlane/metadata/android/vi/changelogs/40105080.txt new file mode 100644 index 0000000000..b2f91aeb98 --- /dev/null +++ b/fastlane/metadata/android/vi/changelogs/40105080.txt @@ -0,0 +1,2 @@ +Thay đổi chính trong phiên bản này: Sửa lỗi và cải thiện. +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/vi/changelogs/40105100.txt b/fastlane/metadata/android/vi/changelogs/40105100.txt new file mode 100644 index 0000000000..caba6b7927 --- /dev/null +++ b/fastlane/metadata/android/vi/changelogs/40105100.txt @@ -0,0 +1,2 @@ +Thay đổi chính trong phiên bản này: Triển khai chế độ toàn màn hình mới cho trình soạn thảo văn bản phong phú và sửa lỗi. +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/vi/changelogs/40105110.txt b/fastlane/metadata/android/vi/changelogs/40105110.txt new file mode 100644 index 0000000000..caba6b7927 --- /dev/null +++ b/fastlane/metadata/android/vi/changelogs/40105110.txt @@ -0,0 +1,2 @@ +Thay đổi chính trong phiên bản này: Triển khai chế độ toàn màn hình mới cho trình soạn thảo văn bản phong phú và sửa lỗi. +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/vi/changelogs/40105120.txt b/fastlane/metadata/android/vi/changelogs/40105120.txt index 803f1d99bd..ef2d600e1e 100644 --- a/fastlane/metadata/android/vi/changelogs/40105120.txt +++ b/fastlane/metadata/android/vi/changelogs/40105120.txt @@ -1,2 +1,2 @@ Thay đổi chính trong phiên bản này: Chức năng chủ đề được bật theo mặc định. -Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/vi/changelogs/40105130.txt b/fastlane/metadata/android/vi/changelogs/40105130.txt index 803f1d99bd..ef2d600e1e 100644 --- a/fastlane/metadata/android/vi/changelogs/40105130.txt +++ b/fastlane/metadata/android/vi/changelogs/40105130.txt @@ -1,2 +1,2 @@ Thay đổi chính trong phiên bản này: Chức năng chủ đề được bật theo mặc định. -Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/vi/changelogs/40105140.txt b/fastlane/metadata/android/vi/changelogs/40105140.txt index 803f1d99bd..ef2d600e1e 100644 --- a/fastlane/metadata/android/vi/changelogs/40105140.txt +++ b/fastlane/metadata/android/vi/changelogs/40105140.txt @@ -1,2 +1,2 @@ Thay đổi chính trong phiên bản này: Chức năng chủ đề được bật theo mặc định. -Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/vi/changelogs/40105160.txt b/fastlane/metadata/android/vi/changelogs/40105160.txt index 803f1d99bd..ef2d600e1e 100644 --- a/fastlane/metadata/android/vi/changelogs/40105160.txt +++ b/fastlane/metadata/android/vi/changelogs/40105160.txt @@ -1,2 +1,2 @@ Thay đổi chính trong phiên bản này: Chức năng chủ đề được bật theo mặc định. -Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/vi/changelogs/40105180.txt b/fastlane/metadata/android/vi/changelogs/40105180.txt index 803f1d99bd..ef2d600e1e 100644 --- a/fastlane/metadata/android/vi/changelogs/40105180.txt +++ b/fastlane/metadata/android/vi/changelogs/40105180.txt @@ -1,2 +1,2 @@ Thay đổi chính trong phiên bản này: Chức năng chủ đề được bật theo mặc định. -Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/vi/changelogs/40105200.txt b/fastlane/metadata/android/vi/changelogs/40105200.txt index 4ec1289898..eda4b66d50 100644 --- a/fastlane/metadata/android/vi/changelogs/40105200.txt +++ b/fastlane/metadata/android/vi/changelogs/40105200.txt @@ -1,2 +1,2 @@ Thay đổi chính trong phiên bản này: Hầu hết là sửa lỗi. -Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/vi/changelogs/40105220.txt b/fastlane/metadata/android/vi/changelogs/40105220.txt index ab503fd458..881cf0601f 100644 --- a/fastlane/metadata/android/vi/changelogs/40105220.txt +++ b/fastlane/metadata/android/vi/changelogs/40105220.txt @@ -1,2 +1,2 @@ Thay đổi chính trong phiên bản này: Hầu hết là cải thiện chức năng phát thanh. -Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/vi/changelogs/40105240.txt b/fastlane/metadata/android/vi/changelogs/40105240.txt index 8ea7cb0c54..302e0ed184 100644 --- a/fastlane/metadata/android/vi/changelogs/40105240.txt +++ b/fastlane/metadata/android/vi/changelogs/40105240.txt @@ -1,2 +1,2 @@ Thay đổi chính trong phiên bản này: Hầu hết là sửa lỗi, cụ thể là sửa lỗi khiến cho tin nhắn không xuất hiện trên dòng thời gian. -Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/vi/changelogs/40105250.txt b/fastlane/metadata/android/vi/changelogs/40105250.txt index 8ea7cb0c54..302e0ed184 100644 --- a/fastlane/metadata/android/vi/changelogs/40105250.txt +++ b/fastlane/metadata/android/vi/changelogs/40105250.txt @@ -1,2 +1,2 @@ Thay đổi chính trong phiên bản này: Hầu hết là sửa lỗi, cụ thể là sửa lỗi khiến cho tin nhắn không xuất hiện trên dòng thời gian. -Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/vi/changelogs/40105260.txt b/fastlane/metadata/android/vi/changelogs/40105260.txt index e9ca191393..124a0f504c 100644 --- a/fastlane/metadata/android/vi/changelogs/40105260.txt +++ b/fastlane/metadata/android/vi/changelogs/40105260.txt @@ -1,2 +1,2 @@ Thay đổi chính trong phiên bản này: Chủ yếu là sửa lỗi. -Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/vi/changelogs/40105280.txt b/fastlane/metadata/android/vi/changelogs/40105280.txt index e9ca191393..124a0f504c 100644 --- a/fastlane/metadata/android/vi/changelogs/40105280.txt +++ b/fastlane/metadata/android/vi/changelogs/40105280.txt @@ -1,2 +1,2 @@ Thay đổi chính trong phiên bản này: Chủ yếu là sửa lỗi. -Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/vi/changelogs/40105300.txt b/fastlane/metadata/android/vi/changelogs/40105300.txt index 374a609262..617b41c00b 100644 --- a/fastlane/metadata/android/vi/changelogs/40105300.txt +++ b/fastlane/metadata/android/vi/changelogs/40105300.txt @@ -1,2 +1,2 @@ Thay đổi chính trong phiên bản này: Liên kết cố định tới các phòng, spaces, người dùng và tin nhắn giờ được hiển thị hình viên thuốc. Chúng tôi cũng đã sửa một số vấn đề với những nhãn dãn (sticker) tùy chỉnh và thanh đánh dấu đã đọc bị kẹt ở quá khứ. -Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/vi/changelogs/40105320.txt b/fastlane/metadata/android/vi/changelogs/40105320.txt index 57963d522b..d66fe4802d 100644 --- a/fastlane/metadata/android/vi/changelogs/40105320.txt +++ b/fastlane/metadata/android/vi/changelogs/40105320.txt @@ -1,2 +1,2 @@ Thay đổi chính trong phiên bản này: Chủ yếu là sửa lỗi -Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/vi/changelogs/40106000.txt b/fastlane/metadata/android/vi/changelogs/40106000.txt new file mode 100644 index 0000000000..08b7503d74 --- /dev/null +++ b/fastlane/metadata/android/vi/changelogs/40106000.txt @@ -0,0 +1,2 @@ +Thay đổi chính trong phiên bản này: Element Android giờ dùng công cụ phát triển phần mềm Rust cho mã hóa +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/vi/changelogs/40106010.txt b/fastlane/metadata/android/vi/changelogs/40106010.txt new file mode 100644 index 0000000000..08b7503d74 --- /dev/null +++ b/fastlane/metadata/android/vi/changelogs/40106010.txt @@ -0,0 +1,2 @@ +Thay đổi chính trong phiên bản này: Element Android giờ dùng công cụ phát triển phần mềm Rust cho mã hóa +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/vi/changelogs/40106020.txt b/fastlane/metadata/android/vi/changelogs/40106020.txt new file mode 100644 index 0000000000..08b7503d74 --- /dev/null +++ b/fastlane/metadata/android/vi/changelogs/40106020.txt @@ -0,0 +1,2 @@ +Thay đổi chính trong phiên bản này: Element Android giờ dùng công cụ phát triển phần mềm Rust cho mã hóa +Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40106000.txt b/fastlane/metadata/android/zh-TW/changelogs/40106000.txt new file mode 100644 index 0000000000..0ee6434a8d --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40106000.txt @@ -0,0 +1,2 @@ +此版本的主要變更:現在起,Element Android 使用 Crypto Rust SDK。 +完整異動:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40106010.txt b/fastlane/metadata/android/zh-TW/changelogs/40106010.txt new file mode 100644 index 0000000000..0ee6434a8d --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40106010.txt @@ -0,0 +1,2 @@ +此版本的主要變更:現在起,Element Android 使用 Crypto Rust SDK。 +完整異動:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40106020.txt b/fastlane/metadata/android/zh-TW/changelogs/40106020.txt new file mode 100644 index 0000000000..0ee6434a8d --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40106020.txt @@ -0,0 +1,2 @@ +此版本的主要變更:現在起,Element Android 使用 Crypto Rust SDK。 +完整異動: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 1df5a29451..14c322330d 100644 --- a/library/ui-strings/src/main/res/values-ar/strings.xml +++ b/library/ui-strings/src/main/res/values-ar/strings.xml @@ -1162,12 +1162,12 @@ كلمة السر الجديدة التالي - صفر - واحد - اثنان - قليلة - كثيرة - اخرى + لم تحدد + واحد محدد + اثنان محددان + %1$d محددة + %1$d محددة + %1$d محددة صفر @@ -1210,4 +1210,14 @@ د سا أنهى %1$s البث الصوتي. + أظهر كل الغرف في دليل الغرف. حتى الغرف ذات المحتوى الحساس. + هنا ستجد الطلبات والدعوات. + لا جديد. + الدعوات + الفضاءات هي طريقة جديدة لتنظيم الغرف والأفراد.أنشئ فضاءً للبدأ. + بدون فضاءات. + تفضيلات التخطيط + غير %1$s اسمه العلني إلى %2$s + إغلاق %s العناصر الفرعية + توسيع %s العناصر الفرعية \ 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 25b877648a..b74d4825e0 100644 --- a/library/ui-strings/src/main/res/values-cs/strings.xml +++ b/library/ui-strings/src/main/res/values-cs/strings.xml @@ -4,7 +4,7 @@ %1$s pozval(a) %2$s %1$s vás pozval(a) %1$s vstoupil(a) do místnosti - Uživatel %1$s opustil místnost + %1$s opustil(a) místnost %1$s odmítl(a) pozvání %1$s odebral(a) %2$s %1$s zrušil(a) vykázání %2$s @@ -28,7 +28,7 @@ (profilový obrázek byl také změněn) %1$s odstranili název místnosti %1$s odstranili téma místnosti - %1$s do této místnosti pozvali %2$s + %1$s do této místnosti pozval(a) %2$s %1$s přijali pozvání pro %2$s ** Nelze dešifrovat: %s ** Odesílatelovo zařízení nám neposlalo klíče pro tuto zprávu. @@ -40,7 +40,7 @@ %1$s a %2$s Prázdná místnost %s aktualizoval(a) tuto místnost. - %1$s zrušili pozvánku do místnosti pro %2$s + %1$s zrušil(a) pozvánku do místnosti pro %2$s Úvodní synchronizace: \nImportuji účet… Úvodní synchronizace: @@ -91,8 +91,8 @@ Odstranili jste téma místnosti %1$s odstranili obrázek místnosti Odstranili jste obrázek místnosti - Poslali jste %1$s pozvání ke vstupu do místnosti - Zrušili jste pozvánku ke vstupu do místnosti pro %1$s + Poslali jste %1$s pozvání do místnosti + Zrušili jste pozvánku do místnosti pro %1$s Přijali jste pozvání pro %1$s %1$s přidali widget %2$s Přidali jste widget %1$s @@ -177,7 +177,7 @@ Zpřístupnili jste budoucí zprávy pro %1$s %1$s zpřístupnil(a) budoucí zprávy pro %2$s Odešli jste z místnosti - Uživatel %1$s odešel z místnosti + %1$s opustil(a) místnost Vstoupili jste %1$s vstoupil(a) Založili jste diskusi @@ -278,8 +278,8 @@ Žádné výsledky Místnosti Odeslat záznamy - Odeslat záznamy zřícení - Odeslat screenshot + Odeslat záznamy o selhání + Odeslat snímek obrazovky Ohlásit chybu Zde popište svůj problém Za účelem diagnostiky problémů budou logy tohoto klienta odeslány s touto zprávou o chybě. Tato zpráva o chybě včetně logů a snímku obrazovky nebude veřejně viditelná. Pokud byste raději poslali pouze text výše, prosím odznačte: @@ -306,7 +306,7 @@ Nesprávné uživatelské jméno nebo heslo Zdá se, že toto není platná e-mailová adresa Tato e-mailová adresa je již zadána. - Zapomenuté heslo? + Zapomněli jste heslo\? Tento domovský server by se rád přesvědčil, že nejste robot E-mailovou adresu se nepodařilo ověřit. Přesvědčte se, že jste klepli na zaslaný odkaz Prosím, zadejte platné URL @@ -317,7 +317,7 @@ Přijmout Chyba Systémová upozornění - Prosím, popište chybu. Co jste provedli\? Jaké bylo očekávané chování\? Co se ve skutečnosti stalo\? + Popište prosím chybu. Co jste udělali\? Co jste očekávali, že se stane\? Co se ve skutečnosti stalo\? Pokud je to možné, prosím, napište popis anglicky. Třesením oznámit chybu Odeslat hlasovou zprávu @@ -380,7 +380,7 @@ %s píše… %1$s a %2$s píší… %1$s a %2$s a další píší… - Nemáte právo odesílat v této místnosti. + Nemáte oprávnění zveřejňovat příspěvky v této místnosti. %d nová zpráva %d nové zprávy @@ -798,7 +798,7 @@ Tiché Hlučné Vytvořit - Úvod + Domov Místnosti Pozvaní %2$s Vás vykopnul z %1$s @@ -1512,7 +1512,7 @@ Odeslat Přehrát Odmítnout - Nemáte povolení zahájit konferenční hovor v této místnosti + Nemáte oprávnění zahájit konferenční hovor v této místnosti Zahájit video schůzku Zahájit hlasovou schůzku Schůzky používají pravidla zabezpečení a přístupu Jitsi. Všichni lidé nyní v místnosti uvidí pozvánku k připojení, zatímco vaše schůzka probíhá. @@ -1635,7 +1635,7 @@ Role Otevřít chat Umlčet mikrofon - Zrušit umlčení mikrofonu + Zrušit ztlumení mikrofonu Zastavit fotoaparát Spustit fotoaparát Bezpečná záloha @@ -1795,7 +1795,7 @@ Toto je počátek této konverzace. Toto je počátek %s. Exportovat audit - K zapnutí šifrování v této místnosti nemáte oprávnění. + Nemáte oprávnění povolit šifrování v této místnosti. Přímá zpráva Zakládám místnost… Některé znaky nejsou dovoleny @@ -2182,8 +2182,8 @@ Propojte tento e-mail se svým účtem Pozvánka do této místnosti byla odeslána na adresu %s, která není spojena s vaším účtem Pozvánka do tohoto prostoru byla odeslána na adresu %s, která není spojena s vaším účtem - Všechny místnosti, ve kterých se nacházíte, se zobrazí v Úvodu. - Zobrazit všechny místnosti v Úvodu + Všechny místnosti, ve kterých se nacházíte, se zobrazí v Domovu. + Zobrazit všechny místnosti v Domovu Posunutím ukončíte hovor %1$s Klepněte pro návrat Probíhající hovor (%1$s) · @@ -2942,8 +2942,8 @@ Nastavit odkaz Přístupový token umožňuje plný přístup k účtu. Nikomu ho nesdělujte. Přístupový token - Přepnout na odrážky - Přepnout na číslovaný seznam + Přepnout seznam s odrážkami + Přepnout číslovaný seznam V této místnosti nejsou žádné předchozí hlasování Předchozí hlasování V této místnosti nejsou žádné aktivní hlasování @@ -3009,4 +3009,21 @@ Zásady přijatelného používání Přejít k obnovení Verze šifrování + %1$s změnil(a) své zobrazované jméno na %2$s + Profilový obrázek uživatele %1$s + Avatar místnosti %1$s + Avatar prostoru %1$s + Nejnovější aktualizace vylepšila zabezpečené zasílání zpráv. Znovu ověřte své zařízení. + Dokud tento uživatel této relaci nedůvěřuje, jsou zprávy odesílané do ní a z ní označeny varováním. + Aplikace aktualizována + Přesto se odhlásit + Nelze se spojit s domovským serverem. Pokud se přesto odhlásíte, nebude toto zařízení vymazáno ze seznamu zařízení, můžete jej odstranit pomocí jiného klienta. + Přesto zahájit chat + Přesto pozvat + Nelze najít profily pro níže uvedené Matrix identifikátory. Chcete přesto zahájit chat\? +\n +\n%s + Nelze najít profily pro níže uvedené Matrix identifikátory. Chcete je přesto pozvat\? +\n +\n%s \ No newline at end of file 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 65dd4157ea..334a1f45be 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -11,7 +11,7 @@ %1$s hat %2$s gebannt %1$s hat die Einladung für %2$s zurückgezogen %1$s hat das Profilbild geändert - %1$s hat den Anzeigenamen geändert in %2$s + %1$s hat den Anzeigenamen zu %2$s geändert %1$s hat den Anzeigenamen von %2$s auf %3$s geändert %1$s hat den Anzeigenamen gelöscht (war %2$s) %1$s hat das Raumthema geändert auf: %2$s @@ -2948,4 +2948,13 @@ Mit Sicherheitsschlüssel oder -phrase verifizieren … Nutzungsbedingungen Verschlüsselungsversion + %1$s hat den Anzeigenamen zu %2$s geändert + Benutzer-Profilbild von %1$s + Raum-Avatar von %1$s + Space-Avatar von %1$s + Verschlüsselte Kommunikation wurde mit der neuesten Aktualisierung verbessert. Bitte verifiziere deine Geräte erneut. + Solange der Benutzer dieser Sitzung nicht vertraut, werden Nachrichten mit Warnungen versehen. + App aktualisiert + Dein Heim-Server ist nicht erreichbar. Falls du dich dennoch abmeldest, wird dieses Gerät nicht von deiner Geräteliste entfernt, also müsstest du dies mit einer anderen Sitzung selbst machen. + Dennoch abmelden \ 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 ff6fe32a25..cf70cbf5d8 100644 --- a/library/ui-strings/src/main/res/values-et/strings.xml +++ b/library/ui-strings/src/main/res/values-et/strings.xml @@ -2949,4 +2949,21 @@ Sõnum Sõnum kasutajalt %s Krüptoteekide versioon + %1$s muutis oma uueks kuvatavaks nimeks %2$s + Kasutaja %1$s tunnuspilt + %1$s jututoa tunnuspilt + %1$s kogukonna tunnuspilt + Turvalisele sõnumivahetusele on lisandunud palju täiendusi. Palun verifitseeri oma seade uuesti. + Seni kuni nimetatud kasutaja usaldab seda sessiooni, kõik siit ja siia saadetud sõnumid on märgistatud hoiatusega. + Rakendus on uuendatud + Jah, ikkagi logi välja + Ühendus koduserveriga puudub. Kui sa jätkad ja logid võrgust välja, siis seda seadet ei kustutata sinu seadmete loendist ning saad seda hiljem mõnest muust Matrixi kliendist teha. + Ikkagi alusta vestlust + Kutsu siiski + Allpool loetletud Matrix\'i kasutajatunnustele ei leidunud profiile. Kas sa ikkagi tahaksid nendega vestlust alustada\? +\n +\n%s + Allpool loetletud Matrix\'i kasutajatunnustele ei leidunud profiile. Kas sa ikkagi tahaksid neile kutse saata\? +\n +\n%s \ No newline at end of file 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 9e971ce69b..fb5fd92f6b 100644 --- a/library/ui-strings/src/main/res/values-fa/strings.xml +++ b/library/ui-strings/src/main/res/values-fa/strings.xml @@ -10,7 +10,7 @@ %1$s، انسداد %2$s را رفع کرد %1$s، %2$s را مسدود کرد %1$s دعوت %2$s را نپذیرفت - %1$s تصویرش را عوض کرد + %1$s چهرکش را عوض کرد %1$s نام نمایشی خود را به %2$s تنظیم کرد %1$s نام نمایشیش را از %2$s به %3$s تغییر داد %1$s نام نمایشیش (%2$s) را پاک کرد @@ -26,7 +26,7 @@ همهٔ اعضای اتاق. هرکسی. %s این اتاق را ارتقا داد. - (تصویر هم عوض شد) + (چهرک نیز تغییر کرد) %1$s نام اتاق را پاک کرد %1$s موضوع اتاق را پاک کرد %1$s دعوتی برای پیوستن %2$s به اتاق فرستاد @@ -94,13 +94,13 @@ تحریم %1$s را برداشتید %1$s را تحریم کردید دعوت %1$s را پس‌گرفتید - آواتارتان را عوض کردید + چهرکتان را عوض کردید نام نمایشیتان را به %1$s تغییر دادید نام نمایشیتان را از %1$s به %2$s تغییر دادید نام نمایشیتان را برداشتید (%1$s بود) موضوع را به %1$s تغییر دادید - %1$s آواتار اتاق را تغییر داد - آواتار اتاق را تغییر دادید + %1$s چهرک اتاق را تغییر داد + چهرک اتاق را تغییر دادید نام اتاق را به %1$s تغییر دادید تماس تصویری گرفتید. تماس صوتی گرفتید. @@ -112,8 +112,8 @@ این اتاق را ارتقا دادید. نام اتاق را برداشتید موضوع اتاق را برداشتید - %1$s آواتار اتاق را برداشت - آواتار اتاق را برداشتید + %1$s چهرک اتاق را برداشت + چهرک اتاق را برداشتید برای %1$s دعوت پیوستن به اتاق فرستادید دعوت پیوستن %1$s به اتاق را پس گرفتید دعوت برای %1$s را پذیرفتید @@ -1065,7 +1065,7 @@ بازنشاندن از نشانی اصلی ارتباط با مدیر خدمتتان اکنون بازبینی شود - آواتار + چهرک دلیل: %1$s به دست %2$s از %1$s تحریم شدید به دست %2$s از %1$s اخراج شدید @@ -1185,7 +1185,7 @@ پیش‌نمایش محتوای چندرسانه‌ای قبل از ارسال لرزیدن گوشی در هنگام ذکر یک کاربر شامل تغییرات نام نمایشی و چهرک. - نمایش پیام‌های مربوط به حساب کاربری + نمایش رویدادهای حساب دعوت‌ها، برداشتن‌ها و انسدادها تأثیر نمی‌پذیرند. نمایش پیام‌های پیوستن و ترک اتاق پیش‌نمایشی از آدرس‌های URL در پیام‌ها نمایش داده شود. @@ -1691,7 +1691,7 @@ نمی‌توانید به خودتان پیام دهید! هم‌رسانی با متن جست‌وجوی آشنایان روی ماتریکس - تنظیم آواتار + تنظیم چهرک برای تأیید عبارت امنیتیتان، دوباره واردش کنید. عبارت امنیتی تنظیم یک عبارت امنیتی @@ -1794,7 +1794,7 @@ برداشتن پیام‌های دیگران آگاه کردن همه دستکاری ابزارک‌ها - تغییر آواتار اتاق + تغییر چهرک اتاق تغییر نشانی اصلی اتاق به کار انداختن رمزنگاری اتاق تغییر نمایانی تاریخچه @@ -2949,4 +2949,13 @@ سیاست استفادهٔ پذیرفتنی ادامه برای بازنشانی نگارش Crypto + %1$s نام نمایشیش را به %2$s تغییر داد + نگارهٔ نمایهٔ کاربر %1$s + چهرک اتاق %1$s + چهرک فضای %1$s + پیام‌رسانی امن با جدیدترین به‌روز رسانی بهبود یافته است. لطفاً افزاره‌تان را دوباره تأیید کنید. + تا کاربر این نشست را تأیید کند، پیام‌های فرستاده و گرفته‌اش با هشدار برچسب می‌خورند. + کاره به‌روز شد + خروج به هر صورت + نمی‌توان به کارساز خانگی رسید. اگر همچنان خارج شوید این افزاره از سیاههٔ افزاره‌هایتان پاک نخواهد شد و باید از کارخواهس دیگر برش دارید. \ No newline at end of file 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 96408da021..74018e87d1 100644 --- a/library/ui-strings/src/main/res/values-fi/strings.xml +++ b/library/ui-strings/src/main/res/values-fi/strings.xml @@ -12,7 +12,7 @@ %1$s veti takaisin kutsun käyttäjälle %2$s %1$s vaihtoi profiilikuvaansa %1$s asetti näyttönimekseen %2$s - %1$s muutti näyttönimensä nimestä %2$s nimeen %3$s + %1$s vaihtoi näyttönimensä nimestä %2$s nimeen %3$s %1$s poisti näyttönimensä (%2$s) %1$s vaihtoi aiheeksi: %2$s %1$s vaihtoi huoneen nimeksi %2$s @@ -150,7 +150,7 @@ Aloitit videopuhelun. Vaihdoit huoneen nimeksi: %1$s Vaihdoit huoneen profiilikuvaa - %1$s muutti huoneen profiilikuvaa + %1$s vaihtoi huoneen profiilikuvaa Vaihdoit aiheen: %1$s Poistit nimimerkkisi (se oli %1$s) Vaihdoit nimimerkkisi %1$s nimeen %2$s @@ -424,10 +424,10 @@ Viesti sisältää käyttäjänimeni Näytä aikaleimat 12 tunnin muodossa Analytiikka - Haluatko varmasti poistaa pienoissovelluksen tästä huoneesta\? + Haluatko varmasti poistaa sovelman tästä huoneesta\? Sovelmaa ei voitu luoda. Pyynnön lähetys epäonnistui. - Oikeustason täytyy olla positiivinen luku. + Oikeustason täytyy olla positiivinen kokonaisluku. Et ole tässä huoneessa. Sinulla ei ole oikeutta suorittaa toimintoa tässä huoneessa. room_id puuttuu pyynnöstä. @@ -930,13 +930,13 @@ Sovelma Lataa sovelma Sovelman lisäsi: - Sovelman käyttö saattaa asettaa keksejä ja jakaa tietoa kohteen %s kanssa: + Sovelman käyttö saattaa asettaa evästeitä ja jakaa tietoa kohteen %s kanssa: Sovelman käyttö saattaa jakaa tietoa kohteen %s kanssa: Sovelman lataus epäonnistui. \n%s Lataa sovelma uudelleen Avaa selaimessa - Kumoa minun pääsy + Kumoa minun pääsyni Näyttönimesi Profiilikuvasi osoite Käyttäjätunnisteesi @@ -946,9 +946,9 @@ Tämä sovelma haluaa käyttää seuraavia resursseja: Salli Estä kaikki - Käytä kameraa - Käytä mikrofonia - Lue DRM-suojattua mediaa + Käyttää kameraa + Käyttää mikrofonia + Lukea DRM-suojattua mediaa Huomiotta Muuta Tuo osapuolten välisen salauksen avaimet tiedostosta ”%1$s”. @@ -1252,7 +1252,7 @@ Haluatko varmasti poistaa tämän tapahtuman\? Huomaa, että jos poistat huoneen nimen tai aiheen muutostapahtuman, se voi perua muutoksen. Anna syy Käyttäjä poistanut tapahtuman, syynä: %1$s - Tapahtuma moderoitu huoneen ylläpitäjän toimesta, syynä: %1$s + Huoneen ylläpitäjä moderoi tapahtuman, syy: %1$s Avaimet ovat jo ajan tasalla! ${app_name} Android Avainpyynnöt @@ -1403,7 +1403,7 @@ Jos poistat käyttäjän porttikiellon, hän voi liittyä huoneeseen uudelleen. Tämä poistaa käyttäjän huoneesta, mutta hän voi liittyä uudelleen. \n -\nJos haluat estää häntä uudelleen liittymästä, anna hänelle porttikielto. +\nJos haluat estää häntä liittymästä uudelleen, anna hänelle porttikielto. Syy porttikielolle Syy poistolle Poista käyttäjä @@ -1461,7 +1461,7 @@ Järjestelmän oletus Matrix ID Poistettu viesti - Aktivoi sovelma + Aktiiviset sovelmat Poista esto Estä käyttäjä Tai @@ -2429,4 +2429,81 @@ Yhdistä Kumoa kutsu Kotipalvelin ei hyväksy pelkistä numeroista koostuvaa käyttäjänimeä. + Viesti huoneessa + Viesti huoneessa %s + Viesti + Viesti käyttäjältä %s + Kysely + päätti kyselyn. + loi kyselyn. + lähetti tarran. + lähetti videon. + lähetti kuvan. + lähetti ääniviestin. + lähetti äänitiedoston. + lähetti tiedoston. + Muokkaa linkkiä + Luo linkki + Linkki + Teksti + Koko näytön tila päälle/pois + Koodilohko päälle/pois + Lainaus päälle/pois + Aseta linkki + Käytä alleviivaus-muotoilua + Käytä yliviivaus-muotoilua + Käytä kursivointi-muotoilua + Käytä lihavointi-muotoilua + Varmista, että tiedät tämän koodin alkuperän. Laitteet linkittämällä annat täyden pääsyn tiliisi. + Vahvista + Yritä uudelleen + Kirjaudutaan + Yhdistetään laitteeseen + Avaa sovellus toisella laitteellasi + Kotipalvelin ei tue QR-koodilla kirjautumista. + QR-koodi on virheellinen. + Toisen laitteen on oltava kirjattu sisään. + Toinen laite on jo kirjattu sisään. + Pyyntö epäonnistui. + Yhteyden muodostaminen ei onnistunut + Turvallinen yhteys muodostettu + Kirjaudu QR-koodilla + Monikäyttöinen ja turvallinen viestisovellus tiimeille, kavereille ja organisaatioille. Aloita luomalla keskustelu tai liittymällä olemassa olevaan huoneeseen. + ${app_name} toivottaa sinut tervetulleeksi, +\n%s. + Sovellus + Muuta menetelmää kuin taustasynkronointi ei löytynyt. + Tekstin muotoilu + Zoomaa nykyiseen sijaintiin + Näytä kysely aikajanalla + Kelaa 30 sekuntia eteenpäin + Kelaa 30 sekuntia taaksepäin + Osa tuloksista voi olla piilotettu, koska ne ovat yksityisiä ja vaativat kutsun. + Vain kutsulla, paras vaihtoehto itsellesi tai tiimeille + Luotettu-luottamustaso + Varoitus-luottamustaso + Palautusavaimen tallennuspaikka: + %s, jotta ihmiset tietävät, mistä huoneessa on kyse. + Odotetaan käyttäjien liittymistä sovellukseen ${app_name} + Kyselyhistoria + kertakirjautuminen + Kirjaudu käyttäen palvelua %s + Rekisteröidy käyttäen palvelua %s + Olet antanut suostumuksen sähköpostiosoitteiden ja puhelinnumeroiden lähettämiseen identiteettipalvelimelle yhteystiedoissasi olevien käyttäjien löytämiseksi. + Sähköposti lähetettiin osoitteeseen %s, tarkista sähköpostisi ja napsauta vahvistuslinkkiä + Siirry + Suojaudu salattuihin viesteihin ja tietoihin pääsyn menettämiseltä varmuuskopioimalla salausavaimesi palvelimellesi. + Suojaudu salattuihin viesteihin ja tietoihin pääsyn menettämiseltä varmuuskopioimalla salausavaimesi palvelimellesi. + Suojaudu salattuihin viesteihin ja tietoihin pääsyn menettämiseltä + Käyttäjän %1$s profiilikuva + Avaa kehittäjätyökalujen ruutu + Tili + Kryptografian versio + Ilmoitusasetusten päivittämisessä tapahtui virhe. Yritä uudelleen. + Salaus on määritetty virheellisesti, joten et voi lähettää viestejä. Avaa asetukset napsauttamalla. + Salaus on määritetty virheellisesti, joten et voi lähettää viestejä. Ota yhteyttä ylläpitäjään salauksen palauttamiseksi toimintakuntoon. + ${app_name} tarvitsee luvan ilmoitusten näyttämiseen. Ilmoituksia voi saada viesteistä, kutsuista ym. +\n +\nAnna käyttöoikeus seuraavissa ponnahdusikkunoissa, jotta näet ilmoituksia. + %1$s vaihtoi näyttänimekseen %2$s \ No newline at end of file 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 e48d308ce7..bf813ea8f1 100644 --- a/library/ui-strings/src/main/res/values-fr/strings.xml +++ b/library/ui-strings/src/main/res/values-fr/strings.xml @@ -2949,4 +2949,13 @@ Politique d’utilisation acceptable Version de cryptographie Faire la réinitialisation + %1$s a modifié son nom d’affichage en %2$s + Image de profile de l’utilisateur %1$s + Avatar du salon %1$s + Avatar de l’espace %1$s + La messagerie sécurisée a été améliorée avec la dernière mise-à-jour. Veuillez re-vérifier votre appareil. + Jusqu’à ce que cet utilisateur fasse confiance à cette session, les messages sur cette session sont étiquetés avec des avertissements. + Application mise-à-jour + Se déconnecter malgré tout + Impossible de joindre le serveur d’accueil. Si vous vous déconnectez malgré tout, cet appareil ne sera pas effacé de la liste de vos appareils, vous pourrez l’enlever en utilisant un autre client. \ 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 c32cf40e85..c584ca793a 100644 --- a/library/ui-strings/src/main/res/values-in/strings.xml +++ b/library/ui-strings/src/main/res/values-in/strings.xml @@ -2891,4 +2891,13 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Kebijakan Penggunaan Wajar Versi kripto Lanjutkan mengatur ulang + %1$s mengubah nama tampilannya ke %2$s + Foto profil pengguna %1$s + Avatar ruangan %1$s + Avatar space %1$s + Perpesanan aman telah ditingkatkan dengan pembaruan terkini. Silakan verifikasi ulang perangkat Anda. + Sampai pengguna ini mempercayai sesi ini, pesan yang dikirim dan diterima akan ditandai dengan peringatan. + Aplikasi diperbarui + Tidak dapat mencapai homeserver. Jika Anda tetap keluar, perangkat ini tidak akan dihapus dari daftar perangkat. Anda dapat menghapusnya menggunakan klien lain. + Tetap keluar \ No newline at end of file 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 54ad5f5dd9..8a12dc5ca4 100644 --- a/library/ui-strings/src/main/res/values-it/strings.xml +++ b/library/ui-strings/src/main/res/values-it/strings.xml @@ -2,9 +2,9 @@ Invito di %s %1$s ha invitato %2$s - %1$s ti ha invitato - %1$s è entrato nella stanza - %1$s è uscito dalla stanza + %1$s ti ha invitato/a + %1$s è entrato/a nella stanza + %1$s è uscito/a dalla stanza %1$s ha rifiutato l\'invito %1$s ha buttato fuori %2$s %1$s ha rimosso il ban nei confronti di %2$s @@ -60,8 +60,8 @@ Invito di %1$s. Motivo: %2$s %1$s ha invitato %2$s. Motivo: %3$s %1$s ti ha invitato. Motivo: %2$s - %1$s è entrato nella stanza. Motivo: %2$s - %1$s è uscito dalla stanza. Motivo: %2$s + %1$s è entrato/a nella stanza. Motivo: %2$s + %1$s è uscito/a dalla stanza. Motivo: %2$s %1$s ha rifiutato l\'invito. Motivo: %2$s %1$s ha cacciato fuori %2$s. Motivo: %3$s %1$s ha riammesso %2$s. Motivo: %3$s @@ -87,8 +87,8 @@ Il tuo invito Hai creato la stanza Hai invitato %1$s - Sei entrato nella stanza - Sei uscito dalla stanza + Sei entrato/a nella stanza + Sei uscito/a dalla stanza Hai rifiutato l\'invito Hai buttato fuori %1$s Hai rimosso il ban nei confronti di %1$s @@ -133,8 +133,8 @@ %1$s da %2$s a %3$s Il tuo invito. Motivo: %1$s Hai invitato %1$s. Motivo: %2$s - Sei entrato nella stanza. Motivo: %1$s - Sei uscito dalla stanza. Motivo: %1$s + Sei entrato/a nella stanza. Motivo: %1$s + Sei uscito/a dalla stanza. Motivo: %1$s Hai rifiutato l\'invito. Motivo: %1$s Hai cacciato %1$s. Motivo: %2$s Hai riammesso %1$s. Motivo: %2$s @@ -160,10 +160,10 @@ %1$s ha impedito l\'accesso alla stanza agli ospiti. Hai permesso l\'accesso agli ospiti. %1$s ha permesso l\'accesso agli ospiti. - Sei entrato. Motivo: %1$s - Sei uscito. Motivo: %1$s - %1$s è uscito. Motivo: %2$s - %1$s è entrato. Motivo: %2$s + Sei entrato/a. Motivo: %1$s + Sei uscito/a. Motivo: %1$s + %1$s è uscito/a. Motivo: %2$s + %1$s è entrato/a. Motivo: %2$s Hai revocato l\'invito a %1$s %1$s ha revocato l\'invito a %2$s Hai invitato %1$s @@ -172,10 +172,10 @@ %s ha aggiornato la stanza. Hai reso visibili i messaggi futuri a %1$s %1$s ha reso visibili i messaggi futuri a %2$s - Sei uscito dalla stanza - %1$s è uscito dalla stanza - Sei entrato - %1$s è entrato + Sei uscito/a dalla stanza + %1$s è uscito/a dalla stanza + Sei entrato/a + %1$s è entrato/a Hai creato la discussione %1$s ha creato la discussione Stanza vuota (era %s) @@ -528,7 +528,7 @@ Crea Home Stanze - Invitato + Invitato/a %2$s ti ha buttato fuori da %1$s %2$s ti ha bannato da %1$s Motivo: %1$s @@ -677,7 +677,7 @@ ${app_name} non è influenzato dall\'ottimizzazione della batteria. Se si lascia un dispositivo scollegato, fermo e con lo schermo spento, dopo un certo tempo questo entra in modalità Doze. Ciò impedisce alle App di accedere alla rete e ritarda le attività, le sincronizzazioni e la ricezione dei normali allarmi. Ignora l\'ottimizzazione - Nessun APK Google Play Services trovato. Le notifiche non funzioneranno bene. + Google Play Services non trovato. Le notifiche non funzioneranno bene. Chiamata video in corso… Backup delle chiavi Usa il Backup delle chiavi @@ -851,7 +851,7 @@ Rispondi Riprova Ti ha inviato un invito - Invitato da %s + Invitato/a da %s Non hai più messaggi non letti Conversazioni Le tue conversazioni dirette verranno mostrate qui. Tocca il pulsante + in basso a destra per iniziarne qualcuna. @@ -1451,7 +1451,7 @@ MEDIA In questa stanza non ci sono file multimediali FILE - %1$s alle %2$s + %1$s: %2$s In questa stanza non ci sono file Accedi con il tuo ID utente Accedi con il tuo ID utente @@ -1684,7 +1684,7 @@ Fallo solo se non hai altri dispositivi con cui fare la verifica. Reimposta tutto Hai dimenticato o perso tutte le opzioni di ripristino\? Reimposta tutto - Sei entrato. + Sei entrato/a. I messaggi in questa conversazione sono cifrati end-to-end. Esci Impostazioni @@ -1716,7 +1716,7 @@ Il codice PIN è richiesto ogni volta che apri ${app_name}. Il codice PIN è richiesto dopo 2 minuti di inattività su ${app_name}. Richiedi il PIN dopo 2 minuti - %s è entrato. + %s è entrato/a. L\'applicazione è in attesa del PUSH Scarta le modifiche Ci sono modifiche non salvate. Scartare le modifiche\? @@ -1932,7 +1932,7 @@ Sincronizzazione iniziale: \nIn attesa di risposta dal server… Messaggio inviato - Sei stato invitato + Sei stato/a invitato/a Gli Spazi sono un nuovo modo per raggruppare stanze e contatti. Aggiungi stanze e Spazi esistenti Esci @@ -2008,7 +2008,7 @@ Consigliato Gestisci stanze Cerchi qualcuno che non è in %s\? - %s ti ha invitato + %s ti ha invitato/a Stanza pubblica Invia i file multimediali nella dimensione originale @@ -2287,7 +2287,7 @@ Questo server non presenta alcuna informativa. Librerie di terze parti L\'informativa del tuo server d\'identità - L\'informativa del tuo homeserver + Informativa del tuo homeserver Informativa di ${app_name} Puoi disattivarlo in qualsiasi momento nelle impostazioni Non condividiamo informazioni con terze parti @@ -2940,4 +2940,13 @@ Politica di utilizzo accettabile Procedi con la reimpostazione Versione crittografia + %1$s ha cambiato il nome visualizzato in %2$s + La messaggistica sicura è stata migliorata con l\'aggiornamento più recente. Ri-verifica il tuo dispositivo. + Finché l\'utente si fida di questa sessione, i messaggi inviati da e verso essa sono contrassegnati da avvisi. + App aggiornata + Immagine del profilo dell\'utente %1$s + Avatar della stanza %1$s + Avatar dello spazio %1$s + Disconnetti comunque + Impossibile contattare l\'homeserver. Se ti disconnetti comunque, questo dispositivo non verrà cancellato dalla tua lista, meglio se lo rimuovi da un altro client. \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-nn/strings.xml b/library/ui-strings/src/main/res/values-nn/strings.xml index 45c8679736..37b159f21f 100644 --- a/library/ui-strings/src/main/res/values-nn/strings.xml +++ b/library/ui-strings/src/main/res/values-nn/strings.xml @@ -38,7 +38,6 @@ Telefonnummer Rominnbjoding %1$s og %2$s - Tomt rom %s oppgraderte rommet. %1$s forlot rommet. Grunn: %2$s @@ -67,7 +66,6 @@ Slett Gje nytt namn Rapporter innhaldet - eller Inviter Logg av @@ -127,7 +125,6 @@ Dette ser ikkje ut som ei gyldig e-postadresse Denne e-postadressa er allereie i bruk. Gløymt passord\? - Heimtenaren ynskjer å stadfeste at du ikkje er ein robot Fekk ikkje til å stadfesta e-postadressa: sjå til at du klikka på lenken i e-posten Skriv inn ein gyldig URL @@ -152,14 +149,10 @@ Samtalen er i gang… Den andre parten tok ikkje samtalen. Info - - ${app_name} treng tilgang til mikrofonen din for å utføra talesamtalar. - ${app_name} treng tilgang til kameraet og mikrofonen din for å utføra videosamtalar. \n \nGjer vel og gjev tilgang på sprettvindauget som kjem for å utføra samtalen. - JA NEI Gå fram @@ -168,8 +161,6 @@ Avvis Medlemsoversikt Hopp til første uleste melding. - - %d medlem %d medlem @@ -205,12 +196,9 @@ Sertifikatet har forandra seg frå det som var stolt på av mobilen din. Dette er SÆRS UVANLEG. Det er tilrådd å IKKJE GODKJENNA dette nye sertifikatet. Sertifikatet har endra seg frå eit som tidlegare var stole på, til eit som ikkje er det. Det kan henda at tenaren har fornya sertifikatet. Snakk med administrator for å få det forventa fingeravtrykket. Godkjenn BERRE sertifikatet viss tenaradministratoren har publisert eit fingeravtrykk som samsvarar med det over. - Søk Filtrer rommedlemmar Ingi treff - - Alle meldingar Legg til på heimskjermen Profilbilete @@ -270,7 +258,6 @@ Oppdater offentleg namn Sist sedd %1$s @ %2$s - Stadfesting Logga inn som Heimtenar @@ -306,7 +293,6 @@ Desse funksjonane er under utprøving og uventa vanskar kan dukka opp. Bruk med omhug. Set som hovudadresse Fjern som hovudadresse - Preg Noko gjekk gale med dekrypteringa Offentleg namn @@ -329,7 +315,6 @@ Godkjenn For å godkjenna at denne sesjonen er til å stola på, ver venleg og snakk med eigaren på ein anna måte (t.d. ansikt til ansikt eller på telefon) og spør han om nøkkelen han ser i Brukarinnstillingane for denne sesjonen samsvarar med nøkkelen under: Viss det samsvarer, klikk Verifiser-knappen under. Viss det ikkje gjer det, avlyttar nokon andre denne sesjonen og du bør sannsynlegvis svarteliste den. I framtida vil denne verifikasjonsprosessen bli meir forbetra. - Vel ein romkatalog Heimtenar-URL Alle rom på %s-tenaren @@ -337,7 +322,6 @@ %d ulest varsla melding %d uleste varsla meldingar - %d rom %d rom @@ -413,16 +397,10 @@ Meldingssynlegheit på Matrix liknar på epost. At vi gløymer meldingane dine t Rommet er ein vidareføring av ei anna samtale Klikk her for å sjå gamle meldingar Systemvarsel - - - - %d valt %d valde - - kontakt tenesteadministratoren din Heimtenaren har truffe ei av ressursgrensene sine so nokre brukarar vil ikkje kunna logga inn. Heimtenaren har forbigått ei av ressursgrensene sine. @@ -482,7 +460,6 @@ Meldingssynlegheit på Matrix liknar på epost. At vi gløymer meldingane dine t Trekk tilbake Koble frå Avvis - Logg inn med SSO (single sign-on) Dette er ikkje ei gyldig Matrix-tenaradresse Klarar ikkje å nå ein heimetenar på denne URL\'en, sjekk at den er korrekt @@ -503,7 +480,7 @@ Meldingssynlegheit på Matrix liknar på epost. At vi gløymer meldingane dine t Versjon Matrix SDK versjon Innstillingar - Sikkerheit og personvern + Tryggleik og personvern Tale og video Hjelp og om Vis skjulte hendelsar i historikken @@ -561,4 +538,4 @@ Meldingssynlegheit på Matrix liknar på epost. At vi gløymer meldingane dine t Nullstill Folk Folk - + \ 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 603265bd26..31ab6c475d 100644 --- a/library/ui-strings/src/main/res/values-pl/strings.xml +++ b/library/ui-strings/src/main/res/values-pl/strings.xml @@ -377,7 +377,7 @@ %1$s w %2$s Czy na pewno chcesz usunąć widżet z tego pokoju? Nie można utworzyć widżetu. - Nie udało się wysłać prośby. + Nie udało się wysłać żądania. Poziom uprawnień musi być liczbą dodatnią. Nie jesteś w tym pokoju. Nie masz uprawnień, aby zrobić to w tym pokoju. @@ -391,7 +391,7 @@ Błąd polecenia Nierozpoznane polecenie: %s Głośne - Strona startowa + Strona główna Pokoje Zaproszeni Dezaktywuj konto @@ -609,7 +609,7 @@ Importowanie kluczy… Odblokuj Historię Wprowadź klucz odzyskiwania - Kopia Przywrócona %s ! + Kopia przywrócona %s! dodano jeden nowy klucz do tej sesji. dodano %d nowe klucze do tej sesji. @@ -661,7 +661,7 @@ Pomoc i o aplikacji Wszystkie wiadomości (hałaśliwy) Wszystkie wiadomości - Tylko wspomnienia + Tylko wzmianki Wycisz Ustawienia Nie ignorujesz żadnych użytkowników @@ -801,9 +801,9 @@ Użyj kopii zapasowej klucza aby odblokować historię zaszyfrowanych wiadomości użyj klucza odzyskiwania Użyj Klucza Odzyskiwania aby odblokować historię zaszyfrowanych wiadomości - Kopia zapasowa nie może zostać zdeszyfrowana za pomocą tego hasła: proszę upewnij się, czy wprowadzone hasło jest poprawne. + Kopia zapasowa nie mogła zostać rozszyfrowana za pomocą tego Hasła: upewnij się, że wprowadzono prawidłowe Hasło bezpieczeństwa. Przywracanie kopii zapasowej: - Kopia zapasowa nie może zostać zdeszyfrowana za pomocą tego klucza odzyskiwania: proszę upewnij się, czy wprowadzony klucz odzyskiwania jest poprawny. + Kopia zapasowa nie mogła zostać rozszyfrowana za pomocą tego Klucza: upewnij się, że wprowadzono prawidłowy Klucz bezpieczeństwa. Kopia zapasowa klucza nie jest aktywna dla tej sesji. Twoje klucze nie są będą zapisywane w kopii zapasowej od tej sesji. Kopia zapasowa posiada sygnaturę od nieznanej sesji z ID %s. @@ -1127,7 +1127,7 @@ %d aktywnych sesji %d aktywnych sesji - Zweryfikuj te urządzenie + Weryfikuj to urządzenie Otwórz obecną sesję i użyj jej do zweryfikowania obecnej, przyznając jej dostęp do zaszyfrowanych wiadomości. Zweryfkuj Zweryfikowano @@ -1240,7 +1240,7 @@ Wpisz frazę bezpieczeństwa którą znasz tylko ty, będzie wykorzystywana do zabezpieczania sekretów na twoim serwerze. Przechowuj swój Klucz Bezpieczeństwa w chronionym miejscu takim jak menadżer haseł lub sejf. Zapisz Klucz Bezpieczeństwa - Użyj Frazy Bezpieczeństwa + Użyj hasła bezpieczeństwa Użyj klucza bezpieczeństwa Zabezpiecza przed utratą dostępu do zaszyfrowanych wiadomości poprzez zapisanie kluczy szyfrujących na twoim serwerze. Włącz aparat @@ -1384,7 +1384,7 @@ Niektóre znaki nie są dozwolone Podaj adres pokoju Ten adres jest już w użyciu - Możesz aktywować tę opcję jeżeli pokój będzie wykorzystywany jedynie do współpracy z wewnętrznymi zespołami na Twoim serwerze domowym. Ta opcja nie może być zmieniona później. + Możesz aktywować tę opcję, jeżeli pokój będzie wykorzystywany jedynie do współpracy z wewnętrznymi zespołami na Twoim serwerze domowym. Nie będzie można zmienić tej opcji. Zablokuj wszystkich nie będących członkami %s przed dołączeniem do tego pokoju Ukryj zaawansowane Pokaż zaawansowane @@ -1545,13 +1545,13 @@ Konfigurowanie odzyskiwania. Nie zweryfikujesz %1$s (%2$s) jeżeli przerwiesz w tym momencie. Zacznij ponownie w ich profilu użytkownika. ROZUMIEM - Fraza Bezpieczeństwa - Ustaw Frazę Bezpieczeństwa + Hasło bezpieczeństwa + Ustaw hasło bezpieczeństwa Wybierz nazwę użytkownika. Potwierdź swoją tożsamość poprzez zweryfikowanie tego logowania aby uzyskać dostęp do zaszyfrowanych wiadomości. Potwierdź swoją tożsamość poprzez zweryfikowanie tego logowania przy pomocy którejś z pozostałych sesji w celu przyznania dostępu do zaszyfrowanych wiadomości. - Interaktywna weryfikacja z wykorzystaniem emotikon - Zweryfikuj logowanie + Zweryfikuj interaktywnie za pomocą emoji + Weryfikuj logowanie Zweryfikuj nowe logowanie do swojego konta: %1$s Zaszyfrowano przez urządzenie niezweryfikowane Niezaszyfrowane @@ -1564,7 +1564,7 @@ Zaczniesz ponownie od zera, bez historii, bez wiadomości, bez zaufanych urządzeń bądź użytkowników Jeżeli zresetujesz wszystko Wykonaj tę akcję wyłącznie wówczas gdy nie masz żadnego innego urządzenia na którym możesz zweryfikować bieżące urządzenie. - Zresetuj wszystko + Resetuj wszystko Zapomniałeś(-łaś) albo straciłeś wszystkie opcje odzyskiwania\? Zresetuj wszystko Nie udało się uzyskać dostępu do bezpiecznego magazynu Sprawdzanie klucza kopii zapasowej @@ -1818,7 +1818,7 @@ Uprawnienia pokoju Odblokowanie użytkownika pozwoli mu na ponowne dołączenie do tej przestrzeni. Zablokowanie użytkownika usunie go z tego miejsca i uniemożliwi ponowne dołączenie. - Ten pokój jest prywatny. Nie będziesz w stanie dołączyć bez zaproszenia. + Ten pokój nie jest publiczny. Nie będziesz w stanie dołączyć bez zaproszenia. Zakańczanie połączenia… Brak odpowiedzi Użytkownik, do którego zadzwoniłeś jest zajęty. @@ -1947,15 +1947,15 @@ Wszystkie pokoje, w których jesteś będą pokazywane na ekranie domowym. Pokaż wszystkie pokoje w ekranie domowym Zarządzaj pokojami oraz przestrzeniami - Oznacz jako nie sugerowana - Oznacz jako sugerowana + Oznacz jako nie sugerowane + Oznacz jako sugerowane Sugerowane Zarządzaj pokojami Szukasz kogoś , kto nie jest w %s\? %s Cię zaprasza Zostałeś zaproszony Przestrzenie są nową metodą na grupowanie razem wielu pokoi i osób. - Dodaj przestrzeń do jakiejkolwiek przestrzeni którą zarządzasz. + Dodaj przestrzeń do jakiejkolwiek przestrzeni, którą zarządzasz. Dodaj istniejące przestrzenie Dodaj istniejące pokoje Dodaj istniejące pokoje i przestrzenie @@ -1974,7 +1974,7 @@ Dołącz pomimo to Dołącz do przestrzeni Utwórz przestrzeń - Na razie pomiń + Pomiń na razie Dołącz do mojej przestrzeni %1$s %2$s Nie będą częścią %s Tylko do tego pokoju @@ -1983,13 +1983,13 @@ Udostępnij link Zaproś przez nazwę użytkownika lub email Zaproś przez email - Aktualnie jesteś tu tylko ty. %s będzie jeszcze lepszą przestrzenią, gdy dołączą do niej inni. + Aktualnie jesteś tu tylko Ty. %s będzie jeszcze lepsze, gdy dołączą inni. Zaproś do %s Zaproś osoby Zaproś osoby do Twojej przestrzeni Opis Tworzenie przestrzeni… - Losowy + Losowe Ogólny Kim są Twoi znajomi \? Stworzymy dla nich pokoje. Możesz też dodać następne w późniejszym etapie. @@ -2000,9 +2000,9 @@ Prywatna Otwarta dla każdego, najlepsza dla społeczności Publiczna - Prywatna przestrzeń dla Ciebie i Twoich znajomych - Ja i moi znajomi - Prywatna przestrzeń do organizacji Twoich pokojów + Prywatna przestrzeń dla Ciebie i Twoich kolegów z drużyny + Ja i moi koledzy z drużyny + Prywatna przestrzeń do organizacji Twoich pokoi Tylko ja Upewnij się, że odpowiednie osoby mają dostęp do %s. Z kim pracujesz\? @@ -2069,7 +2069,7 @@ Przekaż opinię Nie udało się przesłać opinii (%s) Dziękujemy, Twoja opinia została wysłana - Pozwalam na kontakt ze mną w razie dodatkowych pytań + Możesz się ze mną skontaktować, jeśli masz jakiekolwiek pytania Używasz przestrzeni w wersji beta. Ta opinia pomoże nam w tworzeniu kolejnych wersji. Twoja platforma i nazwa użytkownika zostaną odnotowane, abyśmy mogli w pełni wykorzystać Twoje sugestie. Prześlij opinię o przestrzeniach Stwórz nową przestrzeń @@ -2089,7 +2089,7 @@ Twój serwer Wersja pokoju Inne przestrzenie lub pokoje, których możesz nie znać - Przestrzeń, o której wiesz, że zawiera ten pokój + Przestrzeń, którą znasz, że zawiera ten pokój Zdecyduj kto może odnaleźć i dołączyć do tego pokoju. Dotknij, aby edytować przestrzenie Wybierz przestrzenie @@ -2110,7 +2110,7 @@ Usuń nagranie Opinia użytkownika Synchronizacja klucza samopodpisującego (Self Signing key) - Weryfikacja ręczna poprzez tekst + Zweryfikuj ręcznie za pomocą tekstu lub innego klienta Matrix z krzyżową weryfikacją nowych sesji logowania Nie masz uprawnień do zmiany poziomu pokoju Oczekiwanie na historię szyfrowania @@ -2210,7 +2210,7 @@ Rozmowa głosowa zakończona • %1$s Oddzwoń Nie udało się skonfigurować logowania krzyżowego - Aktualizacja spowoduje utworzenie pokoju w nowej wersji. Wszystkie obecne wiadomości zostaną w zarchiwizowanym pokoju. + Aktualizacja spowoduje utworzenie pokoju w nowej wersji. Wszystkie bieżące wiadomości zostaną zarchiwizowane w tym pokoju. Nagrywanie wiadomości głosowej Zatrzymaj nagrywanie Wstrzymaj wiadomość głosową @@ -2242,7 +2242,7 @@ Dodaj do danej przestrzeni Stwórz przestrzeń Edytuj treść - Poznaj stan pokoju + Przeglądaj stan pokoju Narzędzia deweloperskie Nieobecny Pokój publiczny @@ -2295,7 +2295,7 @@ Nie udostępniamy informacji podmiotom trzecim Nie zbieramy i nie profilujemy danych użytkownika tutaj - Pomóż nam znaleźć błędy i ulepszyć ${app_name} poprzez udostępnianie anonimowych danych użytkowania. Aby lepiej zrozumieć jak użytkownicy wykorzystują wiele urządzeń wygenerujemy losowy identyfikator dzielony pomiędzy Twoimi urządzeniami. + Pomóż nam zidentyfikować problemy i ulepszyć ${app_name}, udostępniając anonimowe dane o użytkowaniu. Aby zrozumieć, w jaki sposób użytkownicy korzystają z wielu urządzeń, wygenerujemy losowy identyfikator dzielony pomiędzy Twoimi urządzeniami. \n \nWięcej informacji %s. Pomóż usprawnić ${app_name} @@ -2311,7 +2311,7 @@ Posiadam już konto Bezpieczna komunikacja. Masz wszystko pod kontrolą. - Przejmij swoje konwersacje. + Bądź właścicielem swoich konwersacji. By odkryć istniejące kontakty, musisz najpierw przesłać swoje dane kontaktowe (adresy e-mail i numer telefonu) do serwera tożsamości. Przed wysłaniem Twoje dane zostaną zaszyfrowane w celu zachowania prywatności. Uzyskaj pomoc w korzystaniu z ${app_name} Nie masz uprawnień by dołączyć do tego pokoju @@ -2350,7 +2350,7 @@ Uwaga: aplikacja zostanie uruchomiona ponownie Włącz wiadomości w wątkach Upewnij się, że odpowiednie osoby mają dostęp do firmy %s. Więcej osób możesz zaprosić później. - Wyślij niestandardowe zdarzenie stanowe + Wyślij własne wydarzenie stanu Wyślij zdarzenie stanowe Zdarzenia stanowe Zawartość zdarzenia @@ -2583,7 +2583,7 @@ min Pokoje w przestrzeni Dalej - Dostawca + Metoda Znaleziono %d dostawcę. Znaleziono %d dostawców. @@ -2632,7 +2632,7 @@ Kod został wysłany do %s Potwierdź swój numer telefonu Wyloguj wszystkie urządzenia - Zresetuj hasło + Resetuj hasło Upewnij się, że ma 8 lub więcej znaków. Wybierz nowe hasło Nowe hasło @@ -2670,7 +2670,7 @@ MSC3061: Współdzielenie kluczy pokoju dla wcześniejszych wiadomości Ten kod QR wygląda na niepoprawny. Spróbuj zweryfikować przy użyciu innej metody. Dostęp do wcześniejszych zaszyfrowanych wiadomości nie będzie możliwy. Zresetuj bezpieczną kopię zapasową wiadomości oraz klucze weryfikacyjne by zacząć od nowa. - Nie udało się zweryfikować tego urządzenia + Nie można zweryfikować tego urządzenia Zapoznaj się z warunkami i zasadami serwera %s Jaki jest adres twojego serwera\? Miejsce na twoje konwersacje @@ -3040,7 +3040,7 @@ Sprawdź, by upewnić się że Twoje konto jest bezpieczne Zaszyfrowano za pomocą usuniętego urządzenia Akceptowalna polityka użytkowania - Jak tylko zaproszeni użytkownicy dołączą do ${app_name}, będziesz mógł czatować w pokoju zaszyfrowanym end-to-end + Jak tylko zaproszeni użytkownicy dołączą do ${app_name}, będziesz mógł czatować w pokoju szyfrowanym end-to-end Czekanie aż użytkownicy dołączą do ${app_name} Żądanie weryfikacji nie zostało znalezione. Mogło zostać anulowane lub obsłużone przez inną sesję. Wznów @@ -3048,7 +3048,7 @@ Zdobądź najnowszą wersję (uwaga: mogą wystąpić problemy z logowaniem) Nightly build Zweryfikuj swoją tożsamość, aby uzyskać dostęp do wiadomości zaszyfrowanych i udowodnić swoją tożsamość innym. - Zweryfikuj za pomocą innego urządzenia + Weryfikuj innym urządzeniem Weryfikuję z Klucza bezpieczeństwa lub Frazy… Rozpoczął transmisje głosową Możesz zaprosić tylko jeden e-mail jednocześnie @@ -3068,4 +3068,13 @@ %1$d zaznaczono %1$d zaznaczono + %1$s zmienił swoją wyświetlaną nazwę na %2$s + Zdjęcie profilowe użytkownika %1$s + Awatar pokoju %1$s + Awatar przestrzeni %1$s + Bezpieczne wysyłanie wiadomości zostało usprawnione z najnowszą aktualizacją. Zweryfikuj swoje urządzenie ponownie. + Dopóki ten użytkownik nie zweryfikuje tej sesji, wysłane wiadomości będą zawierać ostrzeżenie. + Zaktualizowano aplikację + Wyloguj mimo to + Nie można skontaktować się z serwerem domowym. Jeśli mimo to się wylogujesz, urządzenie nie zostanie usunięte z listy urządzeń. Usuń je za pomocą innego klienta. \ 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 f6a2c94553..ee6431ea3f 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 @@ -1045,7 +1045,7 @@ Visualizar Histórico de Edição Termos de Serviço Ser descobertável por outras(os) - Usar Bots, bridges, widgets e pacotes de stickers + Usar bots, bridges, widgets e pacotes de stickers Servidor de identidade Desconectar servidor de identidade Configurar servidor de identidade @@ -1352,7 +1352,7 @@ \nA confirmação vai ser salvada localmente e compartilhada numa versão futura do app. Envia a dada mensagem colorida como um arco-íris Envia o dado emote colorido como um arco-íris - Timeline + Linha do tempo Editor de mensagem Habilitar encriptação ponta-a-ponta… Habilitar encriptação\? @@ -2292,8 +2292,8 @@ Jurídicos Este servidor não provê nenhuma política. Bibliotecas de terceiros - A política de seu servidor de identidade - A política de seu servidorcasa + A política do seu servidor de identidade + A política do seu servidor local Política de ${app_name} Nós não gravaremos nem criaremos um perfil dos dados de sua conta Ajude-nos a identificar problemas e melhorar ${app_name} ao compartilhar dados de uso anônimos. Para entender como pessoas usam seus múltiplos dispositivos, nós vamos gerar um identificador aleatório, compartilhado por seus dispositivos. @@ -2527,7 +2527,7 @@ Você precisa ter as permissões certas a fim de compartilhar localização ao vivo nesta sala. Você não tem permissão para compartilhar localização ao vivo Resultados vão ser visíveis quando a sondagem estiver terminada - Quando convidando numa sala encriptada que está compartilhando histórico, histórico encriptada vai estar visível. + Ao convidar alguém para uma sala criptografada que compartilha o histórico de texto, ele será visível mesmo sendo criptografado. MSC3061: Compartilhando chaves de sala para mensagens passadas Envie sua primeira mensagem para convidar %s a fazer chat Mensagens neste chat vão ser encriptadas ponta-a-ponta. @@ -2808,7 +2808,7 @@ A requisição falhou. Seja capaz de gravar e enviar broadcast de voz em timeline de sala. Broadcast de voz - Buffering… + Pré-carregando… Pausar broadcast de voz Tocar ou retomar broadcast de voz Parar gravação de broadcast de voz @@ -2884,4 +2884,29 @@ 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 - + Avatar da sala %1$s + Avatar do espaço %1$s + Os detalhes da sua conta são gerenciados separadamente em %1$s. + Conta + Termos de Uso Aceitável + Um erro ocorreu ao atualizar suas preferências de notificação. Por favor, tente novamente. + Seu servidorcasa ainda não suporta threads. + Continuar com o reset + %1$s mudou seu nome de exibição para %2$s + Incapaz de reproduzir esse broadcast de voz. + Incapaz de decriptar esse broadcast de voz. + Erro de conexão - Gravação pausada + Aplicação actualizada + Versão criptográfica + Não foi possível contactar o servidor doméstico. Se ainda assim terminar sessão, este dispositivo não será apagado da sua lista de dispositivos, poderá querer removê-lo utilizando outro cliente. + Imagem de perfil do utilizador %1$s + Iniciou uma emissão de voz + Histórico da sondagem + Verificar com outro dispositivo + Verifique a sua identidade para aceder a mensagens encriptadas e provar a sua identidade a outros. + Foi enviado um pedido de verificação. Abra uma das suas outras sessões para aceitar e iniciar a verificação. + Só é possível convidar um email de cada vez + Terminar sessão ainda assim + Verificação a partir de Chave ou Frase Segura… + Até que este utilizador confie nesta sessão, as mensagens enviadas para e a partir dela são marcadas com avisos. + \ 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 bf329b89ec..b48e72cbba 100644 --- a/library/ui-strings/src/main/res/values-ru/strings.xml +++ b/library/ui-strings/src/main/res/values-ru/strings.xml @@ -3024,4 +3024,35 @@ Голосовая трансляция начата Ваш домашний сервер не поддерживает список обсуждений. Остановить + Всё равно выйти + Подтвердить сброс + Аватар пространства %1$s + Аватар комнаты %1$s + Сообщение от %s + Сообщение в %s + Сообщение в комнате + Комната/Пространство + Аккаунт + Аватар профиля пользователя %1$s + Продолжайте, только если вы уверены, что ваш ключ утерян, а доступ к другим активным устройствам отсустствует. + Пока пользователь не верифицировал эту сессию, отправленные и полученные сообщения отмечаются предупреждениями. + Запрос на верификацию отправлен. Откройте приложение на одном из активных устройств и подтвердите эту сессию. + Можно пригласить один email за раз + Приложение обновлено + Верификация секретной фразы или ключа… + Подтвердите ваш сеанс для доступа к зашифрованным сообщениям и верификации для других пользователей. + Не удается подключиться к серверу. Если вы подтвердите выход сейчас, это устройство не будет удалено из списка активных сеансов. Удалить этот сеанс можно будет с другого устройства. + Ожидание подключения пользователей к ${app_name} + При изменении настроек уведомлений произошла ошибка. Попробуйте ещё раз. + Когда приглашенные пользователи присоединятся к ${app_name}, вы сможете писать им с использованием сквозного шифрования + Сброс ваших ключей верификации не может быть отменен. После сброса, вы не будете иметь доступа к старым зашифрованным сообщениям, а все ваши контакты, верифицировавшие вас ранее, увидят предупреждение о повторной верификации. + Зашифрованно неактивным устройством + Защищенный обмен сообщениями был обновлен. Пожалуйста, повторно верифицируйте ваше устройство. + Не удается расшифровать голосовое сообщение. + Обзор опроса во времени + Сообщение + Подтвердить с помощью активного устройства + Возобновить + %1$s изменил отображаемое имя на %2$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 6f95e6428b..7536c44cb7 100644 --- a/library/ui-strings/src/main/res/values-sk/strings.xml +++ b/library/ui-strings/src/main/res/values-sk/strings.xml @@ -3009,4 +3009,13 @@ Zásady prijateľného používania Pokračovať v obnovení Krypto verzia + %1$s zmenil/a svoje zobrazované meno na %2$s + Profilový obrázok používateľa %1$s + Obrázok miestnosti %1$s + Obrázok priestoru %1$s + Najnovšou aktualizáciou sa zlepšilo bezpečné zasielanie správ. Overte prosím znova svoje zariadenie. + Pokiaľ tento používateľ tejto relácii nedôveruje, správy odoslané do nej a z nej sú označené varovaním. + Aplikácia bola aktualizovaná + Aj tak sa odhlásiť + Nie je možné sa spojiť s domovským serverom. Ak sa aj tak odhlásite, toto zariadenie nebude vymazané zo zoznamu zariadení, môžete ho odstrániť pomocou iného klienta. \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-sl/strings.xml b/library/ui-strings/src/main/res/values-sl/strings.xml index 80bc09dd34..9fec98dab7 100644 --- a/library/ui-strings/src/main/res/values-sl/strings.xml +++ b/library/ui-strings/src/main/res/values-sl/strings.xml @@ -1,5 +1,5 @@ - + Ime Prijava hrošča Pošlji ekransko sliko @@ -22,4 +22,47 @@ %1$s je ustvaril sobo Ni omrežja. Preveri internetno povezavo. Ustvari novo sobo + Ustvaril si razpravo + %1$s je povabil %2$s + Pridružil si se + %1$s je izključil %2$s + Preklical si povabilo %1$s + %1$s je spremenil svojega avatarja + %1$s je spremenil svoje prikazno ime v %2$s + Svoje prikazno ime si spremenil v %1$s + %1$s je spremenil svoje prikazno ime iz %2$s v %3$s + %1$s je odstranil svoje prikazno ime (bilo je %2$s) + Tvoje povabilo + Odstranil si %1$s + %1$s je preklical povabilo %2$s + %1$s je spremenil temo v: %2$s + %1$s je spremenil avatarja sobe + Spremenil si avatarja sobe + Začel si video klic. + %s je začel video klic. + Začel si video klic. + %s je končal klic. + Končal si klic. + %1$s je %2$s omogočil ogled prihodnje zgodovine sobe + Omogočil si ogled prihodnje zgodovine sobe %1$s + + %1$d izbran + %1$d izbrana + %1$d izbranih + %1$d izbranih + + %1$s je ustvaril razpravo + Povabil si %1$s + %1$s je odstranil %2$s + %1$s je izključil %2$s + Izključil si %1$s + Spremenil si svojega avatarja + %1$s je spremenil svoje prikazno ime v %2$s + Svoje prikazno ime si spremenil iz %1$s v %2$s + Odstranil si svoje prikazno ime (bilo je %1$s) + Temo si spremenil v: %1$s + %s je začel video klic. + %1$s je omočil %2$s ogled prihodnjih sporočil + %1$s si omogočil ogled prihodnjih sporočil + vsi člani sobe, od trenutka povabila. \ No newline at end of file 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 39a67b3b72..f79d418453 100644 --- a/library/ui-strings/src/main/res/values-sq/strings.xml +++ b/library/ui-strings/src/main/res/values-sq/strings.xml @@ -2935,4 +2935,14 @@ Rregulla të Pranueshme Përdorimi Vazhdo me rikthimin te parazgjedhjet Version kriptografie + %1$s ndërroi emrin e vet në ekran në %2$s + Foto profili i përdoruesit %1$s + Avatar i dhomës %1$s + Avatar i hapësirës %1$s + Shkëmbimi i siguruar i mesazheve është përmirësuar me përditësimin më të ri. Ju lutemi, riverifikoni pajisjen tuaj. + Rimerre + Deri kur ky përdorues të besojë këtë sesion, mesazhet dërguar për të dhe nga ai etiketohem me sinjalizime. + Aplikacioni u përditësua + Dil, sido qoftë + S’kapet dot shërbyesi Home. Nëse keni dalë, sido qoftë, kjo pajisje s’do të fshihet te lista e pajisjeve tuaja, mund të doni ta hiqni duke përdorur klient tjetër. \ No newline at end of file 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 744c4c4e0f..3ec7b60778 100644 --- a/library/ui-strings/src/main/res/values-sv/strings.xml +++ b/library/ui-strings/src/main/res/values-sv/strings.xml @@ -2949,4 +2949,13 @@ Kryptoversion Ett fel uppstod när du uppdaterade dina aviseringsinställningar. Var god försök igen. Fortsätt till återställning + Profilbild för användaren %1$s + Avatar för rummet %1$s + Avatar för utrymmet %1$s + %1$s bytte sitt visningsnamn till %2$s + Säker meddelandehantering har förbättrats med den senaste uppdateringen. Vänligen verifiera din enhet igen. + Tills den här användaren litar på den här sessionen märks meddelanden som skickas till och från den med varningar. + App uppdaterad + Kan inte nå hemservern. Om du ändå loggar ut kommer den här enheten inte att raderas från din enhetslista, du kanske vill ta bort den med en annan klient. + Logga ut ändå \ No newline at end of file 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 0ba658abd7..a621db40b6 100644 --- a/library/ui-strings/src/main/res/values-uk/strings.xml +++ b/library/ui-strings/src/main/res/values-uk/strings.xml @@ -3069,4 +3069,13 @@ Політика прийнятного користування Перейти до скидання Криптоверсія + %1$s змінює своє ім\'я на %2$s + Зображення профілю користувача %1$s + Аватар кімнати %1$s + Аватар простору %1$s + В останньому оновленні було вдосконалено захищений обмін повідомленнями. Перевірте свій пристрій ще раз. + Поки користувач не довіряє цьому сеансу, повідомлення, надіслані до нього та від нього, позначаються попередженнями. + Застосунок оновлено + Усе одно вийти + Не вдалося зв\'язатися з домашнім сервером. Якщо ви все одно вийдете з системи, цей пристрій не буде видалено з вашого списку пристроїв, можливо, ви захочете видалити його за допомогою іншого клієнта. \ No newline at end of file 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 34cbdd6afc..38da26bd3d 100644 --- a/library/ui-strings/src/main/res/values-vi/strings.xml +++ b/library/ui-strings/src/main/res/values-vi/strings.xml @@ -1,5 +1,5 @@ - + Lắc điện thoại để báo cáo lỗi Có vẻ bạn đang lắc điện thoại một cách tức giận. Bạn có muốn mở màn hình báo cáo lỗi không\? Miêu tả vấn đề của bạn ở đây @@ -669,7 +669,7 @@ Phòng chat của bạn được hiển thị ở đây. Bạn có thể tạo mới phòng chat hoặc tham gia các phòng cộng đồng hiện có. Chọn một máy chủ Hãy bắt đầu - Mở rộng và hiệu chỉnh trải nghiệm của bạn + Mở rộng và tùy chỉnh trải nghiệm của bạn Giữ cho hội thoải riêng tư với bảo mật đầu cuối Chat với một người hoặc chat nhóm Đây là hội thoại của bạn. Bạn sở hữu nó. @@ -754,7 +754,7 @@ Cấu hình máy chủ định danh Ngắt kết nối máy chủ định danh Máy chủ định danh - Sử dụng Bot, cầu nối, widget hoặc sticker + Sử dụng Bot, cầu nối, widget hoặc gói sticker Được khám phá bởi người khác Điều khoản Dịch vụ Xem lịch sử chỉnh sửa @@ -789,7 +789,7 @@ Nén dữ liệu mặc định Media Chọn quốc gia - Quản lý email và số điện thoại liên kết với tài khoản Matrix + Quản lý địa chỉ thư điện tử và số điện thoại liên kết với tài khoản Matrix Email và số điện thoại Hiện tất cả tin nhắn từ %s\? Mật khẩu của bạn vừa được cập nhật @@ -911,7 +911,7 @@ Tiếp Email (tùy chọn) Email - Thêm địa chỉ email để phục hồi tài khoản. Sau này bạn có thể tùy chọn cho phép người khác tìm mình qua email. + Thêm địa chỉ thư điện tử để có thể phục hồi tài khoản. Sau này bạn có thể tùy chọn cho phép người khác tìm mình qua thông tin này. Thêm địa chỉ email Mật khẩu chưa được thay đổi. \n @@ -925,7 +925,7 @@ Nhấp vào đường dẫn để xác nhận mật khẩu mới. Sau khi bạn nhâp vào đường dẫn, hãy nhấp vào bên dưới. Email xác thực đã được gửi tới %1$s. Kiểm tra mailbox - Email này không gắn với tài khoản nào + Địa chỉ thư điện tử này không được liên kết với tài khoản nào Tiếp tục Đổi mật khẩu sẽ đặt lại tất cả khóa bảo mật trên tất cả phiên của bạn, làm cho lịch sử chat mã hóa không đọc được. Vui lòng Sao lưu Khóa hoặc xuất khẩu tất cả khóa bảo mật các phòng từ một phiên đăng nhập khác trước khi đặt lại mật khẩu. Cảnh báo! @@ -934,7 +934,7 @@ Tiếp Email xác thực thông tin đã được gửi tới bạn để xác nhận đặt lại mật khẩu mới. Đặt lại mật khẩu ở %1$s - Địa chỉ email này không có trong hệ thống. + Địa chỉ thư điện tử này không được liên kết với tài khoản nào. Địa chỉ Địa chỉ Dịch vụ Element Matrix Xóa lịch sử @@ -985,7 +985,7 @@ Đảm bảo rằng bạn nhấp vào đường link trong email được gửi tới bạn. Loại bỏ %s\? Số điện thoại - Không có địa chỉ email nào trong tài khoản của bạn + Không có địa chỉ thư điện tử nào trong tài khoản của bạn Địa chỉ email Hiển thị thông tin ứng dụng trong thiết lập hệ thống. Thông tin ứng dụng @@ -1116,7 +1116,7 @@ Chạy thử Chuẩn đoán khắc phục sự cố Bật thông báo qua email cho %s - Để được nhận thông báo qua email, hãy liên kết một địa chỉ mail với tài khoản Matrix của bạn + Để được nhận thông báo qua thư điện tử, hãy liên kết một địa chỉ thư điện tử với tài khoản Matrix của bạn Thông báo qua email Nâng cấp không gian Thay đổi tên không gian @@ -1203,7 +1203,7 @@ Câu hỏi hoặc chủ đề thăm dò ý kiến Tạo Cuộc thăm dò ý kiến %s trong Cài đặt để nhận lời mời trực tiếp trong ${app_name}. - Liên kết email này với tài khoản của bạn + Liên kết địa chỉ thư điện tử này với tài khoản của bạn Lời mời này đến Space này đã được gửi đến %s không được liên kết với tài khoản của bạn Lời mời này đến phòng này đã được gửi đến %s không được liên kết với tài khoản của bạn Xin lưu ý nâng cấp sẽ tạo ra một phiên bản mới của căn phòng. Tất cả các tin nhắn hiện tại sẽ ở trong phòng lưu trữ này. @@ -1431,7 +1431,7 @@ Sự đồng ý của người dùng chưa được cung cấp. Không có mối liên hệ hiện tại với mã định danh này. Sự kết hợp đã thất bại. - Đối với quyền riêng tư của bạn, ${app_name} chỉ hỗ trợ gửi email và số điện thoại của người dùng băm. + Để đảm bảo quyền riêng tư cho bạn, ${app_name} chỉ hỗ trợ gửi địa chỉ thư điện tử và số điện thoại của người dùng khi đã được băm. Trước tiên, vui lòng chấp nhận các điều khoản của máy chủ nhận dạng trong cài đặt. Trước tiên, vui lòng cấu hình máy chủ nhận dạng. Hoạt động này là không thể. Homeerver đã lỗi thời. @@ -1797,19 +1797,19 @@ Không thể kết nối với máy chủ xác thực Nhập URL máy chủ xác thực Bạn có đồng ý gửi thông tin này không\? - Để khám phá các liên hệ hiện có, bạn cần gửi thông tin liên hệ (email và số điện thoại) đến máy chủ nhận dạng của mình. Chúng tôi băm dữ liệu của bạn trước khi gửi cho quyền riêng tư. - Gửi email và số điện thoại đến %s + Để khám phá các liên hệ hiện có, bạn cần gửi thông tin liên hệ (địa chỉ thư điện tử và số điện thoại) đến máy chủ định danh của mình. Chúng tôi băm dữ liệu của bạn trước khi gửi để đảm bảo quyền riêng tư. + Gửi địa chỉ thư điện tử và số điện thoại đến %s Đồng ý Thu hồi sự đồng ý của tôi Các liên hệ của bạn là riêng tư. Để khám phá người dùng từ danh bạ của bạn, chúng tôi cần sự cho phép của bạn để gửi thông tin liên hệ đến máy chủ xác thực của bạn. - Bạn đã đồng ý gửi email và số điện thoại đến máy chủ xác thực này để khám phá những người dùng khác từ danh bạ của bạn. + Bạn đã đồng ý gửi địa chỉ thư điện tử và số điện thoại đến máy chủ định danh này để khám phá những người dùng khác từ danh bạ của bạn. Gửi email và số điện thoại - Chúng tôi đã gửi cho bạn một email xác nhận đến %s, trước tiên vui lòng kiểm tra email của bạn và nhấp vào liên kết xác nhận - Chúng tôi đã gửi cho bạn một email xác nhận đến %s, kiểm tra email của bạn và nhấp vào liên kết xác nhận + Chúng tôi đã gửi một thư đến %s, trước tiên vui lòng kiểm tra hòm thư của bạn và nhấp vào liên kết xác nhận + Chúng tôi đã gửi một thư đến %s, kiểm tra hòm thư của bạn và nhấp vào liên kết xác nhận Số điện thoại có thể khám phá Ngắt kết nối khỏi máy chủ xác thực của bạn sẽ có nghĩa là bạn sẽ không thể khám phá bởi những người dùng khác và bạn sẽ không thể mời người khác qua email hoặc điện thoại. Tùy chọn Khám phá sẽ xuất hiện khi bạn đã thêm số điện thoại. - app_id: + Định danh ứng dụng (ID): Không có cổng Push đã đăng ký Không có quy tắc Push nào được xác định Quy tắc Push @@ -1842,7 +1842,7 @@ Vui lòng chờ… Thay đổi mạng Thay đổi - Không có mạng. Vui lòng kiểm tra kết nối Internet. + Không có mạng. Kiểm tra kết nối Internet. Tạo Space mới Tạo phòng mới Sự kiện bị hỏng, không thể hiển thị @@ -1963,8 +1963,8 @@ Nhập %s của bạn để tiếp tục. Chìa khóa tin nhắn Cụm mật khẩu phục hồi - Xác minh bị hủy bỏ - Xác minh đã bị hủy bỏ. Bạn có thể bắt đầu xác minh lại. + Xác thực đã bị hủy bỏ + Xác thực đã bị hủy bỏ. Bạn có thể bắt đầu xác thực lại. Một trong những điều sau đây có thể bị xâm phạm: \n \n- Mật khẩu của bạn @@ -2011,7 +2011,7 @@ Mã QR Đặt lại khóa Khởi tạo xác thực chéo - Cho đến khi người dùng này tin tưởng phiên này, tin nhắn được gửi đến và đi từ nó được dán nhãn cảnh báo. Ngoài ra, bạn có thể xác minh thủ công. + Cho đến khi người dùng này tin tưởng phiên này, tin nhắn được gửi đến nó và từ nó đều mang nhãn cảnh báo. Ngoài ra, bạn có thể xác minh thủ công. %1$s (%2$s) đã đăng nhập bằng phiên mới: Phiên này được tin cậy để nhắn tin an toàn vì %1$s (%2$s) đã xác minh: Không tin cậy @@ -2206,7 +2206,7 @@ Không phải bây giờ Kích hoạt Nghe thông báo - Tùy chọn Khám phá sẽ xuất hiện sau khi bạn đã thêm email. + Tùy chọn Khám phá sẽ xuất hiện sau khi bạn đã thêm địa chỉ thư điện tử. Địa chỉ email có thể khám phá Hiện tại bạn không sử dụng máy chủ xác thực. Để khám phá và có thể khám phá bởi các liên hệ hiện có mà bạn biết, hãy cấu hình một danh bạ dưới đây. Không có chính sách được cung cấp bởi máy chủ xác thực @@ -2253,8 +2253,8 @@ Âm thanh & Hình ảnh Format: Url: - session_name: - app_display_name: + Tên hiển thị của phiên: + Tên hiển thị của ứng dụng: push_key: Gửi video với kích thước gốc @@ -2320,4 +2320,215 @@ %d thay đổi về danh sách truy cập + Mã hóa bị thiết đặt sai. + Đã chia sẻ vị trí + Không mở được liên kết: cộng đồng đã được thay bằng spaces + Quét mã QR + Tôi đã có tài khoản + Tạo tài khoản + Bỏ qua bước này + Lưu và tiếp tục + Vào cài đặt mỗi khi cần cập nhật hồ sơ + Trông rất tuyệt! + Thêm ảnh hồ sơ + Bạn có thể đổi lại sau + Tên hiển thị + Chọn tên hiển thị + Tên người dùng / Thư điện tử / Số điện thoại + Bạn có phải con người\? + Làm theo chỉ dẫn gửi tới %s + Đặt lại mật khẩu + Quên mật khẩu + Gửi lại thư + Không nhận được thư\? + Làm theo chỉ dẫn gửi tới %s + Xác nhận địa chỉ thư điện tử + Gửi lại mã + Một mã đã được gửi tới %s + Xác nhận số điện thoại + Đăng xuất mọi thiết bị + Đặt lại mật khẩu + Đặt mật khẩu chữa ít nhất 8 ký tự. + Chọn mật khẩu mới + Mật khẩu mới + Kiểm tra hòm thư. + Mã xác nhận + Số điện thoại + Nhập số điện thoại + Địa chỉ thư điện tử + Nhập địa chỉ thư điện tử + Sửa + Hay + Nơi các cuộc trò chuyện được đặt + Nơi các cuộc trò chuyện được đặt + Phải chứa ít nhất 8 ký tự + Tạo tài khoản + Tài khoản %s đã được tạo + Chúc mừng! + Về trang chủ + Cá nhân hóa hồ sơ + Kết nối tới máy chủ + Bỏ qua câu hỏi + Cộng đồng + Nhóm + Bạn bè và gia đình + Nhắn tin bảo mật. + Gửi phản hồi + Đã bật: + Thẻ hồ sơ: + Định danh của phiên: + Đi + Đang cập nhật dữ liệu của bạn… + Mọi người + Yêu thích + Chưa đọc + Tất cả + Xem trong phòng + Đang trả lời %s + Sửa + Sao lưu có con dấu hợp lệ từ người dùng này. + Lệnh \"%s\" không được hỗ trợ ở chủ đề. + Xin lỗi, phòng này không được tìm thấy +\nHãy thử lại.%s + Dùng của hệ thống + Chọn thủ công + Thiết đặt tự động + Chọn cỡ chữ + Bạn đã bật chỉ mã hóa với các phiên đã xác thực trong tất cả các phòng ở Cài đặt bảo mật. + Trong phòng có các thiết bị chưa được xác thực, chúng sẽ không thể giải mã tin nhắn mà bạn gửi. + Không gửi tin nhắn được mã hóa cho các phiên chưa xác thực trong phòng này. + Thông tin tài khoản của bạn được quản lý riêng rẽ tại %1$s. + Tài khoản + Tự động phát các ảnh động + Phiên bản thuật toán + Cấp quyền + + %1$s và %2$d người khác + + %1$s và %2$s + Mẹo: Nhấn giữ một tin nhắn và dùng “%s”. + Space là một cách mới để nhóm các phòng và mọi người. Tạo một space để bắt đầu. + Chưa có space nào. + %1$s đã đổi tên hiển thị thành %2$s + Cứ đăng xuất + Không thể kết nối tới máy chủ nhà. Nếu bạn cứ đăng xuất, thiết bị này sẽ không được xóa khỏi danh sách, bạn có thể muốn xóa nó bằng thiết bị khác. + Giao tiếp độc lập và bảo mật cho bạn mức độ riêng tư ngang với trò chuyện trực tiếp trong nhà. + Được mã hóa đầu cuối và không yêu cầu số điện thoại. Không quảng cáo hay khai thác dữ liệu. + Rời tất cả + Đặt lại các khóa xác thực không thể được hoàn tác. Sau khi đặt lại, bạn không thể truy cập vào các tin nhắn đã được mã hóa cũ, và tất cả bạn bè đã xác thực bạn trước đó sẽ thấy các cảnh báo cho đến khi nào bạn xác thực lại với họ. + Hiện thông tin hồ sơ mới nhất (ảnh đại diện và tên hiển thị) cho tất cả các tin nhắn. + Đăng xuất khỏi mọi phiên khác + Các phiên chưa được xác thực là các phiên đã đăng nhập bằng thông tin của bạn nhưng chưa được xác thực chéo. +\n +\nBạn cần đặc biệt chắc chắn là bạn nhận ra các phiên này vì chúng có thể đang sử dụng trái phép tài khoản của bạn. + Những người dùng khác trong tin nhắn trực tiếp và các phòng bạn tham gia có thể xem danh sách các phiên của bạn. +\n +\nNhư vậy giúp họ chắc chắn họ đang thực sự nói chuyện với bạn, nhưng vì thế mà họ cũng có thể thấy tên phiên bạn nhập ở đây. + Bật trình soạn thảo văn bản phong phú + Thử trình soạn thảo văn bản phong phú (sẽ có chế độ văn bản thuần) + Đây là nơi chứa các yêu cầu và lời mời mới. + Ảnh đại diện cho space %1$s + Ảnh đại diện cho phòng %1$s + Ảnh đại diện cho người dùng %1$s + Mở màn hình công cụ cho nhà phát triển + Thư viện ảnh + Đăng ký điểm cuối + Có một lỗi đã xảy ra. Kiểm tra kết nối mạng và thử lại. + Đang thử nghiệm + Địa chỉ máy chủ của bạn là gì\? Đó như là nhà của tất cả dữ liệu của bạn + Gửi ảnh và phim + Hiện tất cả (%1$d) + ${app_name} cũng là nơi tốt cho nơi làm việc. Được tin cậy bởi những tổ chức bảo mật nhất thế giới. + Tùy chọn bố cục + Mọi phiên + Sử dụng bố cục mới + Điểm cuối đã được đăng ký với máy chủ nhà. + Vị trí + Dịch vụ của Google + Hệ thống sẽ tự động gửi nhật ký khi nào xảy ra lỗi giải mã tin nhắn + + Đăng xuất khỏi %1$d phiên + + Các phiên đã được xác thực là phiên mà bạn đăng nhập bằng mật khẩu hay xác thực danh tính bằng một phiên khác +\n +\nNghĩa là bạn đã có tất cả các khóa cần thiết để mở khóa các tin nhắn đã được mã hóa và xác nhận với những người khác là bạn tin cậy phiên này. + Hiển thị và kiểm soát các phiên của bạn tốt hơn. + Ứng dụng nhắn tin bảo mật tất cả-trong-một cho nhóm, bạn bè và tổ chức. Tạo một cuộc trò chuyện, hay tham gia vào một phòng, để bắt đầu. + Chọn nơi lưu trữ các cuộc trò chuyện của bạn, cho bạn quyền kiểm soát và độc lập. Kết nối qua giao thức Matrix. + Chỉ tiếp tục nếu bạn chắc chắn là bạn đã mất tất cả các thiết bị và khóa bảo mật. + Đóng %s mục + Mở %s mục + Bạn chỉ có thể mời một địa chỉ thư điện tử một lần + Mở danh sách space + Chọn máy chủ của bạn + Bạn nắm quyền kiểm soát. + Muốn chạy một máy chủ của mình\? + %s cần xác nhận tài khoản của bạn + Đi thôi + Bạn sẽ trò chuyện với ai nhiều nhất\? + Chưa chắc chắn\? %s + URL Máy chủ + Đến lúc để thêm khuôn mặt vào tên + Nhắn tin với đồng đội. + Đã chia sẻ vị trí hiện tại + Tạo một cuộc trò chuyện hay phòng mới + Phát thanh + Những người khác có thể tìm thấy bạn %s + Chính sách máy chủ + %s sẽ gửi bạn một liên kết xác nhận + Làm chủ cuộc trò chuyện. + %s cần xác nhận tài khoản của bạn + Chào mừng trở lại! + Địa chỉ máy chủ của bạn là gì\? + Chúng tôi sẽ giúp bạn kết nối + Tìm để tham gia một máy chủ sẵn có\? + Đã bắt đầu phát thanh + Xác thực bằng khóa bảo mật hay chuỗi từ bảo mật… + Vui lòng liên hệ một quản trị viên để khôi phục mã hóa về trạng thái hợp lệ. + Khôi phục mã hóa + Bản dựng hằng ngày + Xác thực với thiết bị khác + Lịch sử bỏ phiếu + Các phiên + Bạn không thể truy cập vào các tin nhắn được mã hóa. Đặt lại sao lưu tin nhắn bảo mật và các khóa xác thực để bắt đầu lại. + Không thể xác thực thiết bị này + Ứng dụng được cập nhật + Trước khi người dùng này tin tưởng phiên này, các tin nhắn gửi đến phiên và từ phiên đều mang nhãn cảnh báo. + Xác thực danh tính để truy cập vào các tin nhắn được mã hóa và chứng minh danh tính của bạn với những người khác. + Tải bản dựng mới nhất (ghi chú: bạn có thể gặp vấn đề khi đăng nhập) + + Xem lại để chắc chắn rằng tài khoản bạn an toàn + %1$d phút %2$d giây + Hoạt động lần cuối + Tên phiên + Mã hóa bởi một thiết bị đã xóa + Mã hóa bị thiết lập sai + Chọn các phiên + Hủy bộ lọc + Thông báo đẩy + Tên + Địa chỉ + Phiên không hoạt động + Phiên chưa xác thực + Yêu cầu bàn phím không được phép cập nhật các dữ liệu cá nhân hóa như lịch sử nhập và từ điển dựa trên những gì bạn nhắn trong trò chuyện. Tuy vậy một số bàn phím sẽ không tuân theo cài đặt này. + Tính xác thực của tin nhắn đã mã hóa này không thể được đảm bảo trên thiết bị này. + Bạn có các phiên chưa xác thực + %1$s, %2$s, %3$s + Đăng xuất + Đăng xuất khỏi phiên này + Chi tiết phiên + Thông tin phần mềm, thiết bị, và hoạt động. + Phiên bản + Trình duyệt + Mẫu mã + Đổi tên phiên + Hiện địa chỉ Internet (IP) + Phần mềm + Địa chỉ Internet (IP) + Tên phiên + Gửi tin nhắn đầu tiên để mời %s vào cuộc trò chuyện + Bàn phím ẩn danh + Ẩn địa chỉ Internet (IP) + Phiên đã xác thực + Hệ điều hành \ No newline at end of file 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 52d5e5f17a..33168cdc75 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 @@ -210,7 +210,7 @@ 请描述你遇到的问题。你做了什么?你期望发生什么?实际上发生了什么? 在这里描述你的问题 进度(%s%%) - 主服务器 URL + 家服务器 URL 登录 提交 错误的用户名和/或密码 @@ -256,7 +256,7 @@ 开始语音通话 开始视频通话 拍摄照片或视频 - 此主服务器想确认你不是机器人 + 此家服务器想确认你不是机器人 电子邮件地址验证失败:请确保你已点击邮件中的链接 原始 通话正在连接…… @@ -368,7 +368,7 @@ 公开名称 最近一次上线 %1$s @ %2$s - 主服务器 + 家服务器 身份服务器 实验室 通过和你其它会话里的用户设置对比以下内容来确认: @@ -501,7 +501,7 @@ \n \n要添加一些吗? 缺少一个必需参数。 - 要想继续使用主服务器 %1$s 你必须阅读并同意其服务条款。 + 要想继续使用家服务器 %1$s 你必须阅读并同意其服务条款。 现在阅读 下载 发送语音消息 @@ -535,13 +535,13 @@ 联系你的服务管理员 本服务器其中一项资源已超出限制,部分用户将无法登录 本服务器其中一项资源已超出限制。 - " 此主服务器已达到其每月活跃用户限制,因此<b>某些用户将无法登录</b>。" - 此主服务器已达到其每月活跃用户限制。 + " 此家服务器已达到其每月活跃用户限制,因此<b>某些用户将无法登录</b>。" + 此家服务器已达到其每月活跃用户限制。 请 %s 以继续使用本服务。 请 %s 以增加此限制的额度。 接受 错误 - 请审阅并接受此主服务器的政策: + 请审阅并接受此家服务器的政策: 通话 为来电使用 ${app_name} 的默认铃声 来电铃声 @@ -580,15 +580,15 @@ FCM令牌获取失败: \n%1$s 注册令牌 - FCM令牌已成功注册至主服务器。 - 未能将FCM令牌注册到主服务器: + FCM令牌已成功注册至家服务器。 + 未能将FCM令牌注册到家服务器: \n %1$s 启动系统相机而非自定义的相机屏幕。 开机时启动 启用开机时启动 检查后台限制 电池优化 - 当主服务器支持此功能时,在聊天中预览链接。 + 当家服务器支持此功能时,在聊天中预览链接。 发送正在输入通知 让房间中的其他用户知道你正在输入。 Markdown 格式化 @@ -652,7 +652,7 @@ 设置响铃通知 设置电话通知 设置静音通知 - 选择指示灯颜色,震动,铃声… + 选择LED颜色、震动、铃声…… 加密密钥管理 恢复已加密消息 管理密钥备份 @@ -669,7 +669,7 @@ (高级) 手动导出密钥 使用口令词组保护你的备份。 - 我们将会在主服务器上为你的密钥保存一份加密拷贝。设置一个口令词组来保护你的备份的安全。 + 我们将会在家服务器上为你的密钥保存一份加密拷贝。设置一个口令词组来保护你的备份的安全。 \n \n为了最大的安全性,此口令词组应当与你的账户密码不同。 设置口令词组 @@ -829,7 +829,7 @@ 断开连接 拒绝 这不是有效的 Matrix 服务器地址 - 无法在此 URL 找到主服务器,请检查 + 无法在此 URL 找到家服务器,请检查 播放 忽略 复制 @@ -837,7 +837,7 @@ 通知 ${app_name} 呼叫失败 无法建立实时连接。 -\n请让你的主服务器的管理员配置一个 TURN 服务器,以便呼叫能够可靠地工作。 +\n请让你的家服务器的管理员配置一个 TURN 服务器,以便呼叫能够可靠地工作。 选择声音设备 电话 扬声器 @@ -928,13 +928,13 @@ 读取受 DRM 保护的媒体 若要继续请接受服务条款。 恢复密钥已保存。 - 你的主服务器上已存在备份 + 你的家服务器上已存在备份 你似乎已在另一个会话中设置密钥备份。你想要将其替换为正在创建的吗? 安全备份 保护加密消息及数据的访问权 设置安全备份 你未使用身份服务器 - 你似乎正在试图连接到另一个主服务器。你想要登出吗? + 你似乎正在试图连接到另一个家服务器。你想要登出吗? 你已经跟上了! 你没有未读消息 你的私聊消息将显示在此处。点击右下角的 + 开始一些对话。 @@ -1008,7 +1008,7 @@ 同意身份服务器 (%s) 服务条款使你可以通过电子邮件地址或电话号码被发现。 启用详细日志。 详细日志将通过在你发送愤怒摇动(RageShake)时提供更多日志来帮助开发人员。即使启用,应用程序也不会记录消息内容或任何其他私人数据。 - 接收你的主服务器条款和条件后请重试。 + 接收你的家服务器条款和条件后请重试。 服务器似乎响应时间太长,这可能是由于连接不良或服务器错误引起的。请稍后再试。 发送附件 打开导航菜单 @@ -1098,7 +1098,7 @@ 输入 Modular Element 或你想使用的服务器地址 输入你想使用的服务器的地址 载入页面时出错:%1$s (%2$d) - 应用无法登录到此主服务器。主服务器支持以下登录类型:%1$s。 + 应用无法登录到此家服务器。家服务器支持以下登录类型:%1$s。 \n \n你想要通过网页客户端登录吗? 抱歉,此服务器不接受新账户。 @@ -1155,24 +1155,24 @@ 你的账户尚未创建。是否中止注册过程? 选择 matrix.org 选择 Element Matrix Services - 选择自定义主服务器 + 选择自定义家服务器 请进行人机验证 接受条款以继续 请检查你的电子邮件 我们向 %1$s 发送了电子邮件。 \n请点击其中包含的链接继续账户创建。 输入的验证码不正确。请检查。 - 过时的主服务器 + 过时的家服务器 发送了太多请求。你可以在 %1$d 秒后重试… 使用 Matrix ID 登录 使用 Matrix ID 登录 - 如果你在主服务器上设置了账户,在下方使用你的 Matrix ID(例 @user:domain.com)和密码。 + 如果你在家服务器上设置了账户,在下方使用你的 Matrix ID(例 @user:domain.com)和密码。 Matrix ID 如果你不知道你的密码,返回并重置。 这不是有效的用户标识符。预期格式:\'@user:homeserver.org\' - 无法找到有效的主服务器。请检查你的标识符 + 无法找到有效的家服务器。请检查你的标识符 你已登出 这可能由于多种原因: \n @@ -1184,7 +1184,7 @@ 重新登录 你已登出 登录 - 你的主服务器 (%1$s) 管理员将你从你的账户 %2$s (%3$s) 登出。 + 你的家服务器 (%1$s) 管理员将你从你的账户 %2$s (%3$s) 登出。 登录以恢复仅存储在此设备上的加密密钥。 你需要使用它们在任何设备上阅读所有安全消息。 登录 密码 @@ -1225,8 +1225,8 @@ 不安全 以下其中一项可能会受到威胁: \n -\n - 你的主服务器 -\n - 你验证的用户连接到的主服务器 +\n - 你的家服务器 +\n - 你验证的用户连接到的家服务器 \n - 你或其他用户的网络连接 \n - 你或其他用户的设备 视频。 @@ -1347,7 +1347,7 @@ 确认移除 你确定要移除(删除)此事件吗?注意,如果删除房间名称或话题的更改,更改会被撤销。 附加理由 - 编辑理由 + 删除理由 事件被用户删除,理由:%1$s ${app_name} Android 密钥请求 @@ -1363,7 +1363,7 @@ 以下其中一项可能有风险: \n \n- 你的密码 -\n- 你的主服务器 +\n- 你的家服务器 \n- 此设备或其它设备 \n- 设备使用的网络连接 \n @@ -1396,7 +1396,7 @@ 输入关键字以查找反应。 已读 跳至已读回执 - 事件被房间管理员调整,理由:%1$s + 事件被房间管理员删除,理由:%1$s 密钥已是最新! 保存到优盘或者备份盘 复制到你的个人云存储 @@ -1487,13 +1487,13 @@ 打开 %s 条款 是否从身份服务器 %s 断开? 身份服务器已过期。${app_name} 仅支持 API V2。 - 无法执行此操作。主服务器已过期。 + 无法执行此操作。家服务器已过时。 请先配置身份服务器。 请先在设置中接受身份服务器的条款。 为了你的隐私,${app_name}仅支持发送经过哈希处理的用户电子邮件的和电话号码。 关联失败。 当前与此标识符没有关联。 - 你的主服务器(%1$s)建议使用 %2$s 作为你的身份服务器 + 你的家服务器(%1$s)建议使用 %2$s 作为你的身份服务器 使用 %1$s 或者,你可以输入任何其它身份服务器网址 输入身份服务器 URL @@ -1632,7 +1632,7 @@ \n \n你的消息受加密保护,并且只有你和消息接收者拥有唯一解密密钥。 此处的消息未经端到端加密。 - 此主服务器正在运行旧版本。 请让你的主服务器管理员升级。 你可以继续,但某些功能可能无法正常工作。 + 此家服务器正在运行旧版本。 请让你的家服务器管理员升级。 你可以继续,但某些功能可能无法正常工作。 你将此房间设为仅邀请。 %1$s 仅发出此邀请。 在加密房间显示完整历史 @@ -1690,7 +1690,7 @@ 一些字符不被允许 请提供一个房间地址 此地址已被使用 - 若房间仅用于与你的主服务器上的内部团队协作,则你可以启用此选项。此选项之后无法更改。 + 若房间仅用于与你的家服务器上的内部团队协作,则你可以启用此选项。此选项之后无法更改。 阻止任何不属于%s的人加入此房间 隐藏高级 显示高级 @@ -1726,7 +1726,7 @@ 发布此地址 添加本地地址 此房间没有本地地址 - 为此房间设置地址以便用户通过你的主服务器(%1$s)找到此房间 + 为此房间设置地址以便用户通过你的家服务器(%1$s)找到此房间 本地地址 新的发布的地址(例如 #alias:server) 尚无其它已发布地址。 @@ -1858,8 +1858,8 @@ 有些房间可能被隐藏,因为其为私有房间,你需要得到邀请。 \n你没有权限添加房间。 此空间没有房间 - 请联系你的主服务器管理员以取得进一步资讯 - 看来你的主服务器尚未支持空间 + 请联系你的家服务器管理员以取得进一步资讯 + 看来你的家服务器尚未支持空间 想要使用实验功能? \n你可以将现有的空间添加到其它空间中。 管理房间和空间 @@ -1882,7 +1882,7 @@ 你认识的 %d 个人已加入 此别名当前无法被访问。 -\n请稍后再试,或询问房间管理员你身份有权访问。 +\n请稍后再试,或询问房间管理员你是否有权访问。 依然加入 加入空间 创建空间 @@ -1954,7 +1954,7 @@ 发送原始大小的视频 限制未知。 - 你的主服务器能接受大小最大为 %s 的附件(文件、媒体等)。 + 你的家服务器能接受大小最大为 %s 的附件(文件、媒体等)。 服务器文件上传限制 此文件过大,无法上传。 搜索名称 @@ -2028,7 +2028,7 @@ 查看和管理这个空间的地址。 空间地址 升级到推荐的房间版本 - 这个房间运行房间版本 %s,此主服务器已将其标记为不稳定。 + 这个房间运行房间版本 %s,此家服务器已将其标记为不稳定。 你需要权限才能升级房间 自动更新空间父级 自动邀请用户 @@ -2048,7 +2048,7 @@ 通过比较表情符号来验证 使用此设备扫描 使用其它设备扫码或切换并使用本设备扫码 - 主服务器 API 网址 + 家服务器API URL 缺少权限 要执行此操作,请从系统设置中授予相机权限。 缺少执行此操作的某些权限,请从系统设置中授予权限。 @@ -2200,8 +2200,8 @@ 离开 离线 在线 - 选择主服务器 - 无法访问 URL %s 上的主服务器。请检查你的链接或手动选择一个主服务器。 + 选择家服务器 + 无法访问 URL %s 上的家服务器。请检查你的链接或手动选择一个家服务器。 侦听通知 需要至少 %1$s 个选项 @@ -2252,7 +2252,7 @@ 第三方库 你的身份服务器政策 我们记录任何账户数据或绘制任何账户数据的画像 - 你的主服务器政策 + 你的家服务器政策 ${app_name} 政策 你可以随时在设置中关闭它 我们与第三方共享信息 @@ -2419,7 +2419,7 @@ 选择保存你的对话的位置,给予你控制权和独立性。通过 Matrix 连接。 安全且独立的通信,为你提供和在家中面对面对话同样等级的隐私。 安全传送消息。 - 向主服务器注册端点token失败: + 向家服务器注册端点token失败: \n%1$s 消息列有助于使你的对话保持话题并易于跟踪。%s 创建消息列将刷新应用程序。对于某些账户,这可能需要更长的时间。 重启应用以使更改生效。 @@ -2444,7 +2444,7 @@ 无法启用生物特征识别。 生物特征识别被禁用,因为最近添加了新的生物特征识别方法。 你可以在“设置”中再次启用它。 - 主服务器不接收仅有数字的用户名。 + 家服务器不接收仅有数字的用户名。 发送你的第一条消息邀请%s聊天 加密配置错误 此聊天中的消息会端到端加密。 @@ -2504,9 +2504,9 @@ \n \n注意,此操作会重启应用并可能需要一些时间。 自动播放动画图片 - 端点成功注册到主服务器。 + 端点成功注册到家服务器。 端点注册 - 你的主服务器当前不支持消息列,因此此功能可能不可靠。某些消息列的消息可能无法可靠地使用。 %s 你仍然要启用消息列吗? + 你的家服务器当前不支持消息列,因此此功能可能不可靠。某些消息列的消息可能无法可靠地使用。 %s 你仍然要启用消息列吗? Threads接近Beta了 🎉 来自消息列 实用提示:长按消息并使用“%s”。 @@ -2544,7 +2544,7 @@ %1$s 和 %2$s 电子邮件未验证,请检查你的收件箱 无法加载地图 -\n此主服务器可能没有设置好显示地图。 +\n此家服务器可能没有设置显示地图。 打开设置 全部聊天 为获得最佳安全性,请验证你的会话,并从任何你不认识或不再使用的会话登出。 @@ -2712,7 +2712,7 @@ 选择“显示QR码” 转到设置 -> 安全和隐私 在你的其它设备上打开应用程序 - 主服务器不支持QR码登录。 + 家服务器不支持QR码登录。 登录已在另一台设备上取消。 该QR码无效。 另一台设备必须登录。 @@ -2822,4 +2822,28 @@ 结束了投票。 你的访问令牌提供对你账户的完全访问权限。勿与任何人分享它。 访问令牌 + 继续重设 + %1$s更改了其显示名称为%2$s + 账户 + 是的,停止 + 无法连接家服务器。若仍登出,此设备将不会从设备列表擦除,你或许想用另一个客户端移除它。 + 无论如何都要登出 + 你的家服务器还不支持列出消息列。 + 空间%1$s的头像 + 房间%1$s的头像 + 用户%1$s的用户资料图片 + 可接受的使用政策 + 正在等待用户加入${app_name} + 正从安全密钥或短语验证…… + 投票历史 + 应用已更新 + 验证你的身份以访问加密消息并向他人证明你的身份。 + 发送至和发送自此会话的消息带有警告标签,直至此用户信任此会话。 + 更新你的通知偏好时出错。请再试一次。 + 你的账户详细信息于%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 2b0a6c2d5b..8c51b44395 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 @@ -1301,9 +1301,9 @@ 確認移除 您確定要刪除此事件嗎?注意,如果您刪除聊天室名稱或主題的變更事件,該變更將被取消。 包含理由 - 修改原因 + 刪除原因 使用者刪除事件,理由:%1$s - 聊天室管理員管理了事件,理由:%1$s + 聊天室管理員刪除了事件,理由:%1$s 金鑰已為最新! ${app_name} Android 金鑰請求 @@ -2889,4 +2889,13 @@ 可接受的使用政策 繼續重設 加密版本 + %1$s 變更了他們的顯示名稱為 %2$s + 使用者 %1$s 的個人資料照片 + 聊天室 %1$s 的大頭貼 + 聊天空間 %1$s 的大頭貼 + 最新更新改善了安全訊息傳遞。請重新驗證您的裝置。 + 在該使用者信任該工作階段之前,發送到該工作階段與從該工作階段傳送的訊息都帶有警告標籤。 + 應用程式已更新 + 無法連線至家伺服器。若您仍要登出,此裝置將不會從您的裝置清單中移除,您可能需要使用其他客戶端來移除。 + 仍要登出 \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index ee7d2fea12..9f842a5741 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -1729,6 +1729,8 @@ "Public" "Anyone will be able to join this room" "The room has been created, but some invitations have not been sent for the following reason:\n\n%s" + Unable to find profiles for the Matrix IDs listed below. Would you like to start a chat anyway?\n\n%s + Start chat anyway "An error occurred getting trust info" "An error occurred getting keys backup data" @@ -2744,6 +2746,8 @@ Invitations sent to %1$s and %2$d more We could not invite users. Please check the users you want to invite and try again. + Unable to find profiles for the Matrix IDs listed below. Would you like to invite them anyway?\n\n%s + Invite anyway Scan a QR code Share my code diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 420b24ab5a..b9e9a71d75 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -17,7 +17,7 @@ buildscript { } } dependencies { - classpath "io.realm:realm-gradle-plugin:10.15.1" + classpath "io.realm:realm-gradle-plugin:10.16.0" } } @@ -63,7 +63,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.6.2\"" + buildConfigField "String", "SDK_VERSION", "\"1.6.3\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" @@ -216,7 +216,7 @@ dependencies { implementation libs.google.phonenumber - rustCryptoImplementation("org.matrix.rustcomponents:crypto-android:0.3.9") + rustCryptoImplementation("org.matrix.rustcomponents:crypto-android:0.3.10") // rustCryptoApi project(":library:rustCrypto") testImplementation libs.tests.junit diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt index bb5618b816..61bd1b42ea 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt @@ -64,9 +64,15 @@ class DeactivateAccountTest : InstrumentedTest { // Test the error assertTrue( + "Unexpected deactivated error $throwable", throwable is Failure.ServerError && - throwable.error.code == MatrixError.M_USER_DEACTIVATED && - throwable.error.message == "This account has been deactivated" + ( + (throwable.error.code == MatrixError.M_USER_DEACTIVATED && + throwable.error.message == "This account has been deactivated") || + // Workaround for a breaking change on synapse to fix CI + // https://github.com/matrix-org/synapse/issues/15747 + throwable.error.code == MatrixError.M_FORBIDDEN + ) ) // Try to create an account with the deactivate account user id, it will fail (M_USER_IN_USE) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt index 4053d1c1c4..983e00b9ea 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt @@ -99,6 +99,23 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig: } } } + + @OptIn(ExperimentalCoroutinesApi::class) + internal fun runLongCryptoTest(context: Context, cryptoConfig: MXCryptoConfig? = null, autoSignoutOnClose: Boolean = true, block: suspend CoroutineScope.(CryptoTestHelper, CommonTestHelper) -> Unit) { + val testHelper = CommonTestHelper(context, cryptoConfig) + val cryptoTestHelper = CryptoTestHelper(testHelper) + return runTest(dispatchTimeoutMs = TestConstants.timeOutMillis * 4) { + try { + withContext(Dispatchers.Default) { + block(cryptoTestHelper, testHelper) + } + } finally { + if (autoSignoutOnClose) { + testHelper.cleanUpOpenedSessions() + } + } + } + } } internal val matrix: TestMatrix diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/RoomShieldTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/RoomShieldTest.kt new file mode 100644 index 0000000000..8e2284d588 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/RoomShieldTest.kt @@ -0,0 +1,133 @@ +/* + * 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.crypto + +import android.util.Log +import androidx.lifecycle.Observer +import androidx.test.filters.LargeTest +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters +import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.SessionTestParams +import org.matrix.android.sdk.common.TestConstants +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +@LargeTest +class RoomShieldTest : InstrumentedTest { + + @Test + fun testShieldNoVerification() = CommonTestHelper.runCryptoTest(context()) { cryptoTestHelper, _ -> + val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val roomId = testData.roomId + + cryptoTestHelper.initializeCrossSigning(testData.firstSession) + cryptoTestHelper.initializeCrossSigning(testData.secondSession!!) + + // Test are flaky unless I use liveData observer on main thread + // Just calling getRoomSummary() with retryWithBackOff keeps an outdated version of the value + testData.firstSession.assertRoomShieldIs(roomId, RoomEncryptionTrustLevel.Default) + testData.secondSession!!.assertRoomShieldIs(roomId, RoomEncryptionTrustLevel.Default) + } + + @Test + fun testShieldInOneOne() = CommonTestHelper.runLongCryptoTest(context()) { cryptoTestHelper, testHelper -> + val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val roomId = testData.roomId + + Log.v("#E2E TEST", "Initialize cross signing...") + cryptoTestHelper.initializeCrossSigning(testData.firstSession) + cryptoTestHelper.initializeCrossSigning(testData.secondSession!!) + Log.v("#E2E TEST", "... Initialized.") + + // let alive and bob verify + Log.v("#E2E TEST", "Alice and Bob verify each others...") + cryptoTestHelper.verifySASCrossSign(testData.firstSession, testData.secondSession!!, testData.roomId) + + // Add a new session for bob + // This session will be unverified for now + + Log.v("#E2E TEST", "Log in a new bob device...") + val bobSecondSession = testHelper.logIntoAccount(testData.secondSession!!.myUserId, SessionTestParams(true)) + + Log.v("#E2E TEST", "Bob session logged in ${bobSecondSession.myUserId.take(6)}") + + Log.v("#E2E TEST", "Assert room shields...") + testData.firstSession.assertRoomShieldIs(roomId, RoomEncryptionTrustLevel.Warning) + // in 1:1 we ignore our own status + testData.secondSession!!.assertRoomShieldIs(roomId, RoomEncryptionTrustLevel.Trusted) + + // Adding another user should make bob consider his devices now and see same shield as alice + Log.v("#E2E TEST", "Create Sam account") + val samSession = testHelper.createAccount(TestConstants.USER_SAM, SessionTestParams(withInitialSync = true)) + + // Let alice invite sam + Log.v("#E2E TEST", "Let alice invite sam") + testData.firstSession.getRoom(roomId)!!.membershipService().invite(samSession.myUserId) + testHelper.waitForAndAcceptInviteInRoom(samSession, roomId) + + Log.v("#E2E TEST", "Assert room shields...") + testData.firstSession.assertRoomShieldIs(roomId, RoomEncryptionTrustLevel.Warning) + testData.secondSession!!.assertRoomShieldIs(roomId, RoomEncryptionTrustLevel.Warning) + + // Now let's bob verify his session + + Log.v("#E2E TEST", "Bob verifies his new session") + cryptoTestHelper.verifyNewSession(testData.secondSession!!, bobSecondSession) + + testData.firstSession.assertRoomShieldIs(roomId, RoomEncryptionTrustLevel.Trusted) + testData.secondSession!!.assertRoomShieldIs(roomId, RoomEncryptionTrustLevel.Trusted) + } + + @OptIn(DelicateCoroutinesApi::class) + private suspend fun Session.assertRoomShieldIs(roomId: String, state: RoomEncryptionTrustLevel?) { + val lock = CountDownLatch(1) + val roomLiveData = withContext(Dispatchers.Main) { + roomService().getRoomSummaryLive(roomId) + } + val observer = object : Observer> { + override fun onChanged(value: Optional) { + Log.v("#E2E TEST ${this@assertRoomShieldIs.myUserId.take(6)}", "Shield Update ${value.getOrNull()?.roomEncryptionTrustLevel}") + if (value.getOrNull()?.roomEncryptionTrustLevel == state) { + lock.countDown() + roomLiveData.removeObserver(this) + } + } + } + GlobalScope.launch(Dispatchers.Main) { roomLiveData.observeForever(observer) } + + lock.await(40_000, TimeUnit.MILLISECONDS) + } +} diff --git a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt index 3090bb805e..e020946484 100644 --- a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -49,7 +49,6 @@ import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.WorkManagerProvider import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.JsonCanonicalizer import org.matrix.android.sdk.internal.util.logLimit import org.matrix.android.sdk.internal.worker.WorkerParamsFactory @@ -66,7 +65,6 @@ internal class DefaultCrossSigningService @Inject constructor( private val deviceListManager: DeviceListManager, private val initializeCrossSigningTask: InitializeCrossSigningTask, private val uploadSignaturesTask: UploadSignaturesTask, - private val taskExecutor: TaskExecutor, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val cryptoCoroutineScope: CoroutineScope, private val workManagerProvider: WorkManagerProvider, @@ -612,9 +610,7 @@ internal class DefaultCrossSigningService @Inject constructor( withContext(coroutineDispatchers.crypto) { // This device should be yours val device = cryptoStore.getUserDevice(myUserId, deviceId) - if (device == null) { - throw IllegalArgumentException("This device [$deviceId] is not known, or not yours") - } + ?: throw IllegalArgumentException("This device [$deviceId] is not known, or not yours") val myKeys = getUserCrossSigningKeys(myUserId) ?: throw Throwable("CrossSigning is not setup for this account") diff --git a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt index ce6e046de5..80f37a6c57 100644 --- a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt +++ b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt @@ -31,7 +31,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerif import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import org.matrix.android.sdk.internal.SessionManager -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntity import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntityFields @@ -40,10 +40,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntity import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields import org.matrix.android.sdk.internal.database.awaitTransaction -import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity -import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity -import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.CryptoDatabase import org.matrix.android.sdk.internal.di.SessionDatabase @@ -83,35 +80,54 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses @Inject lateinit var myUserId: String @Inject lateinit var crossSigningKeysMapper: CrossSigningKeysMapper @Inject lateinit var updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository + @Inject lateinit var cryptoSessionInfoProvider: CryptoSessionInfoProvider // @Inject lateinit var roomSummaryUpdater: RoomSummaryUpdater - @Inject lateinit var cryptoStore: IMXCryptoStore +// @Inject lateinit var cryptoStore: IMXCryptoStore override fun injectWith(injector: SessionComponent) { injector.inject(this) } override suspend fun doSafeWork(params: Params): Result { - val userList = params.filename + val sId = myUserId.take(5) + Timber.v("## CrossSigning - UpdateTrustWorker started..") + val workerParams = params.filename ?.let { updateTrustWorkerDataRepository.getParam(it) } - ?.userIds - ?: params.updatedUserIds.orEmpty() + ?: return Result.success().also { + Timber.w("## CrossSigning - UpdateTrustWorker failed to get params") + cleanup(params) + } + + Timber.v("## CrossSigning [$sId]- UpdateTrustWorker userIds:${workerParams.userIds.logLimit()}, roomIds:${workerParams.roomIds.orEmpty().logLimit()}") + val userList = workerParams.userIds // List should not be empty, but let's avoid go further in case of empty list if (userList.isNotEmpty()) { // Unfortunately we don't have much info on what did exactly changed (is it the cross signing keys of that user, // or a new device?) So we check all again :/ - Timber.v("## CrossSigning - Updating trust for users: ${userList.logLimit()}") + Timber.v("## CrossSigning [$sId]- Updating trust for users: ${userList.logLimit()}") updateTrust(userList) } + val roomsToCheck = workerParams.roomIds ?: cryptoSessionInfoProvider.getRoomsWhereUsersAreParticipating(userList) + Timber.v("## CrossSigning [$sId]- UpdateTrustWorker roomShield to check:${roomsToCheck.logLimit()}") + var myCrossSigningInfo: MXCrossSigningInfo? + Realm.getInstance(cryptoRealmConfiguration).use { realm -> + myCrossSigningInfo = getCrossSigningInfo(realm, myUserId) + } + // So Cross Signing keys trust is updated, device trust is updated + // We can now update room shields? in the session DB? + updateRoomShieldInSummaries(roomsToCheck, myCrossSigningInfo) + cleanup(params) return Result.success() } private suspend fun updateTrust(userListParam: List) { + val sId = myUserId.take(5) var userList = userListParam - var myCrossSigningInfo: MXCrossSigningInfo? = null + var myCrossSigningInfo: MXCrossSigningInfo? // First we check that the users MSK are trusted by mine // After that we check the trust chain for each devices of each users @@ -123,7 +139,7 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses var myTrustResult: UserTrustResult? = null if (userList.contains(myUserId)) { - Timber.d("## CrossSigning - Clear all trust as a change on my user was detected") + Timber.d("## CrossSigning [$sId]- Clear all trust as a change on my user was detected") // i am in the list.. but i don't know exactly the delta of change :/ // If it's my cross signing keys we should refresh all trust // do it anyway ? @@ -153,7 +169,7 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses myUserId -> myTrustResult else -> { crossSigningService.checkOtherMSKTrusted(myCrossSigningInfo, entry.value).also { - Timber.v("## CrossSigning - user:${entry.key} result:$it") + Timber.v("## CrossSigning [$sId]- user:${entry.key} result:$it") } } } @@ -163,12 +179,12 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses // i have all the new trusts, update DB trusts.forEach { val verified = it.value?.isVerified() == true - Timber.v("[$myUserId] ## CrossSigning - Updating user trust: ${it.key} to $verified") + Timber.v("[$myUserId] ## CrossSigning [$sId]- Updating user trust: ${it.key} to $verified") updateCrossSigningKeysTrust(cryptoRealm, it.key, verified) } // Ok so now we have to check device trust for all these users.. - Timber.v("## CrossSigning - Updating devices cross trust users: ${trusts.keys.logLimit()}") + Timber.v("## CrossSigning [$sId]- Updating devices cross trust users: ${trusts.keys.logLimit()}") trusts.keys.forEach { userId -> val devicesEntities = cryptoRealm.where() .equalTo(UserEntityFields.USER_ID, userId) @@ -184,9 +200,9 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses // Update trust if needed devicesEntities?.forEach { device -> val crossSignedVerified = trustMap?.get(device)?.isCrossSignedVerified() - Timber.v("## CrossSigning - Trust for ${device.userId}|${device.deviceId} : cross verified: ${trustMap?.get(device)}") + Timber.v("## CrossSigning [$sId]- Trust for ${device.userId}|${device.deviceId} : cross verified: ${trustMap?.get(device)}") if (device.trustLevelEntity?.crossSignedVerified != crossSignedVerified) { - Timber.d("## CrossSigning - Trust change detected for ${device.userId}|${device.deviceId} : cross verified: $crossSignedVerified") + Timber.d("## CrossSigning [$sId]- Trust change detected for ${device.userId}|${device.deviceId} : cross verified: $crossSignedVerified") // need to save val trustEntity = device.trustLevelEntity if (trustEntity == null) { @@ -197,50 +213,46 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses } else { trustEntity.crossSignedVerified = crossSignedVerified } + } else { + Timber.v("## CrossSigning [$sId]- Trust unchanged for ${device.userId}|${device.deviceId} : cross verified: $crossSignedVerified") } } } } - - // So Cross Signing keys trust is updated, device trust is updated - // We can now update room shields? in the session DB? - updateTrustStep2(userList, myCrossSigningInfo) } - private suspend fun updateTrustStep2(userList: List, myCrossSigningInfo: MXCrossSigningInfo?) { - Timber.d("## CrossSigning - Updating shields for impacted rooms...") + private suspend fun updateRoomShieldInSummaries(roomList: List, myCrossSigningInfo: MXCrossSigningInfo?) { + val sId = myUserId.take(5) + Timber.d("## CrossSigning [$sId]- Updating shields for impacted rooms... ${roomList.logLimit()}") awaitTransaction(sessionRealmConfiguration) { sessionRealm -> Timber.d("## CrossSigning - Updating shields for impacted rooms - in transaction") Realm.getInstance(cryptoRealmConfiguration).use { cryptoRealm -> - sessionRealm.where(RoomMemberSummaryEntity::class.java) - .`in`(RoomMemberSummaryEntityFields.USER_ID, userList.toTypedArray()) - .distinct(RoomMemberSummaryEntityFields.ROOM_ID) - .findAll() - .map { it.roomId } - .also { Timber.d("## CrossSigning - ... impacted rooms ${it.logLimit()}") } - .forEach { roomId -> - RoomSummaryEntity.where(sessionRealm, roomId) - .equalTo(RoomSummaryEntityFields.IS_ENCRYPTED, true) - .findFirst() - ?.let { roomSummary -> - Timber.v("## CrossSigning - Check shield state for room $roomId") - val allActiveRoomMembers = RoomMemberHelper(sessionRealm, roomId).getActiveRoomMemberIds() - try { - val updatedTrust = computeRoomShield( - myCrossSigningInfo, - cryptoRealm, - allActiveRoomMembers, - roomSummary - ) - if (roomSummary.roomEncryptionTrustLevel != updatedTrust) { - Timber.d("## CrossSigning - Shield change detected for $roomId -> $updatedTrust") - roomSummary.roomEncryptionTrustLevel = updatedTrust - } - } catch (failure: Throwable) { - Timber.e(failure) - } + roomList.forEach { roomId -> + Timber.v("## CrossSigning [$sId]- Checking room $roomId") + RoomSummaryEntity.where(sessionRealm, roomId) +// .equalTo(RoomSummaryEntityFields.IS_ENCRYPTED, true) + .findFirst() + ?.let { roomSummary -> + Timber.v("## CrossSigning [$sId]- Check shield state for room $roomId") + val allActiveRoomMembers = RoomMemberHelper(sessionRealm, roomId).getActiveRoomMemberIds() + try { + val updatedTrust = computeRoomShield( + myCrossSigningInfo, + cryptoRealm, + allActiveRoomMembers, + roomSummary + ) + if (roomSummary.roomEncryptionTrustLevel != updatedTrust) { + Timber.d("## CrossSigning [$sId]- Shield change detected for $roomId -> $updatedTrust") + roomSummary.roomEncryptionTrustLevel = updatedTrust + } else { + Timber.v("## CrossSigning [$sId]- Shield unchanged for $roomId -> $updatedTrust") } - } + } catch (failure: Throwable) { + Timber.e(failure) + } + } + } } } Timber.d("## CrossSigning - Updating shields for impacted rooms - END") diff --git a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/session/sync/handler/ShieldSummaryUpdater.kt b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/session/sync/handler/ShieldSummaryUpdater.kt new file mode 100644 index 0000000000..bcc078b550 --- /dev/null +++ b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/session/sync/handler/ShieldSummaryUpdater.kt @@ -0,0 +1,56 @@ +/* + * 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.sync.handler + +import androidx.work.BackoffPolicy +import androidx.work.ExistingWorkPolicy +import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker +import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorkerDataRepository +import org.matrix.android.sdk.internal.di.SessionId +import org.matrix.android.sdk.internal.di.WorkManagerProvider +import org.matrix.android.sdk.internal.session.SessionScope +import org.matrix.android.sdk.internal.util.logLimit +import org.matrix.android.sdk.internal.worker.WorkerParamsFactory +import timber.log.Timber +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +@SessionScope +internal class ShieldSummaryUpdater @Inject constructor( + @SessionId private val sessionId: String, + private val workManagerProvider: WorkManagerProvider, + private val updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository, +) { + + fun refreshShieldsForRoomIds(roomIds: Set) { + Timber.d("## CrossSigning - checkAffectedRoomShields for roomIds: ${roomIds.logLimit()}") + val workerParams = UpdateTrustWorker.Params( + sessionId = sessionId, + filename = updateTrustWorkerDataRepository.createParam(emptyList(), roomIds = roomIds.toList()) + ) + val workerData = WorkerParamsFactory.toData(workerParams) + + val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder() + .setInputData(workerData) + .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) + .build() + + workManagerProvider.workManager + .beginUniqueWork("TRUST_UPDATE_QUEUE", ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest) + .enqueue() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt index 6122aae972..bbf65288cc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt @@ -20,7 +20,6 @@ import timber.log.Timber sealed class Action { object Notify : Action() - object DoNotNotify : Action() data class Sound(val sound: String = ACTION_OBJECT_VALUE_VALUE_DEFAULT) : Action() data class Highlight(val highlight: Boolean) : Action() @@ -72,7 +71,6 @@ fun List.toJson(): List { return map { action -> when (action) { is Action.Notify -> Action.ACTION_NOTIFY - is Action.DoNotNotify -> Action.ACTION_DONT_NOTIFY is Action.Sound -> { mapOf( Action.ACTION_OBJECT_SET_TWEAK_KEY to Action.ACTION_OBJECT_SET_TWEAK_VALUE_SOUND, @@ -95,7 +93,7 @@ fun PushRule.getActions(): List { actions.forEach { actionStrOrObj -> when (actionStrOrObj) { Action.ACTION_NOTIFY -> Action.Notify - Action.ACTION_DONT_NOTIFY -> Action.DoNotNotify + Action.ACTION_DONT_NOTIFY -> return@forEach is Map<*, *> -> { when (actionStrOrObj[Action.ACTION_OBJECT_SET_TWEAK_KEY]) { Action.ACTION_OBJECT_SET_TWEAK_VALUE_SOUND -> { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushRule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushRule.kt index a11ffc0a98..31dbd8dd2e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushRule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushRule.kt @@ -121,8 +121,6 @@ data class PushRule( if (notify) { mutableActions.add(Action.ACTION_NOTIFY) - } else { - mutableActions.add(Action.ACTION_DONT_NOTIFY) } return copy(actions = mutableActions) @@ -140,5 +138,5 @@ data class PushRule( * * @return true if the rule should not play sound */ - fun shouldNotNotify() = actions.contains(Action.ACTION_DONT_NOTIFY) + fun shouldNotNotify() = actions.isEmpty() || actions.contains(Action.ACTION_DONT_NOTIFY) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt index e26ca2f86a..086d741acc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt @@ -19,19 +19,19 @@ package org.matrix.android.sdk.internal.crypto import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.internal.database.mapper.EventMapper import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntityFields -import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity -import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereType import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.query.process import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.util.fetchCopied -import org.matrix.android.sdk.internal.util.logLimit import timber.log.Timber import javax.inject.Inject @@ -89,14 +89,30 @@ internal class CryptoSessionInfoProvider @Inject constructor( } fun getRoomsWhereUsersAreParticipating(userList: List): List { + if (userList.contains(myUserId)) { + // just take all + val roomIds: List? = null + monarchy.doWithRealm { sessionRealm -> + RoomSummaryEntity.where(sessionRealm) + .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships()) + .findAll() + .map { it.roomId } + } + return roomIds.orEmpty() + } var roomIds: List? = null monarchy.doWithRealm { sessionRealm -> - roomIds = sessionRealm.where(RoomMemberSummaryEntity::class.java) - .`in`(RoomMemberSummaryEntityFields.USER_ID, userList.toTypedArray()) - .distinct(RoomMemberSummaryEntityFields.ROOM_ID) + roomIds = RoomSummaryEntity.where(sessionRealm) + .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships()) .findAll() + .filter { it.otherMemberIds.any { it in userList } } .map { it.roomId } - .also { Timber.d("## CrossSigning - ... impacted rooms ${it.logLimit()}") } +// roomIds = sessionRealm.where(RoomMemberSummaryEntity::class.java) +// .`in`(RoomMemberSummaryEntityFields.USER_ID, userList.toTypedArray()) +// .distinct(RoomMemberSummaryEntityFields.ROOM_ID) +// .findAll() +// .map { it.roomId } +// .also { Timber.d("## CrossSigning - ... impacted rooms ${it.logLimit()}") } } return roomIds.orEmpty() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorkerDataRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorkerDataRepository.kt index 0878a9f765..d9207d05be 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorkerDataRepository.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorkerDataRepository.kt @@ -28,7 +28,10 @@ import javax.inject.Inject @JsonClass(generateAdapter = true) internal data class UpdateTrustWorkerData( @Json(name = "userIds") - val userIds: List + val userIds: List, + // When we just need to refresh the room shield (no change on user keys, but a membership change) + @Json(name = "roomIds") + val roomIds: List? = null ) internal class UpdateTrustWorkerDataRepository @Inject constructor( @@ -38,12 +41,12 @@ internal class UpdateTrustWorkerDataRepository @Inject constructor( private val jsonAdapter = MoshiProvider.providesMoshi().adapter(UpdateTrustWorkerData::class.java) // Return the path of the created file - fun createParam(userIds: List): String { + fun createParam(userIds: List, roomIds: List? = null): String { val filename = "${UUID.randomUUID()}.json" workingDirectory.mkdirs() val file = File(workingDirectory, filename) - UpdateTrustWorkerData(userIds = userIds) + UpdateTrustWorkerData(userIds = userIds, roomIds = roomIds) .let { jsonAdapter.toJson(it) } .let { file.writeText(it) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt index 42b069f8fa..8707c24383 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt @@ -63,7 +63,7 @@ internal fun RoomNotificationState.toRoomPushRule(roomId: String): RoomPushRule? pattern = roomId ) val rule = PushRule( - actions = listOf(Action.DoNotNotify).toJson(), + actions = emptyList().toJson(), enabled = true, ruleId = roomId, conditions = listOf(condition) @@ -81,7 +81,7 @@ internal fun RoomNotificationState.toRoomPushRule(roomId: String): RoomPushRule? internal fun RoomPushRule.toRoomNotificationState(): RoomNotificationState { return if (rule.enabled) { val actions = rule.getActions() - if (actions.contains(Action.DoNotNotify)) { + if (actions.isEmpty()) { if (kind == RuleSetKey.OVERRIDE) { RoomNotificationState.MUTE } else { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index b251bc24f0..cbb75398c4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -168,6 +168,9 @@ internal class RoomSummaryUpdater @Inject constructor( val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel()?.aliases .orEmpty() roomSummaryEntity.updateAliases(roomAliases) + + val wasEncrypted = roomSummaryEntity.isEncrypted + roomSummaryEntity.isEncrypted = encryptionEvent != null roomSummaryEntity.e2eAlgorithm = ContentMapper.map(encryptionEvent?.content) @@ -197,17 +200,13 @@ internal class RoomSummaryUpdater @Inject constructor( // better to use what we know roomSummaryEntity.joinedMembersCount = otherRoomMembers.size + 1 } - if (roomSummaryEntity.isEncrypted && otherRoomMembers.isNotEmpty()) { - if (aggregator == null) { - // Do it now - // mmm maybe we could only refresh shield instead of checking trust also? - // XXX why doing this here? we don't show shield anymore and it will be refreshed - // by the sdk - // crossSigningService.checkTrustAndAffectedRoomShields(otherRoomMembers) - } else { - // Schedule it - aggregator.userIdsForCheckingTrustAndAffectedRoomShields.addAll(otherRoomMembers) - } + } + + if (roomSummaryEntity.isEncrypted) { + if (!wasEncrypted || updateMembers || roomSummaryEntity.roomEncryptionTrustLevel == null) { + // trigger a shield update + // if users add devices/keys or signatures the device list manager will trigger a refresh + aggregator?.roomsWithMembershipChangesForShieldUpdate?.add(roomId) } } } @@ -410,7 +409,7 @@ internal class RoomSummaryUpdater @Inject constructor( val relatedSpaces = lookupMap.keys .filter { it.roomType == RoomType.SPACE } .filter { - dmRoom.otherMemberIds.toList().intersect(it.otherMemberIds.toList()).isNotEmpty() + dmRoom.otherMemberIds.toList().intersect(it.otherMemberIds.toSet()).isNotEmpty() } .map { it.roomId } .distinct() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt index af05e08da3..4532a8d418 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt @@ -29,7 +29,8 @@ internal class SyncResponsePostTreatmentAggregator { val userIdsToFetch = mutableSetOf() // Set of users to call `crossSigningService.checkTrustAndAffectedRoomShields` once per sync - val userIdsForCheckingTrustAndAffectedRoomShields = mutableSetOf() + + val roomsWithMembershipChangesForShieldUpdate = mutableSetOf() // For the crypto store val cryptoStoreAggregator = CryptoStoreAggregator() 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 948a0a2501..3700bbf46f 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 @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.sync.handler import androidx.work.BackoffPolicy import androidx.work.ExistingWorkPolicy import org.matrix.android.sdk.api.MatrixPatterns -import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorkerDataRepository import org.matrix.android.sdk.internal.di.SessionId @@ -39,16 +38,16 @@ internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor( private val directChatsHelper: DirectChatsHelper, private val ephemeralTemporaryStore: RoomSyncEphemeralTemporaryStore, private val updateUserAccountDataTask: UpdateUserAccountDataTask, - private val crossSigningService: CrossSigningService, private val updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository, private val workManagerProvider: WorkManagerProvider, + private val roomShieldSummaryUpdater: ShieldSummaryUpdater, @SessionId private val sessionId: String, ) { suspend fun handle(aggregator: SyncResponsePostTreatmentAggregator) { cleanupEphemeralFiles(aggregator.ephemeralFilesToDelete) updateDirectUserIds(aggregator.directChatsToCheck) fetchAndUpdateUsers(aggregator.userIdsToFetch) - handleUserIdsForCheckingTrustAndAffectedRoomShields(aggregator.userIdsForCheckingTrustAndAffectedRoomShields) + handleRefreshRoomShieldsForRooms(aggregator.roomsWithMembershipChangesForShieldUpdate) } private fun cleanupEphemeralFiles(ephemeralFilesToDelete: List) { @@ -105,8 +104,8 @@ internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor( .enqueue() } - private suspend fun handleUserIdsForCheckingTrustAndAffectedRoomShields(userIdsWithDeviceUpdate: Collection) { - if (userIdsWithDeviceUpdate.isEmpty()) return - crossSigningService.checkTrustAndAffectedRoomShields(userIdsWithDeviceUpdate.toList()) + private fun handleRefreshRoomShieldsForRooms(roomIds: Set) { + if (roomIds.isEmpty()) return + roomShieldSummaryUpdater.refreshShieldsForRoomIds(roomIds) } } diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/Device.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/Device.kt index 7f2b1232fe..4cb329175b 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/Device.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/Device.kt @@ -187,8 +187,7 @@ internal class Device @AssistedInject constructor( locallyVerified = innerDevice.locallyTrusted ), isBlocked = innerDevice.isBlocked, - // TODO - firstTimeSeenLocalTs = null + firstTimeSeenLocalTs = innerDevice.firstTimeSeenTs.toLong() ) } } diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/network/OutgoingRequestsProcessor.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/network/OutgoingRequestsProcessor.kt index 77fd9b3ea3..9e0301f487 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/network/OutgoingRequestsProcessor.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/network/OutgoingRequestsProcessor.kt @@ -27,10 +27,10 @@ import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.internal.crypto.ComputeShieldForGroupUseCase import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider import org.matrix.android.sdk.internal.crypto.OlmMachine import org.matrix.android.sdk.internal.session.SessionScope +import org.matrix.android.sdk.internal.session.sync.handler.ShieldSummaryUpdater import org.matrix.rustcomponents.sdk.crypto.Request import org.matrix.rustcomponents.sdk.crypto.RequestType import timber.log.Timber @@ -43,7 +43,7 @@ internal class OutgoingRequestsProcessor @Inject constructor( private val requestSender: RequestSender, private val coroutineScope: CoroutineScope, private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, - private val computeShieldForGroup: ComputeShieldForGroupUseCase, + private val shieldSummaryUpdater: ShieldSummaryUpdater, private val matrixConfiguration: MatrixConfiguration, private val coroutineDispatchers: MatrixCoroutineDispatchers, ) { @@ -137,7 +137,7 @@ internal class OutgoingRequestsProcessor @Inject constructor( return try { val response = requestSender.queryKeys(request) olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_QUERY, response) - coroutineScope.updateShields(olmMachine, request.users) + shieldSummaryUpdater.refreshShieldsForRoomsWithMembers(request.users) coroutineScope.markMessageVerificationStatesAsDirty(request.users) true } catch (throwable: Throwable) { @@ -146,18 +146,6 @@ internal class OutgoingRequestsProcessor @Inject constructor( } } - private fun CoroutineScope.updateShields(olmMachine: OlmMachine, userIds: List) = launch(coroutineDispatchers.computation) { - cryptoSessionInfoProvider.getRoomsWhereUsersAreParticipating(userIds).forEach { roomId -> - if (cryptoSessionInfoProvider.isRoomEncrypted(roomId)) { - val userGroup = cryptoSessionInfoProvider.getUserListForShieldComputation(roomId) - val shield = computeShieldForGroup(olmMachine, userGroup) - cryptoSessionInfoProvider.updateShieldForRoom(roomId, shield) - } else { - cryptoSessionInfoProvider.updateShieldForRoom(roomId, null) - } - } - } - private fun CoroutineScope.markMessageVerificationStatesAsDirty(userIds: List) = launch(coroutineDispatchers.computation) { cryptoSessionInfoProvider.markMessageVerificationStateAsDirty(userIds) } diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/session/sync/handler/ShieldSummaryUpdater.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/session/sync/handler/ShieldSummaryUpdater.kt new file mode 100644 index 0000000000..9f77d7003e --- /dev/null +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/session/sync/handler/ShieldSummaryUpdater.kt @@ -0,0 +1,60 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.sync.handler + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers +import org.matrix.android.sdk.internal.crypto.ComputeShieldForGroupUseCase +import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider +import org.matrix.android.sdk.internal.crypto.OlmMachine +import org.matrix.android.sdk.internal.session.SessionScope +import javax.inject.Inject + +@SessionScope +internal class ShieldSummaryUpdater @Inject constructor( + private val olmMachine: dagger.Lazy, + private val coroutineScope: CoroutineScope, + private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, + private val computeShieldForGroup: ComputeShieldForGroupUseCase, +) { + + fun refreshShieldsForRoomsWithMembers(userIds: List) { + coroutineScope.launch(coroutineDispatchers.computation) { + cryptoSessionInfoProvider.getRoomsWhereUsersAreParticipating(userIds).forEach { roomId -> + if (cryptoSessionInfoProvider.isRoomEncrypted(roomId)) { + val userGroup = cryptoSessionInfoProvider.getUserListForShieldComputation(roomId) + val shield = computeShieldForGroup(olmMachine.get(), userGroup) + cryptoSessionInfoProvider.updateShieldForRoom(roomId, shield) + } else { + cryptoSessionInfoProvider.updateShieldForRoom(roomId, null) + } + } + } + } + + fun refreshShieldsForRoomIds(roomIds: Set) { + coroutineScope.launch(coroutineDispatchers.computation) { + roomIds.forEach { roomId -> + val userGroup = cryptoSessionInfoProvider.getUserListForShieldComputation(roomId) + val shield = computeShieldForGroup(olmMachine.get(), userGroup) + cryptoSessionInfoProvider.updateShieldForRoom(roomId, shield) + } + } + } +} diff --git a/vector-app/build.gradle b/vector-app/build.gradle index 9a2506391e..7a09213bb9 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -37,7 +37,7 @@ ext.versionMinor = 6 // 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 = 2 +ext.versionPatch = 3 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' diff --git a/vector/build.gradle b/vector/build.gradle index fb9c63ad2a..dab8b8bdeb 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -299,6 +299,7 @@ dependencies { testImplementation libs.tests.kluent testImplementation libs.mockk.mockk testImplementation libs.androidx.coreTesting + testImplementation libs.tests.robolectric // Plant Timber tree for test testImplementation libs.tests.timberJunitRule testImplementation libs.airbnb.mavericksTesting diff --git a/vector/src/main/java/im/vector/app/core/di/ConfigurationModule.kt b/vector/src/main/java/im/vector/app/core/di/ConfigurationModule.kt index 7a1f7f2292..7c8dea3b7d 100644 --- a/vector/src/main/java/im/vector/app/core/di/ConfigurationModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/ConfigurationModule.kt @@ -77,8 +77,8 @@ object ConfigurationModule { fun providesCryptoConfig() = CryptoConfig( fallbackKeySharingStrategy = when (Config.KEY_SHARING_STRATEGY) { KeySharingStrategy.WhenSendingEvent -> OutboundSessionKeySharingStrategy.WhenSendingEvent - KeySharingStrategy.WhenEnteringRoom -> OutboundSessionKeySharingStrategy.WhenSendingEvent - KeySharingStrategy.WhenTyping -> OutboundSessionKeySharingStrategy.WhenSendingEvent + KeySharingStrategy.WhenEnteringRoom -> OutboundSessionKeySharingStrategy.WhenEnteringRoom + KeySharingStrategy.WhenTyping -> OutboundSessionKeySharingStrategy.WhenTyping } ) diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt index fbddf815c6..faf4374d93 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt @@ -48,6 +48,7 @@ import im.vector.app.features.qrcode.QrCodeScannerEvents import im.vector.app.features.qrcode.QrCodeScannerFragment import im.vector.app.features.qrcode.QrCodeScannerViewModel import im.vector.app.features.qrcode.QrScannerArgs +import im.vector.app.features.userdirectory.PendingSelection import im.vector.app.features.userdirectory.UserListFragment import im.vector.app.features.userdirectory.UserListFragmentArgs import im.vector.app.features.userdirectory.UserListSharedAction @@ -160,7 +161,19 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { } private fun handleOnMenuItemSubmitClick(action: UserListSharedAction.OnMenuItemSubmitClick) { - viewModel.handle(CreateDirectRoomAction.PrepareRoomWithSelectedUsers(action.selections)) + val unknownUsers = action.selections.filter { it is PendingSelection.UserPendingSelection && it.isUnknownUser } + if (unknownUsers.isEmpty()) { + viewModel.handle(CreateDirectRoomAction.PrepareRoomWithSelectedUsers(action.selections)) + } else { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.dialog_title_confirmation) + .setMessage(getString(R.string.create_room_unknown_users_dialog_content, unknownUsers.joinToString("\n • ", " • ") { it.getMxId() })) + .setPositiveButton(R.string.create_room_unknown_users_dialog_submit) { _, _ -> + viewModel.handle(CreateDirectRoomAction.PrepareRoomWithSelectedUsers(action.selections)) + } + .setNegativeButton(R.string.action_cancel, null) + .show() + } } private fun renderCreateAndInviteState(state: Async) { diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationBottomSheet.kt index 5005ccd12b..e63c149a26 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationBottomSheet.kt @@ -156,7 +156,7 @@ class SelfVerificationBottomSheet : VectorBaseBottomSheetDialogFragment { - dismiss() + dismiss() } is VerificationBottomSheetViewEvents.ConfirmCancel -> { // TODO? applies to self? @@ -229,6 +229,9 @@ class SelfVerificationBottomSheet : VectorBaseBottomSheetDialogFragment state.unknownSessions.invoke()?.let { unknownDevices -> + val uid = PopupAlertManager.REVIEW_LOGIN_UID if (unknownDevices.firstOrNull()?.currentSessionTrust == true) { - val uid = PopupAlertManager.REVIEW_LOGIN_UID alertManager.cancelAlert(uid) val olderUnverified = unknownDevices.filter { !it.isNew } val newest = unknownDevices.firstOrNull { it.isNew }?.deviceInfo @@ -172,6 +174,9 @@ class NewHomeDetailFragment : // In this case we prompt to go to settings to review logins promptToReviewChanges(uid, state, olderUnverified.map { it.deviceInfo }) } + } else { + // cancel as there are not anymore untrusted devices + alertManager.cancelAlert(uid) } } } @@ -278,7 +283,14 @@ class NewHomeDetailFragment : uid = uid, title = getString(R.string.review_unverified_sessions_title), description = getString(R.string.review_unverified_sessions_description), - iconId = R.drawable.ic_shield_warning + iconId = R.drawable.ic_shield_warning, + shouldBeDisplayedIn = { activity -> + // do not show when there is an ongoing verification flow + if (activity is VectorBaseActivity<*>) { + activity.supportFragmentManager.findFragmentByTag(SelfVerificationBottomSheet.TAG) == null && + activity !is QrCodeScannerActivity + } else true + } ).apply { viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer) colorInt = colorProvider.getColorFromAttribute(R.attr.colorPrimary) @@ -351,9 +363,9 @@ class NewHomeDetailFragment : }) } - /* ========================================================================================== - * KeysBackupBanner Listener - * ========================================================================================== */ +/* ========================================================================================== + * KeysBackupBanner Listener + * ========================================================================================== */ override fun onCloseClicked() { serverBackupStatusViewModel.handle(ServerBackupStatusAction.OnBannerClosed) diff --git a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt index 6ba5976eb8..7a28c98a7d 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt @@ -39,7 +39,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.sample 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.Session import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo @@ -116,8 +115,14 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor( session.sessionParams.deviceId != it.deviceId } .filter { info -> - // filter out verified sessions or those which do not support encryption (i.e. without crypto info) - cryptoList.firstOrNull { info.deviceId == it.deviceId }?.isVerified?.not().orFalse() + val matchingDeviceWithKeys = cryptoList.firstOrNull { it.deviceId == info.deviceId } + if (matchingDeviceWithKeys == null) { + // filter out verified sessions or those which do not support encryption (i.e. without crypto info) + false + } else { + // Only report unverified + !matchingDeviceWithKeys.isVerified + } } // filter out ignored devices .filter { shouldShowUnverifiedSessionsAlertUseCase.execute(it.deviceId) } @@ -136,7 +141,7 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor( } .distinctUntilChanged() .execute { async -> - Timber.v("## Detector trigger passed distinct") + Timber.v("## Detector trigger passed distinct ${async.invoke()}") copy( myMatrixItem = session.getUserOrDefault(session.myUserId).toMatrixItem(), unknownSessions = async diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt index 51e1fb06f2..568f4cf9e7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt @@ -40,23 +40,31 @@ import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.html.PillImageSpan import im.vector.app.features.themes.ThemeUtils +import io.element.android.wysiwyg.EditorEditText +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toEveryoneInRoomMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toRoomAliasMatrixItem +import timber.log.Timber class AutoCompleter @AssistedInject constructor( @Assisted val roomId: String, @Assisted val isInThreadTimeline: Boolean, + private val session: Session, private val avatarRenderer: AvatarRenderer, private val commandAutocompletePolicy: CommandAutocompletePolicy, autocompleteCommandPresenterFactory: AutocompleteCommandPresenter.Factory, private val autocompleteMemberPresenterFactory: AutocompleteMemberPresenter.Factory, private val autocompleteRoomPresenter: AutocompleteRoomPresenter, - private val autocompleteEmojiPresenter: AutocompleteEmojiPresenter + private val autocompleteEmojiPresenter: AutocompleteEmojiPresenter, ) { + private val permalinkService: PermalinkService + get() = session.permalinkService() + private lateinit var autocompleteMemberPresenter: AutocompleteMemberPresenter @AssistedFactory @@ -79,6 +87,7 @@ class AutoCompleter @AssistedInject constructor( } private lateinit var glideRequests: GlideRequests + private val autocompletes: MutableSet> = hashSetOf() fun setup(editText: EditText) { this.editText = editText @@ -90,26 +99,41 @@ class AutoCompleter @AssistedInject constructor( setupRooms(backgroundDrawable, editText) } + fun setEnabled(isEnabled: Boolean) = + autocompletes.forEach { + if (!isEnabled) { it.dismissPopup() } + it.setEnabled(isEnabled) + } + fun clear() { this.editText = null autocompleteEmojiPresenter.clear() autocompleteRoomPresenter.clear() autocompleteCommandPresenter.clear() autocompleteMemberPresenter.clear() + autocompletes.forEach { + it.setEnabled(false) + it.dismissPopup() + } + autocompletes.clear() } private fun setupCommands(backgroundDrawable: Drawable, editText: EditText) { - Autocomplete.on(editText) + autocompletes += Autocomplete.on(editText) .with(commandAutocompletePolicy) .with(autocompleteCommandPresenter) .with(ELEVATION_DP) .with(backgroundDrawable) .with(object : AutocompleteCallback { override fun onPopupItemClicked(editable: Editable, item: Command): Boolean { - editable.clear() - editable - .append(item.command) - .append(" ") + if (editText is EditorEditText) { + editText.replaceTextSuggestion(item.command) + } else { + editable.clear() + editable + .append(item.command) + .append(" ") + } return true } @@ -121,24 +145,22 @@ class AutoCompleter @AssistedInject constructor( private fun setupMembers(backgroundDrawable: ColorDrawable, editText: EditText) { autocompleteMemberPresenter = autocompleteMemberPresenterFactory.create(roomId) - Autocomplete.on(editText) + autocompletes += Autocomplete.on(editText) .with(CharPolicy(TRIGGER_AUTO_COMPLETE_MEMBERS, true)) .with(autocompleteMemberPresenter) .with(ELEVATION_DP) .with(backgroundDrawable) .with(object : AutocompleteCallback { override fun onPopupItemClicked(editable: Editable, item: AutocompleteMemberItem): Boolean { - return when (item) { - is AutocompleteMemberItem.Header -> false // do nothing header is not clickable - is AutocompleteMemberItem.RoomMember -> { - insertMatrixItem(editText, editable, TRIGGER_AUTO_COMPLETE_MEMBERS, item.roomMemberSummary.toMatrixItem()) - true - } - is AutocompleteMemberItem.Everyone -> { - insertMatrixItem(editText, editable, TRIGGER_AUTO_COMPLETE_MEMBERS, item.roomSummary.toEveryoneInRoomMatrixItem()) - true - } - } + val matrixItem = when (item) { + is AutocompleteMemberItem.Header -> null // do nothing header is not clickable + is AutocompleteMemberItem.RoomMember -> item.roomMemberSummary.toMatrixItem() + is AutocompleteMemberItem.Everyone -> item.roomSummary.toEveryoneInRoomMatrixItem() + } ?: return false + + insertMatrixItem(editText, editable, TRIGGER_AUTO_COMPLETE_MEMBERS, matrixItem) + + return true } override fun onPopupVisibilityChanged(shown: Boolean) { @@ -148,7 +170,7 @@ class AutoCompleter @AssistedInject constructor( } private fun setupRooms(backgroundDrawable: ColorDrawable, editText: EditText) { - Autocomplete.on(editText) + autocompletes += Autocomplete.on(editText) .with(CharPolicy(TRIGGER_AUTO_COMPLETE_ROOMS, true)) .with(autocompleteRoomPresenter) .with(ELEVATION_DP) @@ -166,7 +188,10 @@ class AutoCompleter @AssistedInject constructor( } private fun setupEmojis(backgroundDrawable: Drawable, editText: EditText) { - Autocomplete.on(editText) + // Rich text editor is not yet supported + if (editText is EditorEditText) return + + autocompletes += Autocomplete.on(editText) .with(CharPolicy(TRIGGER_AUTO_COMPLETE_EMOJIS, false)) .with(autocompleteEmojiPresenter) .with(ELEVATION_DP) @@ -197,7 +222,41 @@ class AutoCompleter @AssistedInject constructor( .build() } - private fun insertMatrixItem(editText: EditText, editable: Editable, firstChar: Char, matrixItem: MatrixItem) { + private fun insertMatrixItem(editText: EditText, editable: Editable, firstChar: Char, matrixItem: MatrixItem) = + if (editText is EditorEditText) { + insertMatrixItemIntoRichTextEditor(editText, matrixItem) + } else { + insertMatrixItemIntoEditable(editText, editable, firstChar, matrixItem) + } + + private fun insertMatrixItemIntoRichTextEditor(editorEditText: EditorEditText, matrixItem: MatrixItem) { + if (matrixItem is MatrixItem.EveryoneInRoomItem) { + editorEditText.replaceTextSuggestion(matrixItem.displayName) + return + } + + val permalink = permalinkService.createPermalink(matrixItem.id) + + if (permalink == null) { + Timber.e(NullPointerException("Cannot autocomplete as permalink is null")) + return + } + + val linkText = when (matrixItem) { + is MatrixItem.RoomAliasItem, + is MatrixItem.RoomItem, + is MatrixItem.SpaceItem -> + matrixItem.id + is MatrixItem.EveryoneInRoomItem, + is MatrixItem.UserItem, + is MatrixItem.EventItem -> + matrixItem.getBestName() + } + + editorEditText.setLinkSuggestion(url = permalink, text = linkText) + } + + private fun insertMatrixItemIntoEditable(editText: EditText, editable: Editable, firstChar: Char, matrixItem: MatrixItem) { // Detect last firstChar and remove it var startIndex = editable.lastIndexOf(firstChar) if (startIndex == -1) { 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 746396d1e1..3793ed18d2 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 @@ -765,6 +765,9 @@ class TimelineViewModel @AssistedInject constructor( return room?.membershipService()?.getRoomMember(userId) } + fun getRoom(roomId: String): RoomSummary? = + session.roomService().getRoomSummary(roomId) + private fun handleComposerFocusChange(action: RoomDetailAction.ComposerFocusChange) { if (room == null) return // Ensure outbound session keys 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 d9459d259a..338a635818 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 @@ -83,6 +83,7 @@ import im.vector.app.features.home.room.detail.TimelineViewModel import im.vector.app.features.home.room.detail.composer.link.SetLinkFragment import im.vector.app.features.home.room.detail.composer.link.SetLinkSharedAction import im.vector.app.features.home.room.detail.composer.link.SetLinkSharedActionViewModel +import im.vector.app.features.home.room.detail.composer.mentions.PillDisplayHandler import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet @@ -100,6 +101,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.util.MatrixItem import reactivecircus.flowbinding.android.view.focusChanges import reactivecircus.flowbinding.android.widget.textChanges @@ -122,11 +124,12 @@ class MessageComposerFragment : VectorBaseFragment(), A @Inject lateinit var session: Session @Inject lateinit var errorTracker: ErrorTracker + private val permalinkService: PermalinkService + get() = session.permalinkService() + private val roomId: String get() = withState(timelineViewModel) { it.roomId } - private val autoCompleter: AutoCompleter by lazy { - autoCompleterFactory.create(roomId, isThreadTimeLine()) - } + private val autoCompleters: MutableMap = hashMapOf() private val emojiPopup: EmojiPopup by lifecycleAwareLazy { createEmojiPopup() @@ -261,9 +264,8 @@ class MessageComposerFragment : VectorBaseFragment(), A override fun onDestroyView() { super.onDestroyView() - if (!vectorPreferences.isRichTextEditorEnabled()) { - autoCompleter.clear() - } + autoCompleters.values.forEach(AutoCompleter::clear) + autoCompleters.clear() messageComposerViewModel.endAllVoiceActions() } @@ -274,7 +276,12 @@ class MessageComposerFragment : VectorBaseFragment(), A (composer as? View)?.isVisible = messageComposerState.isComposerVisible composer.sendButton.isInvisible = !messageComposerState.isSendButtonVisible - (composer as? RichTextComposerLayout)?.isTextFormattingEnabled = attachmentState.isTextFormattingEnabled + (composer as? RichTextComposerLayout)?.also { + val isTextFormattingEnabled = attachmentState.isTextFormattingEnabled + it.isTextFormattingEnabled = isTextFormattingEnabled + autoCompleters[it.richTextEditText]?.setEnabled(isTextFormattingEnabled) + autoCompleters[it.plainTextEditText]?.setEnabled(!isTextFormattingEnabled) + } } private fun setupBottomSheet() { @@ -315,8 +322,11 @@ class MessageComposerFragment : VectorBaseFragment(), A val composerEditText = composer.editText composerEditText.setHint(R.string.room_message_placeholder) - if (!vectorPreferences.isRichTextEditorEnabled()) { - autoCompleter.setup(composerEditText) + (composer as? RichTextComposerLayout)?.let { + initAutoCompleter(it.richTextEditText) + initAutoCompleter(it.plainTextEditText) + } ?: run { + initAutoCompleter(composer.editText) } observerUserTyping() @@ -404,6 +414,21 @@ class MessageComposerFragment : VectorBaseFragment(), A SetLinkFragment.show(isTextSupported, initialLink, childFragmentManager) } } + (composer as? RichTextComposerLayout)?.pillDisplayHandler = PillDisplayHandler( + roomId = roomId, + getRoom = timelineViewModel::getRoom, + getMember = timelineViewModel::getMember, + ) { matrixItem: MatrixItem -> + PillImageSpan(glideRequests, avatarRenderer, requireContext(), matrixItem) + } + } + + private fun initAutoCompleter(editText: EditText) { + if (autoCompleters.containsKey(editText)) return + + autoCompleters[editText] = + autoCompleterFactory.create(roomId, isThreadTimeLine()) + .also { it.setup(editText) } } private fun sendTextMessage(text: CharSequence, formattedText: String? = null) { @@ -435,12 +460,12 @@ class MessageComposerFragment : VectorBaseFragment(), A } private fun renderRegularMode(content: CharSequence) { - autoCompleter.exitSpecialMode() + autoCompleters.values.forEach(AutoCompleter::exitSpecialMode) composer.renderComposerMode(MessageComposerMode.Normal(content)) } private fun renderSpecialMode(mode: MessageComposerMode.Special) { - autoCompleter.enterSpecialMode() + autoCompleters.values.forEach(AutoCompleter::enterSpecialMode) composer.renderComposerMode(mode) } @@ -771,30 +796,37 @@ class MessageComposerFragment : VectorBaseFragment(), A } else { val roomMember = timelineViewModel.getMember(userId) val displayName = sanitizeDisplayName(roomMember?.displayName ?: userId) - val pill = buildSpannedString { - append(displayName) - setSpan( - PillImageSpan( - glideRequests, - avatarRenderer, - requireContext(), - MatrixItem.UserItem(userId, displayName, roomMember?.avatarUrl) - ) - .also { it.bind(composer.editText) }, - 0, - displayName.length, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE - ) - append(if (startToCompose) ": " else " ") - } - if (startToCompose) { - if (displayName.startsWith("/")) { + if ((composer as? RichTextComposerLayout)?.isTextFormattingEnabled == true) { + // Rich text editor is enabled so we need to use its APIs + permalinkService.createPermalink(userId)?.let { url -> + (composer as RichTextComposerLayout).insertMention(url, displayName) + composer.editText.append(" ") + } + } else { + val pill = buildSpannedString { + append(displayName) + setSpan( + PillImageSpan( + glideRequests, + avatarRenderer, + requireContext(), + MatrixItem.UserItem(userId, displayName, roomMember?.avatarUrl), + ) + .also { it.bind(composer.editText) }, + 0, + displayName.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + append(if (startToCompose) ": " else " ") + } + if (startToCompose && displayName.startsWith("/")) { // Ensure displayName will not be interpreted as a Slash command composer.editText.append("\\") } - composer.editText.append(pill) - } else { - composer.editText.text?.insert(composer.editText.selectionStart, pill) + // Always use EditText.getText().insert for adding pills as TextView.append doesn't appear + // to upgrade to BufferType.Spannable as hinted at in the docs: + // https://developer.android.com/reference/android/widget/TextView#append(java.lang.CharSequence) + composer.editText.text.insert(composer.editText.selectionStart, pill) } } focusComposerAndShowKeyboard() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt index 49e8f0cdc6..48163a43bf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt @@ -49,7 +49,11 @@ import im.vector.app.core.utils.DimensionConverter import im.vector.app.databinding.ComposerRichTextLayoutBinding import im.vector.app.databinding.ViewRichTextMenuButtonBinding import im.vector.app.features.home.room.detail.composer.images.UriContentListener +import im.vector.app.features.home.room.detail.composer.mentions.PillDisplayHandler import io.element.android.wysiwyg.EditorEditText +import io.element.android.wysiwyg.display.KeywordDisplayHandler +import io.element.android.wysiwyg.display.LinkDisplayHandler +import io.element.android.wysiwyg.display.TextDisplay import io.element.android.wysiwyg.utils.RustErrorCollector import io.element.android.wysiwyg.view.models.InlineFormat import io.element.android.wysiwyg.view.models.LinkAction @@ -88,7 +92,7 @@ internal class RichTextComposerLayout @JvmOverloads constructor( override val text: Editable? get() = editText.text override val formattedText: String? - get() = (editText as? EditorEditText)?.getHtmlOutput() + get() = (editText as? EditorEditText)?.getContentAsMessageHtml() override val editText: EditText get() = if (isTextFormattingEnabled) { views.richTextComposerEditText @@ -102,6 +106,13 @@ internal class RichTextComposerLayout @JvmOverloads constructor( override val attachmentButton: ImageButton get() = views.attachmentButton + val richTextEditText: EditText get() = + views.richTextComposerEditText + val plainTextEditText: EditText get() = + views.plainTextComposerEditText + + var pillDisplayHandler: PillDisplayHandler? = null + // Border of the EditText private val borderShapeDrawable: MaterialShapeDrawable by lazy { MaterialShapeDrawable().apply { @@ -227,6 +238,16 @@ internal class RichTextComposerLayout @JvmOverloads constructor( views.composerEditTextOuterBorder.background = borderShapeDrawable setupRichTextMenu() + views.richTextComposerEditText.linkDisplayHandler = LinkDisplayHandler { text, url -> + pillDisplayHandler?.resolveLinkDisplay(text, url) ?: TextDisplay.Plain + } + views.richTextComposerEditText.keywordDisplayHandler = object : KeywordDisplayHandler { + override val keywords: List + get() = pillDisplayHandler?.keywords.orEmpty() + + override fun resolveKeywordDisplay(text: String): TextDisplay = + pillDisplayHandler?.resolveKeywordDisplay(text) ?: TextDisplay.Plain + } updateTextFieldBorder(isFullScreen) } @@ -284,6 +305,10 @@ internal class RichTextComposerLayout @JvmOverloads constructor( fun removeLink() = views.richTextComposerEditText.removeLink() + // Update the API to insertMention when available + fun insertMention(url: String, displayText: String) = + views.richTextComposerEditText.insertLink(url, displayText) + @SuppressLint("ClickableViewAccessibility") private fun disallowParentInterceptTouchEvent(view: View) { view.setOnTouchListener { v, event -> diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/mentions/PillDisplayHandler.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/mentions/PillDisplayHandler.kt new file mode 100644 index 0000000000..c2b71ea15b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/mentions/PillDisplayHandler.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.composer.mentions + +import android.text.style.ReplacementSpan +import io.element.android.wysiwyg.display.KeywordDisplayHandler +import io.element.android.wysiwyg.display.LinkDisplayHandler +import io.element.android.wysiwyg.display.TextDisplay +import org.matrix.android.sdk.api.session.permalinks.PermalinkData +import org.matrix.android.sdk.api.session.permalinks.PermalinkParser +import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.util.MatrixItem +import org.matrix.android.sdk.api.util.toEveryoneInRoomMatrixItem +import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.api.util.toRoomAliasMatrixItem + +/** + * A rich text editor [LinkDisplayHandler] and [KeywordDisplayHandler] + * that helps with replacing user and room links with pills. + */ +internal class PillDisplayHandler( + private val roomId: String, + private val getRoom: (roomId: String) -> RoomSummary?, + private val getMember: (userId: String) -> RoomMemberSummary?, + private val replacementSpanFactory: (matrixItem: MatrixItem) -> ReplacementSpan, +) : LinkDisplayHandler, KeywordDisplayHandler { + override fun resolveLinkDisplay(text: String, url: String): TextDisplay { + val matrixItem = when (val permalink = PermalinkParser.parse(url)) { + is PermalinkData.UserLink -> { + val userId = permalink.userId + when (val roomMember = getMember(userId)) { + null -> MatrixItem.UserItem(userId, userId, null) + else -> roomMember.toMatrixItem() + } + } + is PermalinkData.RoomLink -> { + val roomId = permalink.roomIdOrAlias + val room = getRoom(roomId) + when { + room == null -> MatrixItem.RoomItem(roomId, roomId, null) + text == MatrixItem.NOTIFY_EVERYONE -> room.toEveryoneInRoomMatrixItem() + permalink.isRoomAlias -> room.toRoomAliasMatrixItem() + else -> room.toMatrixItem() + } + } + else -> + return TextDisplay.Plain + } + val replacement = replacementSpanFactory.invoke(matrixItem) + return TextDisplay.Custom(customSpan = replacement) + } + + override val keywords: List + get() = listOf(MatrixItem.NOTIFY_EVERYONE) + + override fun resolveKeywordDisplay(text: String): TextDisplay = + when (text) { + MatrixItem.NOTIFY_EVERYONE -> { + val matrixItem = getRoom(roomId)?.toEveryoneInRoomMatrixItem() + ?: MatrixItem.EveryoneInRoomItem(roomId) + TextDisplay.Custom(replacementSpanFactory.invoke(matrixItem)) + } + else -> TextDisplay.Plain + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 5f8883fdfe..dd52c05265 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -286,7 +286,10 @@ class MessageItemFactory @Inject constructor( } else { null } - val pollContent = pollStartEvent?.root?.getClearContent()?.toModel() + + val editedContent = pollStartEvent?.annotations?.editSummary?.latestEdit?.getClearContent()?.toModel()?.newContent + val latestContent = editedContent ?: pollStartEvent?.root?.getClearContent() + val pollContent = latestContent?.toModel() return if (pollContent == null) { val title = stringProvider.getString(R.string.message_reply_to_ended_poll_preview).toEpoxyCharSequence() diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt index 7f514d2ad2..c000efaef2 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt @@ -37,6 +37,7 @@ import im.vector.app.core.utils.onPermissionDeniedSnackbar import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.core.utils.toast import im.vector.app.features.contactsbook.ContactsBookFragment +import im.vector.app.features.userdirectory.PendingSelection import im.vector.app.features.userdirectory.UserListFragment import im.vector.app.features.userdirectory.UserListFragmentArgs import im.vector.app.features.userdirectory.UserListSharedAction @@ -94,7 +95,19 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { } private fun handleOnMenuItemSubmitClick(action: UserListSharedAction.OnMenuItemSubmitClick) { - viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.selections)) + val unknownUsers = action.selections.filter { it is PendingSelection.UserPendingSelection && it.isUnknownUser } + if (unknownUsers.isEmpty()) { + viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.selections)) + } else { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.dialog_title_confirmation) + .setMessage(getString(R.string.invite_unknown_users_dialog_content, unknownUsers.joinToString("\n • ", " • ") { it.getMxId() })) + .setPositiveButton(R.string.invite_unknown_users_dialog_submit) { _, _ -> + viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.selections)) + } + .setNegativeButton(R.string.action_cancel, null) + .show() + } } private fun openPhoneBook() { diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index b38805f05a..3496ced21c 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -242,8 +242,8 @@ class DefaultNavigator @Inject constructor( } if (context is AppCompatActivity) { - SelfVerificationBottomSheet.forTransaction(tx.transactionId) - .show(context.supportFragmentManager, "VERIF") + SelfVerificationBottomSheet.forTransaction(tx.transactionId) + .show(context.supportFragmentManager, "VERIF") } } } @@ -258,7 +258,7 @@ class DefaultNavigator @Inject constructor( ) if (context is AppCompatActivity) { context.supportFragmentManager.commitTransaction(allowStateLoss = true) { - add(SelfVerificationBottomSheet.forTransaction(request.transactionId), "VERIF") + add(SelfVerificationBottomSheet.forTransaction(request.transactionId), SelfVerificationBottomSheet.TAG) } } } @@ -266,25 +266,10 @@ class DefaultNavigator @Inject constructor( override fun requestSelfSessionVerification(context: Context) { coroutineScope.launch { - // TODO - // val session = sessionHolder.getSafeActiveSession() ?: return@launch -// val otherSessions = session.cryptoService() -// .getCryptoDeviceInfoList(session.myUserId) -// .filter { it.deviceId != session.sessionParams.deviceId } -// .map { it.deviceId } if (context is AppCompatActivity) { context.supportFragmentManager.commitTransaction(allowStateLoss = true) { - add(SelfVerificationBottomSheet.verifyOwnUntrustedDevice(), "VERIF") + add(SelfVerificationBottomSheet.verifyOwnUntrustedDevice(), SelfVerificationBottomSheet.TAG) } -// if (otherSessions.isNotEmpty()) { -// val pr = session.cryptoService().verificationService().requestSelfKeyVerification( -// supportedVerificationMethodsProvider.provide()) -// VerificationBottomSheet.forSelfVerification(session, pr.transactionId) -// .show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG) -// } else { -// VerificationBottomSheet.forSelfVerification(session) -// .show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG) -// } } } } @@ -293,7 +278,7 @@ class DefaultNavigator @Inject constructor( // val session = sessionHolder.getSafeActiveSession() ?: return coroutineScope.launch(Dispatchers.Main) { SelfVerificationBottomSheet.forTransaction(transactionId) - .show(fragmentActivity.supportFragmentManager, "SELF_VERIF_TAG") + .show(fragmentActivity.supportFragmentManager, SelfVerificationBottomSheet.TAG) } } diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationAction.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationAction.kt index 19c7fcea7d..ffa833b7ce 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationAction.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationAction.kt @@ -30,7 +30,6 @@ fun List.toNotificationAction(): NotificationAction { forEach { action -> when (action) { is Action.Notify -> shouldNotify = true - is Action.DoNotNotify -> shouldNotify = false is Action.Highlight -> highlight = action.highlight is Action.Sound -> sound = action.sound } diff --git a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt index 02275c933e..c59b2c7216 100644 --- a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt +++ b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt @@ -164,7 +164,7 @@ class PopupAlertManager @Inject constructor( next = alertQueue.maxByOrNull { it.priority } // If next alert with highest priority is higher than the current one, we should display it // and add the current one to queue again. - if (next != null && next.priority > currentAlerter?.priority ?: Int.MIN_VALUE) { + if (next != null && next.priority > (currentAlerter?.priority ?: Int.MIN_VALUE)) { alertQueue.remove(next) currentAlerter?.also { alertQueue.add(0, it) diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/StandardActions.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/StandardActions.kt index b19b5bcdd6..46aaa38ae5 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/StandardActions.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/StandardActions.kt @@ -26,6 +26,6 @@ sealed class StandardActions( object NotifyRingSound : StandardActions(actions = listOf(Action.Notify, Action.Sound(sound = Action.ACTION_OBJECT_VALUE_VALUE_RING))) object Highlight : StandardActions(actions = listOf(Action.Notify, Action.Highlight(highlight = true))) object HighlightDefaultSound : StandardActions(actions = listOf(Action.Notify, Action.Highlight(highlight = true), Action.Sound())) - object DontNotify : StandardActions(actions = listOf(Action.DoNotNotify)) + object DontNotify : StandardActions(actions = emptyList()) object Disabled : StandardActions(actions = null) } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/PendingSelection.kt b/vector/src/main/java/im/vector/app/features/userdirectory/PendingSelection.kt index 643aa30995..7838b76452 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/PendingSelection.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/PendingSelection.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.toMatrixItem sealed class PendingSelection { - data class UserPendingSelection(val user: User) : PendingSelection() + data class UserPendingSelection(val user: User, var isUnknownUser: Boolean = false) : PendingSelection() data class ThreePidPendingSelection(val threePid: ThreePid) : PendingSelection() fun getBestName(): String { diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index ae28ff020d..96875d73a5 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -260,6 +260,7 @@ class UserListViewModel @AssistedInject constructor( .sortedBy { it.toMatrixItem().firstLetterOfDisplayName() } val userProfile = if (MatrixPatterns.isUserId(search)) { val user = tryOrNull { session.profileService().getProfileAsUser(search) } + setState { copy(unknownUserId = search.takeIf { user == null }) } User( userId = search, displayName = user?.displayName, @@ -284,6 +285,9 @@ class UserListViewModel @AssistedInject constructor( (action.pendingSelection is PendingSelection.UserPendingSelection && state.pendingSelections.last() is PendingSelection.UserPendingSelection) if (canSelectUser) { + if (action.pendingSelection is PendingSelection.UserPendingSelection) { + action.pendingSelection.isUnknownUser = action.pendingSelection.getMxId() == state.unknownUserId + } val selections = state.pendingSelections.toggle(action.pendingSelection, singleElement = state.singleSelection) setState { copy(pendingSelections = selections) } } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt index ec932a2a57..27fa11bf54 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt @@ -30,6 +30,7 @@ data class UserListViewState( val matchingEmail: Async = Uninitialized, val filteredMappedContacts: List = emptyList(), val pendingSelections: Set = emptySet(), + val unknownUserId: String? = null, val searchTerm: String = "", val singleSelection: Boolean, val single3pidSelection: Boolean, diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/composer/mentions/PillDisplayHandlerTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/composer/mentions/PillDisplayHandlerTest.kt new file mode 100644 index 0000000000..57c7aee420 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/composer/mentions/PillDisplayHandlerTest.kt @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.composer.mentions + +import android.graphics.Canvas +import android.graphics.Paint +import android.text.style.ReplacementSpan +import io.element.android.wysiwyg.display.TextDisplay +import io.mockk.every +import io.mockk.mockk +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.util.MatrixItem +import org.matrix.android.sdk.api.util.MatrixItem.Companion.NOTIFY_EVERYONE +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +internal class PillDisplayHandlerTest { + private val mockGetMember = mockk<(userId: String) -> RoomMemberSummary?>() + private val mockGetRoom = mockk<(roomId: String) -> RoomSummary?>() + private val fakeReplacementSpanFactory = { matrixItem: MatrixItem -> MatrixItemHolderSpan(matrixItem) } + + private companion object { + const val ROOM_ID = "!thisroom:matrix.org" + const val NON_MATRIX_URL = "https://example.com" + const val UNKNOWN_MATRIX_ROOM_ID = "!unknown:matrix.org" + const val UNKNOWN_MATRIX_ROOM_URL = "https://matrix.to/#/$UNKNOWN_MATRIX_ROOM_ID" + const val KNOWN_MATRIX_ROOM_ID = "!known:matrix.org" + const val KNOWN_MATRIX_ROOM_URL = "https://matrix.to/#/$KNOWN_MATRIX_ROOM_ID" + const val KNOWN_MATRIX_ROOM_AVATAR = "https://example.com/avatar.png" + const val KNOWN_MATRIX_ROOM_NAME = "known room" + const val UNKNOWN_MATRIX_USER_ID = "@unknown:matrix.org" + const val UNKNOWN_MATRIX_USER_URL = "https://matrix.to/#/$UNKNOWN_MATRIX_USER_ID" + const val KNOWN_MATRIX_USER_ID = "@known:matrix.org" + const val KNOWN_MATRIX_USER_URL = "https://matrix.to/#/$KNOWN_MATRIX_USER_ID" + const val KNOWN_MATRIX_USER_AVATAR = "https://example.com/avatar.png" + const val KNOWN_MATRIX_USER_NAME = "known user" + const val CUSTOM_DOMAIN_MATRIX_ROOM_URL = "https://customdomain/#/room/$KNOWN_MATRIX_ROOM_ID" + const val CUSTOM_DOMAIN_MATRIX_USER_URL = "https://customdomain.com/#/user/$KNOWN_MATRIX_USER_ID" + const val KNOWN_MATRIX_ROOM_ALIAS = "#known-alias:matrix.org" + const val KNOWN_MATRIX_ROOM_ALIAS_URL = "https://matrix.to/#/$KNOWN_MATRIX_ROOM_ALIAS" + } + + @Before + fun setUp() { + every { mockGetMember(UNKNOWN_MATRIX_USER_ID) } returns null + every { mockGetMember(KNOWN_MATRIX_USER_ID) } returns createFakeRoomMember(KNOWN_MATRIX_USER_NAME, KNOWN_MATRIX_USER_ID, KNOWN_MATRIX_USER_AVATAR) + every { mockGetRoom(UNKNOWN_MATRIX_ROOM_ID) } returns null + every { mockGetRoom(KNOWN_MATRIX_ROOM_ID) } returns createFakeRoom(KNOWN_MATRIX_ROOM_ID, KNOWN_MATRIX_ROOM_NAME, KNOWN_MATRIX_ROOM_AVATAR) + every { mockGetRoom(ROOM_ID) } returns createFakeRoom(ROOM_ID, KNOWN_MATRIX_ROOM_NAME, KNOWN_MATRIX_ROOM_AVATAR) + every { mockGetRoom(KNOWN_MATRIX_ROOM_ALIAS) } returns createFakeRoomWithAlias( + KNOWN_MATRIX_ROOM_ALIAS, + KNOWN_MATRIX_ROOM_ID, + KNOWN_MATRIX_ROOM_NAME, + KNOWN_MATRIX_ROOM_AVATAR + ) + } + + @Test + fun `when resolve non-matrix link, then it returns plain text`() { + val subject = createSubject() + + val result = subject.resolveLinkDisplay("text", NON_MATRIX_URL) + + assertEquals(TextDisplay.Plain, result) + } + + @Test + fun `when resolve unknown user link, then it returns generic custom pill`() { + val subject = createSubject() + + val matrixItem = subject.resolveLinkDisplay("text", UNKNOWN_MATRIX_USER_URL) + .getMatrixItem() + + assertEquals(MatrixItem.UserItem(UNKNOWN_MATRIX_USER_ID, UNKNOWN_MATRIX_USER_ID, null), matrixItem) + } + + @Test + fun `when resolve known user link, then it returns named custom pill`() { + val subject = createSubject() + + val matrixItem = subject.resolveLinkDisplay("text", KNOWN_MATRIX_USER_URL) + .getMatrixItem() + + assertEquals(MatrixItem.UserItem(KNOWN_MATRIX_USER_ID, KNOWN_MATRIX_USER_NAME, KNOWN_MATRIX_USER_AVATAR), matrixItem) + } + + @Test + fun `when resolve unknown room link, then it returns generic custom pill`() { + val subject = createSubject() + + val matrixItem = subject.resolveLinkDisplay("text", UNKNOWN_MATRIX_ROOM_URL) + .getMatrixItem() + + assertEquals(MatrixItem.RoomItem(UNKNOWN_MATRIX_ROOM_ID, UNKNOWN_MATRIX_ROOM_ID, null), matrixItem) + } + + @Test + fun `when resolve known room link, then it returns named custom pill`() { + val subject = createSubject() + + val matrixItem = subject.resolveLinkDisplay("text", KNOWN_MATRIX_ROOM_URL) + .getMatrixItem() + + assertEquals(MatrixItem.RoomItem(KNOWN_MATRIX_ROOM_ID, KNOWN_MATRIX_ROOM_NAME, KNOWN_MATRIX_ROOM_AVATAR), matrixItem) + } + + @Test + fun `when resolve @room link, then it returns room notification custom pill`() { + val subject = createSubject() + + val matrixItem = subject.resolveLinkDisplay("@room", KNOWN_MATRIX_ROOM_URL) + .getMatrixItem() + + assertEquals(MatrixItem.EveryoneInRoomItem(KNOWN_MATRIX_ROOM_ID, NOTIFY_EVERYONE, KNOWN_MATRIX_ROOM_AVATAR, KNOWN_MATRIX_ROOM_NAME), matrixItem) + } + + @Test + fun `when resolve @room keyword, then it returns room notification custom pill`() { + val subject = createSubject() + + val matrixItem = subject.resolveKeywordDisplay("@room") + .getMatrixItem() + + assertEquals(MatrixItem.EveryoneInRoomItem(ROOM_ID, NOTIFY_EVERYONE, KNOWN_MATRIX_ROOM_AVATAR, KNOWN_MATRIX_ROOM_NAME), matrixItem) + } + + @Test + fun `given cannot get current room, when resolve @room keyword, then it returns room notification custom pill`() { + val subject = createSubject() + every { mockGetRoom(ROOM_ID) } returns null + + val matrixItem = subject.resolveKeywordDisplay("@room") + .getMatrixItem() + + assertEquals(MatrixItem.EveryoneInRoomItem(ROOM_ID, NOTIFY_EVERYONE, null, null), matrixItem) + } + + @Test + fun `when get keywords, then it returns @room`() { + val subject = createSubject() + + assertEquals(listOf("@room"), subject.keywords) + } + + @Test + fun `when resolve known user for custom domain link, then it returns named custom pill`() { + val subject = createSubject() + + val matrixItem = subject.resolveLinkDisplay("text", CUSTOM_DOMAIN_MATRIX_USER_URL) + .getMatrixItem() + + assertEquals(MatrixItem.UserItem(KNOWN_MATRIX_USER_ID, KNOWN_MATRIX_USER_NAME, KNOWN_MATRIX_USER_AVATAR), matrixItem) + } + + @Test + fun `when resolve known room for custom domain link, then it returns named custom pill`() { + val subject = createSubject() + + val matrixItem = subject.resolveLinkDisplay("text", CUSTOM_DOMAIN_MATRIX_ROOM_URL) + .getMatrixItem() + + assertEquals(MatrixItem.RoomItem(KNOWN_MATRIX_ROOM_ID, KNOWN_MATRIX_ROOM_NAME, KNOWN_MATRIX_ROOM_AVATAR), matrixItem) + } + + @Test + fun `when resolve known room with alias link, then it returns named custom pill`() { + val subject = createSubject() + + val matrixItem = subject.resolveLinkDisplay("text", KNOWN_MATRIX_ROOM_ALIAS_URL) + .getMatrixItem() + + assertEquals(MatrixItem.RoomAliasItem(KNOWN_MATRIX_ROOM_ALIAS, KNOWN_MATRIX_ROOM_NAME, KNOWN_MATRIX_ROOM_AVATAR), matrixItem) + } + + private fun TextDisplay.getMatrixItem(): MatrixItem? { + val customSpan = this as? TextDisplay.Custom + assertNotNull("The URL did not resolve to a custom link display method", customSpan) + + val matrixItemHolderSpan = customSpan!!.customSpan as MatrixItemHolderSpan + return matrixItemHolderSpan.matrixItem + } + + private fun createSubject(): PillDisplayHandler = PillDisplayHandler( + roomId = ROOM_ID, + getRoom = mockGetRoom, + getMember = mockGetMember, + replacementSpanFactory = fakeReplacementSpanFactory + ) + + private fun createFakeRoomMember(displayName: String, userId: String, avatarUrl: String): RoomMemberSummary = RoomMemberSummary( + membership = Membership.JOIN, + userId = userId, + displayName = displayName, + avatarUrl = avatarUrl, + ) + + private fun createFakeRoom(roomId: String, roomName: String, avatarUrl: String): RoomSummary = RoomSummary( + roomId = roomId, + displayName = roomName, + avatarUrl = avatarUrl, + encryptionEventTs = null, + typingUsers = emptyList(), + isEncrypted = false + ) + + private fun createFakeRoomWithAlias(roomAlias: String, roomId: String, roomName: String, avatarUrl: String): RoomSummary = RoomSummary( + roomId = roomId, + displayName = roomName, + avatarUrl = avatarUrl, + encryptionEventTs = null, + typingUsers = emptyList(), + isEncrypted = false, + canonicalAlias = roomAlias + ) + + data class MatrixItemHolderSpan( + val matrixItem: MatrixItem + ) : ReplacementSpan() { + override fun draw(canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) { + // Do nothing + } + + override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int { + return 0 + } + } +} diff --git a/vector/src/test/java/im/vector/app/features/settings/notifications/usecase/GetPushRulesOnInvalidStateUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/notifications/usecase/GetPushRulesOnInvalidStateUseCaseTest.kt index a434ac93d3..8ab90c18dc 100644 --- a/vector/src/test/java/im/vector/app/features/settings/notifications/usecase/GetPushRulesOnInvalidStateUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/notifications/usecase/GetPushRulesOnInvalidStateUseCaseTest.kt @@ -48,19 +48,19 @@ internal class GetPushRulesOnInvalidStateUseCaseTest { fun `given a list of push rules with children not matching their parent when execute then returns the list of not matching rules`() { // Given val firstActions = listOf(Action.Notify) - val secondActions = listOf(Action.DoNotNotify) + val secondActions = emptyList() givenARuleList( listOf( // first set of related rules givenARuleId(RuleIds.RULE_ID_ONE_TO_ONE_ROOM, true, firstActions), - givenARuleId(RuleIds.RULE_ID_POLL_START_ONE_TO_ONE, true, listOf(Action.DoNotNotify)), // diff + givenARuleId(RuleIds.RULE_ID_POLL_START_ONE_TO_ONE, true, emptyList()), // diff givenARuleId(RuleIds.RULE_ID_POLL_START_ONE_TO_ONE_UNSTABLE, true, emptyList()), // diff givenARuleId(RuleIds.RULE_ID_POLL_END_ONE_TO_ONE, false, listOf(Action.Notify)), // diff givenARuleId(RuleIds.RULE_ID_POLL_END_ONE_TO_ONE_UNSTABLE, true, listOf(Action.Notify)), // second set of related rules givenARuleId(RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS, false, secondActions), givenARuleId(RuleIds.RULE_ID_POLL_START, true, listOf(Action.Notify)), // diff - givenARuleId(RuleIds.RULE_ID_POLL_START_UNSTABLE, false, listOf(Action.DoNotNotify)), + givenARuleId(RuleIds.RULE_ID_POLL_START_UNSTABLE, false, emptyList()), givenARuleId(RuleIds.RULE_ID_POLL_END, false, listOf(Action.Notify)), // diff givenARuleId(RuleIds.RULE_ID_POLL_END_UNSTABLE, true, listOf()), // diff // Another rule diff --git a/vector/src/test/java/im/vector/app/features/settings/notifications/usecase/UpdatePushRulesIfNeededUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/notifications/usecase/UpdatePushRulesIfNeededUseCaseTest.kt index 1f76a7f9a5..77d21c39dc 100644 --- a/vector/src/test/java/im/vector/app/features/settings/notifications/usecase/UpdatePushRulesIfNeededUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/notifications/usecase/UpdatePushRulesIfNeededUseCaseTest.kt @@ -54,12 +54,12 @@ internal class UpdatePushRulesIfNeededUseCaseTest { val firstParentActions = listOf(Action.Notify) val firstParent = givenARuleId(RuleIds.RULE_ID_ONE_TO_ONE_ROOM, firstParentEnabled, firstParentActions) val secondParentEnabled = false - val secondParentActions = listOf(Action.DoNotNotify) + val secondParentActions = emptyList() val secondParent = givenARuleId(RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS, secondParentEnabled, secondParentActions) val rulesOnError = listOf( // first set of related rules firstParent, - givenARuleId(RuleIds.RULE_ID_POLL_START_ONE_TO_ONE, true, listOf(Action.DoNotNotify)), // diff + givenARuleId(RuleIds.RULE_ID_POLL_START_ONE_TO_ONE, true, emptyList()), // diff givenARuleId(RuleIds.RULE_ID_POLL_START_ONE_TO_ONE_UNSTABLE, true, emptyList()), // diff givenARuleId(RuleIds.RULE_ID_POLL_END_ONE_TO_ONE, false, listOf(Action.Notify)), // diff // second set of related rules