diff --git a/.github/workflows/post-pr.yml b/.github/workflows/post-pr.yml index 9cd33143ad..8fe51eb8d5 100644 --- a/.github/workflows/post-pr.yml +++ b/.github/workflows/post-pr.yml @@ -325,5 +325,5 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} hookshot_url: ${{ secrets.ELEMENT_ANDROID_HOOKSHOT_URL }} - text_template: "Post-merge validation of ${{ github.head_ref }} into ${{ github.base_ref }} by ${{ github.event.pull_request.merged_by }} failed: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" - html_template: "Post-merge validation of ${{ github.head_ref }} into ${{ github.base_ref }} by ${{ github.event.pull_request.merged_by }} failed: {{#each job_statuses }}{{#with this }}{{#if completed }}
{{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" + text_template: "Post-merge validation of ${{ github.head_ref }} into ${{ github.base_ref }} by ${{ github.event.pull_request.merged_by.login }} failed: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" + html_template: "Post-merge validation of ${{ github.head_ref }} into ${{ github.base_ref }} by ${{ github.event.pull_request.merged_by.login }} failed: {{#each job_statuses }}{{#with this }}{{#if completed }}
{{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" diff --git a/CHANGES.md b/CHANGES.md index 8e42149545..c8677b1ae4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,44 @@ +Changes in Element v1.4.16 (2022-05-17) +======================================= + +Features ✨ +---------- + - Use key backup before requesting keys + refactor & improvement of key request/forward ([#5494](https://github.com/vector-im/element-android/issues/5494)) + - Screen sharing over WebRTC ([#5911](https://github.com/vector-im/element-android/issues/5911)) + - Allow using the latest user Avatar and name for all messages in the timeline ([#5932](https://github.com/vector-im/element-android/issues/5932)) + - Added themed launch icons for Android 13 ([#5936](https://github.com/vector-im/element-android/issues/5936)) + - Add presence indicator busy and away. ([#6047](https://github.com/vector-im/element-android/issues/6047)) + +Bugfixes 🐛 +---------- + - Changed copy and list order in member profile screen. ([#5825](https://github.com/vector-im/element-android/issues/5825)) + - Fix for audio only being received in one direction after an un-hold during a sip call. ([#5865](https://github.com/vector-im/element-android/issues/5865)) + - Desynchronized 4S | Megolm backup causing Unusable backup ([#5906](https://github.com/vector-im/element-android/issues/5906)) + - If animations are disable on the System, chat effects and confetti will be disabled too ([#5941](https://github.com/vector-im/element-android/issues/5941)) + - Multiple threads improvement (mainly UI) ([#5959](https://github.com/vector-im/element-android/issues/5959)) + +Improved Documentation 📚 +------------------------ + - Note public_baseurl requirement in integration tests documentation. ([#5973](https://github.com/vector-im/element-android/issues/5973)) + +SDK API changes ⚠️ +------------------ + - - New API to enable/disable key forwarding CryptoService#enableKeyGossiping() + - New API to limit room key request only to own devices MXCryptoConfig#limitRoomKeyRequestsToMyDevices + - Event Trail API has changed, now using AuditTrail events + - New API to manually accept an incoming key request CryptoService#manuallyAcceptRoomKeyRequest() ([#5559](https://github.com/vector-im/element-android/issues/5559)) + - Small change in the Matrix class: deprecated methods have been removed and the constructor is now public. Also the fun `workerFactory()` has been renamed to `getWorkerFactory()` ([#5887](https://github.com/vector-im/element-android/issues/5887)) + - Including SSL/TLS error handing when doing WellKnown lookups without a custom HomeServerConnectionConfig ([#5965](https://github.com/vector-im/element-android/issues/5965)) + +Other changes +------------- + - Improve threads rendering in the main timeline ([#5151](https://github.com/vector-im/element-android/issues/5151)) + - Reformatted project code ([#5953](https://github.com/vector-im/element-android/issues/5953)) + - Update check for server-side threads support to match spec. ([#5997](https://github.com/vector-im/element-android/issues/5997)) + - Setup detekt ([#6038](https://github.com/vector-im/element-android/issues/6038)) + - Notify the user for each new message ([#46312](https://github.com/vector-im/element-android/issues/46312)) + + Changes in Element v1.4.14 (2022-05-05) ======================================= diff --git a/build.gradle b/build.gradle index 8553428b49..25ff8b91c0 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ buildscript { classpath 'com.google.gms:google-services:4.3.10' classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5' - classpath "com.likethesalad.android:stem-plugin:2.0.0" + classpath "com.likethesalad.android:stem-plugin:2.1.1" classpath 'org.owasp:dependency-check-gradle:7.1.0.1' classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.6.21" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" diff --git a/changelog.d/46312.misc b/changelog.d/46312.misc deleted file mode 100644 index 5e0112372f..0000000000 --- a/changelog.d/46312.misc +++ /dev/null @@ -1 +0,0 @@ -Notify the user for each new message diff --git a/changelog.d/5151.misc b/changelog.d/5151.misc deleted file mode 100644 index b785c4229c..0000000000 --- a/changelog.d/5151.misc +++ /dev/null @@ -1 +0,0 @@ -Improve threads rendering in the main timeline diff --git a/changelog.d/5494.feature b/changelog.d/5494.feature deleted file mode 100644 index 59b8a78a2c..0000000000 --- a/changelog.d/5494.feature +++ /dev/null @@ -1 +0,0 @@ -Use key backup before requesting keys + refactor & improvement of key request/forward \ No newline at end of file diff --git a/changelog.d/5559.sdk b/changelog.d/5559.sdk deleted file mode 100644 index 2466fcef48..0000000000 --- a/changelog.d/5559.sdk +++ /dev/null @@ -1,4 +0,0 @@ -- New API to enable/disable key forwarding CryptoService#enableKeyGossiping() -- New API to limit room key request only to own devices MXCryptoConfig#limitRoomKeyRequestsToMyDevices -- Event Trail API has changed, now using AuditTrail events -- New API to manually accept an incoming key request CryptoService#manuallyAcceptRoomKeyRequest() diff --git a/changelog.d/5658.feature b/changelog.d/5658.feature new file mode 100644 index 0000000000..ba41a03207 --- /dev/null +++ b/changelog.d/5658.feature @@ -0,0 +1 @@ +Space explore screen changes: removed space card, added rooms filtering diff --git a/changelog.d/5689.wip b/changelog.d/5689.wip new file mode 100644 index 0000000000..ccea1ec541 --- /dev/null +++ b/changelog.d/5689.wip @@ -0,0 +1 @@ +[Live location sharing] Update message in timeline during the live diff --git a/changelog.d/5724.sdk b/changelog.d/5724.sdk new file mode 100644 index 0000000000..5a0a37fe31 --- /dev/null +++ b/changelog.d/5724.sdk @@ -0,0 +1 @@ +- Notifies other devices when a verification request sent from an Android device is accepted.` diff --git a/changelog.d/5728.misc b/changelog.d/5728.misc new file mode 100644 index 0000000000..6e463fa76f --- /dev/null +++ b/changelog.d/5728.misc @@ -0,0 +1 @@ +leaving space experience changed to be aligned with iOS diff --git a/changelog.d/5825.bugfix b/changelog.d/5825.bugfix deleted file mode 100644 index 77560027ba..0000000000 --- a/changelog.d/5825.bugfix +++ /dev/null @@ -1 +0,0 @@ -Changed copy and list order in member profile screen. \ No newline at end of file diff --git a/changelog.d/5887.sdk b/changelog.d/5887.sdk deleted file mode 100644 index 0f128938dd..0000000000 --- a/changelog.d/5887.sdk +++ /dev/null @@ -1 +0,0 @@ -Small change in the Matrix class: deprecated methods have been removed and the constructor is now public. Also the fun `workerFactory()` has been renamed to `getWorkerFactory()` diff --git a/changelog.d/5906.bugfix b/changelog.d/5906.bugfix deleted file mode 100644 index be1379c6e4..0000000000 --- a/changelog.d/5906.bugfix +++ /dev/null @@ -1 +0,0 @@ -Desynchronized 4S | Megolm backup causing Unusable backup diff --git a/changelog.d/5911.feature b/changelog.d/5911.feature deleted file mode 100644 index 368a3b4056..0000000000 --- a/changelog.d/5911.feature +++ /dev/null @@ -1 +0,0 @@ -Screen sharing over WebRTC diff --git a/changelog.d/5932.feature b/changelog.d/5932.feature deleted file mode 100644 index dcfc6615b0..0000000000 --- a/changelog.d/5932.feature +++ /dev/null @@ -1 +0,0 @@ -Allow using the latest user Avatar and name for all messages in the timeline diff --git a/changelog.d/5936.feature b/changelog.d/5936.feature deleted file mode 100644 index cbf14aaba1..0000000000 --- a/changelog.d/5936.feature +++ /dev/null @@ -1 +0,0 @@ -Added themed launch icons for Android 13 \ No newline at end of file diff --git a/changelog.d/5941.bugfix b/changelog.d/5941.bugfix deleted file mode 100644 index 0ea17668c6..0000000000 --- a/changelog.d/5941.bugfix +++ /dev/null @@ -1 +0,0 @@ -If animations are disable on the System, chat effects and confetti will be disabled too diff --git a/changelog.d/5953.misc b/changelog.d/5953.misc deleted file mode 100644 index a3ad5dae93..0000000000 --- a/changelog.d/5953.misc +++ /dev/null @@ -1 +0,0 @@ -Reformatted project code diff --git a/changelog.d/5965.sdk b/changelog.d/5965.sdk deleted file mode 100644 index 5bb6c3aac4..0000000000 --- a/changelog.d/5965.sdk +++ /dev/null @@ -1 +0,0 @@ -Including SSL/TLS error handing when doing WellKnown lookups without a custom HomeServerConnectionConfig diff --git a/changelog.d/5973.doc b/changelog.d/5973.doc deleted file mode 100644 index cd3b31dd21..0000000000 --- a/changelog.d/5973.doc +++ /dev/null @@ -1 +0,0 @@ -Note public_baseurl requirement in integration tests documentation. diff --git a/changelog.d/5997.misc b/changelog.d/5997.misc deleted file mode 100644 index 328f3c0079..0000000000 --- a/changelog.d/5997.misc +++ /dev/null @@ -1 +0,0 @@ -Update check for server-side threads support to match spec. diff --git a/changelog.d/6025.misc b/changelog.d/6025.misc new file mode 100644 index 0000000000..2900796792 --- /dev/null +++ b/changelog.d/6025.misc @@ -0,0 +1 @@ +@Ignore a number of tests that are currently failing in CI. diff --git a/changelog.d/6032.bugfix b/changelog.d/6032.bugfix new file mode 100644 index 0000000000..c20d7ddd08 --- /dev/null +++ b/changelog.d/6032.bugfix @@ -0,0 +1 @@ +Revert: Use member name instead of room name in DM creation item diff --git a/changelog.d/6038.misc b/changelog.d/6038.misc deleted file mode 100644 index 881aae5ca3..0000000000 --- a/changelog.d/6038.misc +++ /dev/null @@ -1 +0,0 @@ -Setup detekt diff --git a/changelog.d/6041.misc b/changelog.d/6041.misc new file mode 100644 index 0000000000..50378ea3fd --- /dev/null +++ b/changelog.d/6041.misc @@ -0,0 +1 @@ +Remove ShortcutBadger lib and usage (it was dead code) diff --git a/changelog.d/6047.feature b/changelog.d/6047.feature deleted file mode 100644 index 59d37e21e9..0000000000 --- a/changelog.d/6047.feature +++ /dev/null @@ -1 +0,0 @@ -Add presence indicator busy and away. diff --git a/changelog.d/6074.bugfix b/changelog.d/6074.bugfix new file mode 100644 index 0000000000..692dce28d7 --- /dev/null +++ b/changelog.d/6074.bugfix @@ -0,0 +1 @@ +Poll refactoring with unit tests diff --git a/changelog.d/6089.misc b/changelog.d/6089.misc new file mode 100644 index 0000000000..19b951c1a3 --- /dev/null +++ b/changelog.d/6089.misc @@ -0,0 +1 @@ +Test: Ensure calling 'fail()' is not caught by the catch block diff --git a/changelog.d/6095.bugfix b/changelog.d/6095.bugfix new file mode 100644 index 0000000000..11110bfa08 --- /dev/null +++ b/changelog.d/6095.bugfix @@ -0,0 +1 @@ +Correct .well-known/matrix/client handling for server_names which include ports. diff --git a/changelog.d/6098.feature b/changelog.d/6098.feature new file mode 100644 index 0000000000..659da42094 --- /dev/null +++ b/changelog.d/6098.feature @@ -0,0 +1 @@ +Labs flag for enabling live location sharing diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 76869fccf1..59cefe7e89 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -141,7 +141,6 @@ ext.groups = [ 'jline', 'jp.wasabeef', 'junit', - 'me.leolin', 'me.saket', 'net.bytebuddy', 'net.java', diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104120.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104120.txt new file mode 100644 index 0000000000..7867646fe5 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Umožňuje uživatelům zobrazovat se offline a přidává zvukový přehrávač pro zvukové přílohy +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104130.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104130.txt new file mode 100644 index 0000000000..7867646fe5 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Umožňuje uživatelům zobrazovat se offline a přidává zvukový přehrávač pro zvukové přílohy +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104060.txt b/fastlane/metadata/android/de-DE/changelogs/40104060.txt new file mode 100644 index 0000000000..17cfdd26cc --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Hauptänderungen: Threads sind jetzt schneller, Fehlerbehebungen. +Alle Änderungen: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104070.txt b/fastlane/metadata/android/de-DE/changelogs/40104070.txt new file mode 100644 index 0000000000..30da225add --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Änderungen: Fehlerbehebungen +Alle Änderungen: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/de-DE/changelogs/40104080.txt b/fastlane/metadata/android/de-DE/changelogs/40104080.txt new file mode 100644 index 0000000000..902e1d27f7 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Hauptänderungen: Schnellere Threads, Fehlerbehebungen. +Alle Änderungen: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104100.txt b/fastlane/metadata/android/de-DE/changelogs/40104100.txt new file mode 100644 index 0000000000..2de5ec1d6a --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104100.txt @@ -0,0 +1,2 @@ +Hauptänderungen: Scrollen in Sprachnachrichten, Fehlerbehebungen. +Alle Änderungen: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104110.txt b/fastlane/metadata/android/de-DE/changelogs/40104110.txt new file mode 100644 index 0000000000..bde9f04e11 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104110.txt @@ -0,0 +1,2 @@ +Änderungen: Fehlerbehebungen und Stabilitätsverbesserungen +Alle Änderungen: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104120.txt b/fastlane/metadata/android/de-DE/changelogs/40104120.txt new file mode 100644 index 0000000000..e0ce944874 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Hauptänderungen: Nutzer können ihren Status auf „Offline“ setzen, Gesendete Audiodateien können nun in der App abgespielt werden +Alle Änderungen: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104130.txt b/fastlane/metadata/android/de-DE/changelogs/40104130.txt new file mode 100644 index 0000000000..e0ce944874 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Hauptänderungen: Nutzer können ihren Status auf „Offline“ setzen, Gesendete Audiodateien können nun in der App abgespielt werden +Alle Änderungen: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40104160.txt b/fastlane/metadata/android/en-US/changelogs/40104160.txt new file mode 100644 index 0000000000..6e37a4ae7f --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104160.txt @@ -0,0 +1,2 @@ +Main changes in this version: Better management of encrypted messages. Various bug fixes and stability improvements. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40104120.txt b/fastlane/metadata/android/et/changelogs/40104120.txt new file mode 100644 index 0000000000..1a7d3ae979 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: kasutajate võrguolekud ning helisõnumite esitaja. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40104130.txt b/fastlane/metadata/android/et/changelogs/40104130.txt new file mode 100644 index 0000000000..1a7d3ae979 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: kasutajate võrguolekud ning helisõnumite esitaja. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40104120.txt b/fastlane/metadata/android/fa/changelogs/40104120.txt new file mode 100644 index 0000000000..4f730e52dc --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104120.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: اجازه به کاربران برای برون‌خط ظاهر شدن و افزودن یک پخش‌کنندهٔ صدا برای پیوست‌های صوتی +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40104130.txt b/fastlane/metadata/android/fa/changelogs/40104130.txt new file mode 100644 index 0000000000..4f730e52dc --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104130.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: اجازه به کاربران برای برون‌خط ظاهر شدن و افزودن یک پخش‌کنندهٔ صدا برای پیوست‌های صوتی +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40104120.txt b/fastlane/metadata/android/id/changelogs/40104120.txt new file mode 100644 index 0000000000..ce1a4a4d84 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Diperbolehkan pengguna untuk terlihat luring dan ditambahkan sebuah pemain audio untuk lampiran audio +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40104130.txt b/fastlane/metadata/android/id/changelogs/40104130.txt new file mode 100644 index 0000000000..ce1a4a4d84 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Diperbolehkan pengguna untuk terlihat luring dan ditambahkan sebuah pemain audio untuk lampiran audio +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40104120.txt b/fastlane/metadata/android/it-IT/changelogs/40104120.txt new file mode 100644 index 0000000000..fa015ae564 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: consente agli utenti di apparire offline e aggiunge un player audio per gli allegati audio +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40104130.txt b/fastlane/metadata/android/it-IT/changelogs/40104130.txt new file mode 100644 index 0000000000..fa015ae564 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: consente agli utenti di apparire offline e aggiunge un player audio per gli allegati audio +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/lo/changelogs/40100100.txt b/fastlane/metadata/android/lo-LA/changelogs/40100100.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40100100.txt rename to fastlane/metadata/android/lo-LA/changelogs/40100100.txt diff --git a/fastlane/metadata/android/lo/changelogs/40100110.txt b/fastlane/metadata/android/lo-LA/changelogs/40100110.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40100110.txt rename to fastlane/metadata/android/lo-LA/changelogs/40100110.txt diff --git a/fastlane/metadata/android/lo/changelogs/40100120.txt b/fastlane/metadata/android/lo-LA/changelogs/40100120.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40100120.txt rename to fastlane/metadata/android/lo-LA/changelogs/40100120.txt diff --git a/fastlane/metadata/android/lo/changelogs/40100130.txt b/fastlane/metadata/android/lo-LA/changelogs/40100130.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40100130.txt rename to fastlane/metadata/android/lo-LA/changelogs/40100130.txt diff --git a/fastlane/metadata/android/lo/changelogs/40100140.txt b/fastlane/metadata/android/lo-LA/changelogs/40100140.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40100140.txt rename to fastlane/metadata/android/lo-LA/changelogs/40100140.txt diff --git a/fastlane/metadata/android/lo/changelogs/40100150.txt b/fastlane/metadata/android/lo-LA/changelogs/40100150.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40100150.txt rename to fastlane/metadata/android/lo-LA/changelogs/40100150.txt diff --git a/fastlane/metadata/android/lo/changelogs/40100160.txt b/fastlane/metadata/android/lo-LA/changelogs/40100160.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40100160.txt rename to fastlane/metadata/android/lo-LA/changelogs/40100160.txt diff --git a/fastlane/metadata/android/lo/changelogs/40100170.txt b/fastlane/metadata/android/lo-LA/changelogs/40100170.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40100170.txt rename to fastlane/metadata/android/lo-LA/changelogs/40100170.txt diff --git a/fastlane/metadata/android/lo/changelogs/40101000.txt b/fastlane/metadata/android/lo-LA/changelogs/40101000.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40101000.txt rename to fastlane/metadata/android/lo-LA/changelogs/40101000.txt diff --git a/fastlane/metadata/android/lo/changelogs/40101010.txt b/fastlane/metadata/android/lo-LA/changelogs/40101010.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40101010.txt rename to fastlane/metadata/android/lo-LA/changelogs/40101010.txt diff --git a/fastlane/metadata/android/lo/changelogs/40101020.txt b/fastlane/metadata/android/lo-LA/changelogs/40101020.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40101020.txt rename to fastlane/metadata/android/lo-LA/changelogs/40101020.txt diff --git a/fastlane/metadata/android/lo/changelogs/40101030.txt b/fastlane/metadata/android/lo-LA/changelogs/40101030.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40101030.txt rename to fastlane/metadata/android/lo-LA/changelogs/40101030.txt diff --git a/fastlane/metadata/android/lo/changelogs/40101040.txt b/fastlane/metadata/android/lo-LA/changelogs/40101040.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40101040.txt rename to fastlane/metadata/android/lo-LA/changelogs/40101040.txt diff --git a/fastlane/metadata/android/lo/changelogs/40101050.txt b/fastlane/metadata/android/lo-LA/changelogs/40101050.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40101050.txt rename to fastlane/metadata/android/lo-LA/changelogs/40101050.txt diff --git a/fastlane/metadata/android/lo/changelogs/40101060.txt b/fastlane/metadata/android/lo-LA/changelogs/40101060.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40101060.txt rename to fastlane/metadata/android/lo-LA/changelogs/40101060.txt diff --git a/fastlane/metadata/android/lo/changelogs/40101070.txt b/fastlane/metadata/android/lo-LA/changelogs/40101070.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40101070.txt rename to fastlane/metadata/android/lo-LA/changelogs/40101070.txt diff --git a/fastlane/metadata/android/lo/changelogs/40101080.txt b/fastlane/metadata/android/lo-LA/changelogs/40101080.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40101080.txt rename to fastlane/metadata/android/lo-LA/changelogs/40101080.txt diff --git a/fastlane/metadata/android/lo/changelogs/40101090.txt b/fastlane/metadata/android/lo-LA/changelogs/40101090.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40101090.txt rename to fastlane/metadata/android/lo-LA/changelogs/40101090.txt diff --git a/fastlane/metadata/android/lo/changelogs/40101100.txt b/fastlane/metadata/android/lo-LA/changelogs/40101100.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40101100.txt rename to fastlane/metadata/android/lo-LA/changelogs/40101100.txt diff --git a/fastlane/metadata/android/lo/changelogs/40101110.txt b/fastlane/metadata/android/lo-LA/changelogs/40101110.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40101110.txt rename to fastlane/metadata/android/lo-LA/changelogs/40101110.txt diff --git a/fastlane/metadata/android/lo/changelogs/40101120.txt b/fastlane/metadata/android/lo-LA/changelogs/40101120.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40101120.txt rename to fastlane/metadata/android/lo-LA/changelogs/40101120.txt diff --git a/fastlane/metadata/android/lo/changelogs/40101130.txt b/fastlane/metadata/android/lo-LA/changelogs/40101130.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40101130.txt rename to fastlane/metadata/android/lo-LA/changelogs/40101130.txt diff --git a/fastlane/metadata/android/lo/changelogs/40101140.txt b/fastlane/metadata/android/lo-LA/changelogs/40101140.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40101140.txt rename to fastlane/metadata/android/lo-LA/changelogs/40101140.txt diff --git a/fastlane/metadata/android/lo/changelogs/40101150.txt b/fastlane/metadata/android/lo-LA/changelogs/40101150.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40101150.txt rename to fastlane/metadata/android/lo-LA/changelogs/40101150.txt diff --git a/fastlane/metadata/android/lo/changelogs/40101160.txt b/fastlane/metadata/android/lo-LA/changelogs/40101160.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40101160.txt rename to fastlane/metadata/android/lo-LA/changelogs/40101160.txt diff --git a/fastlane/metadata/android/lo/changelogs/40102000.txt b/fastlane/metadata/android/lo-LA/changelogs/40102000.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40102000.txt rename to fastlane/metadata/android/lo-LA/changelogs/40102000.txt diff --git a/fastlane/metadata/android/lo/changelogs/40102010.txt b/fastlane/metadata/android/lo-LA/changelogs/40102010.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40102010.txt rename to fastlane/metadata/android/lo-LA/changelogs/40102010.txt diff --git a/fastlane/metadata/android/lo/changelogs/40103000.txt b/fastlane/metadata/android/lo-LA/changelogs/40103000.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40103000.txt rename to fastlane/metadata/android/lo-LA/changelogs/40103000.txt diff --git a/fastlane/metadata/android/lo/changelogs/40103010.txt b/fastlane/metadata/android/lo-LA/changelogs/40103010.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40103010.txt rename to fastlane/metadata/android/lo-LA/changelogs/40103010.txt diff --git a/fastlane/metadata/android/lo/changelogs/40103020.txt b/fastlane/metadata/android/lo-LA/changelogs/40103020.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40103020.txt rename to fastlane/metadata/android/lo-LA/changelogs/40103020.txt diff --git a/fastlane/metadata/android/lo/changelogs/40103030.txt b/fastlane/metadata/android/lo-LA/changelogs/40103030.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40103030.txt rename to fastlane/metadata/android/lo-LA/changelogs/40103030.txt diff --git a/fastlane/metadata/android/lo/changelogs/40103040.txt b/fastlane/metadata/android/lo-LA/changelogs/40103040.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40103040.txt rename to fastlane/metadata/android/lo-LA/changelogs/40103040.txt diff --git a/fastlane/metadata/android/lo/changelogs/40103050.txt b/fastlane/metadata/android/lo-LA/changelogs/40103050.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40103050.txt rename to fastlane/metadata/android/lo-LA/changelogs/40103050.txt diff --git a/fastlane/metadata/android/lo/changelogs/40103060.txt b/fastlane/metadata/android/lo-LA/changelogs/40103060.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40103060.txt rename to fastlane/metadata/android/lo-LA/changelogs/40103060.txt diff --git a/fastlane/metadata/android/lo/changelogs/40103070.txt b/fastlane/metadata/android/lo-LA/changelogs/40103070.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40103070.txt rename to fastlane/metadata/android/lo-LA/changelogs/40103070.txt diff --git a/fastlane/metadata/android/lo/changelogs/40103080.txt b/fastlane/metadata/android/lo-LA/changelogs/40103080.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40103080.txt rename to fastlane/metadata/android/lo-LA/changelogs/40103080.txt diff --git a/fastlane/metadata/android/lo/changelogs/40103090.txt b/fastlane/metadata/android/lo-LA/changelogs/40103090.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40103090.txt rename to fastlane/metadata/android/lo-LA/changelogs/40103090.txt diff --git a/fastlane/metadata/android/lo/changelogs/40103100.txt b/fastlane/metadata/android/lo-LA/changelogs/40103100.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40103100.txt rename to fastlane/metadata/android/lo-LA/changelogs/40103100.txt diff --git a/fastlane/metadata/android/lo/changelogs/40103110.txt b/fastlane/metadata/android/lo-LA/changelogs/40103110.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40103110.txt rename to fastlane/metadata/android/lo-LA/changelogs/40103110.txt diff --git a/fastlane/metadata/android/lo/changelogs/40103120.txt b/fastlane/metadata/android/lo-LA/changelogs/40103120.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40103120.txt rename to fastlane/metadata/android/lo-LA/changelogs/40103120.txt diff --git a/fastlane/metadata/android/lo/changelogs/40103130.txt b/fastlane/metadata/android/lo-LA/changelogs/40103130.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40103130.txt rename to fastlane/metadata/android/lo-LA/changelogs/40103130.txt diff --git a/fastlane/metadata/android/lo/changelogs/40103140.txt b/fastlane/metadata/android/lo-LA/changelogs/40103140.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40103140.txt rename to fastlane/metadata/android/lo-LA/changelogs/40103140.txt diff --git a/fastlane/metadata/android/lo/changelogs/40103150.txt b/fastlane/metadata/android/lo-LA/changelogs/40103150.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40103150.txt rename to fastlane/metadata/android/lo-LA/changelogs/40103150.txt diff --git a/fastlane/metadata/android/lo/changelogs/40103160.txt b/fastlane/metadata/android/lo-LA/changelogs/40103160.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40103160.txt rename to fastlane/metadata/android/lo-LA/changelogs/40103160.txt diff --git a/fastlane/metadata/android/lo/changelogs/40103170.txt b/fastlane/metadata/android/lo-LA/changelogs/40103170.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40103170.txt rename to fastlane/metadata/android/lo-LA/changelogs/40103170.txt diff --git a/fastlane/metadata/android/lo/changelogs/40103180.txt b/fastlane/metadata/android/lo-LA/changelogs/40103180.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40103180.txt rename to fastlane/metadata/android/lo-LA/changelogs/40103180.txt diff --git a/fastlane/metadata/android/lo/changelogs/40104000.txt b/fastlane/metadata/android/lo-LA/changelogs/40104000.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40104000.txt rename to fastlane/metadata/android/lo-LA/changelogs/40104000.txt diff --git a/fastlane/metadata/android/lo/changelogs/40104020.txt b/fastlane/metadata/android/lo-LA/changelogs/40104020.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40104020.txt rename to fastlane/metadata/android/lo-LA/changelogs/40104020.txt diff --git a/fastlane/metadata/android/lo/changelogs/40104040.txt b/fastlane/metadata/android/lo-LA/changelogs/40104040.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40104040.txt rename to fastlane/metadata/android/lo-LA/changelogs/40104040.txt diff --git a/fastlane/metadata/android/lo/changelogs/40104060.txt b/fastlane/metadata/android/lo-LA/changelogs/40104060.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40104060.txt rename to fastlane/metadata/android/lo-LA/changelogs/40104060.txt diff --git a/fastlane/metadata/android/lo/changelogs/40104070.txt b/fastlane/metadata/android/lo-LA/changelogs/40104070.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40104070.txt rename to fastlane/metadata/android/lo-LA/changelogs/40104070.txt diff --git a/fastlane/metadata/android/lo/changelogs/40104080.txt b/fastlane/metadata/android/lo-LA/changelogs/40104080.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40104080.txt rename to fastlane/metadata/android/lo-LA/changelogs/40104080.txt diff --git a/fastlane/metadata/android/lo/changelogs/40104100.txt b/fastlane/metadata/android/lo-LA/changelogs/40104100.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40104100.txt rename to fastlane/metadata/android/lo-LA/changelogs/40104100.txt diff --git a/fastlane/metadata/android/lo/changelogs/40104110.txt b/fastlane/metadata/android/lo-LA/changelogs/40104110.txt similarity index 100% rename from fastlane/metadata/android/lo/changelogs/40104110.txt rename to fastlane/metadata/android/lo-LA/changelogs/40104110.txt diff --git a/fastlane/metadata/android/lo-LA/changelogs/40104120.txt b/fastlane/metadata/android/lo-LA/changelogs/40104120.txt new file mode 100644 index 0000000000..36c6d678f7 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40104120.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ໃຫ້ຜູ້ໃຊ້ສາມາດສະແດງຕົວເປັນ offline ແລະສາມາດຫຼິ້ນສຽງໄດ້ສຳລັບການແນບສຽງ +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/lo-LA/changelogs/40104130.txt b/fastlane/metadata/android/lo-LA/changelogs/40104130.txt new file mode 100644 index 0000000000..36c6d678f7 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40104130.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ໃຫ້ຜູ້ໃຊ້ສາມາດສະແດງຕົວເປັນ offline ແລະສາມາດຫຼິ້ນສຽງໄດ້ສຳລັບການແນບສຽງ +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/lo/full_description.txt b/fastlane/metadata/android/lo-LA/full_description.txt similarity index 100% rename from fastlane/metadata/android/lo/full_description.txt rename to fastlane/metadata/android/lo-LA/full_description.txt diff --git a/fastlane/metadata/android/lo/short_description.txt b/fastlane/metadata/android/lo-LA/short_description.txt similarity index 100% rename from fastlane/metadata/android/lo/short_description.txt rename to fastlane/metadata/android/lo-LA/short_description.txt diff --git a/fastlane/metadata/android/lo/title.txt b/fastlane/metadata/android/lo-LA/title.txt similarity index 100% rename from fastlane/metadata/android/lo/title.txt rename to fastlane/metadata/android/lo-LA/title.txt diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103050.txt b/fastlane/metadata/android/pl-PL/changelogs/40103050.txt new file mode 100644 index 0000000000..b81d5ef037 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103050.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Dodanie obsługi obecności, dla pokoju wiadomości bezpośrednich (uwaga: obecność jest wyłączona na matrix.org). Dodaje ponownie obsługę Androida Auto. + Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.5 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104100.txt b/fastlane/metadata/android/pl-PL/changelogs/40104100.txt new file mode 100644 index 0000000000..3d1efbc1d2 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104100.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Przewijanie w wiadomości głosowej. Różne poprawki błędów i ulepszenia stabilności. + Pełna lista zmian: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104110.txt b/fastlane/metadata/android/pl-PL/changelogs/40104110.txt new file mode 100644 index 0000000000..6ae86f3c9b --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104110.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Różne poprawki błędów i ulepszenia stabilności. + Pełna lista zmian: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104120.txt b/fastlane/metadata/android/pl-PL/changelogs/40104120.txt new file mode 100644 index 0000000000..bd995ad5e5 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Pozwala użytkownikom pojawiać się w trybie offline i dodaje odtwarzacz audio do załączników audio +Pełna lista zmian: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104130.txt b/fastlane/metadata/android/pl-PL/changelogs/40104130.txt new file mode 100644 index 0000000000..bd995ad5e5 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Pozwala użytkownikom pojawiać się w trybie offline i dodaje odtwarzacz audio do załączników audio +Pełna lista zmian: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pl-PL/title.txt b/fastlane/metadata/android/pl-PL/title.txt index 907f907f99..df4d9b71f4 100644 --- a/fastlane/metadata/android/pl-PL/title.txt +++ b/fastlane/metadata/android/pl-PL/title.txt @@ -1 +1 @@ -Element +Element - komunikator diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104120.txt b/fastlane/metadata/android/pt-BR/changelogs/40104120.txt new file mode 100644 index 0000000000..f77d426d99 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Permite usuárias(os) aparecer offline e adiciona um tocador de áudio para anexos de áudio +Changelog completo: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104130.txt b/fastlane/metadata/android/pt-BR/changelogs/40104130.txt new file mode 100644 index 0000000000..f77d426d99 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Permite usuárias(os) aparecer offline e adiciona um tocador de áudio para anexos de áudio +Changelog completo: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40104120.txt b/fastlane/metadata/android/sk/changelogs/40104120.txt new file mode 100644 index 0000000000..2279ddc574 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Umožňuje používateľom zobrazovať sa v režime offline a pridáva zvukový prehrávač pre zvukové prílohy +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40104130.txt b/fastlane/metadata/android/sk/changelogs/40104130.txt new file mode 100644 index 0000000000..2279ddc574 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Umožňuje používateľom zobrazovať sa v režime offline a pridáva zvukový prehrávač pre zvukové prílohy +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104120.txt b/fastlane/metadata/android/sv-SE/changelogs/40104120.txt new file mode 100644 index 0000000000..6692768e1e --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Låter användare visas offline och lägger till en ljudspelare för ljudbilagor +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104130.txt b/fastlane/metadata/android/sv-SE/changelogs/40104130.txt new file mode 100644 index 0000000000..6692768e1e --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Låter användare visas offline och lägger till en ljudspelare för ljudbilagor +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40104120.txt b/fastlane/metadata/android/uk/changelogs/40104120.txt new file mode 100644 index 0000000000..aa075bd42e --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Основні зміни у цій версії: Дозволяє користувачам з’являтися в режимі офлайн та додає аудіопрогравач для аудіовкладень +Вичерпний перелік змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40104130.txt b/fastlane/metadata/android/uk/changelogs/40104130.txt new file mode 100644 index 0000000000..aa075bd42e --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Основні зміни у цій версії: Дозволяє користувачам з’являтися в режимі офлайн та додає аудіопрогравач для аудіовкладень +Вичерпний перелік змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104120.txt b/fastlane/metadata/android/zh-TW/changelogs/40104120.txt new file mode 100644 index 0000000000..d3d48abab9 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104120.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:允許使用者顯示為離線並為音訊附件新增音訊播放器 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104130.txt b/fastlane/metadata/android/zh-TW/changelogs/40104130.txt new file mode 100644 index 0000000000..d3d48abab9 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104130.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:允許使用者顯示為離線並為音訊附件新增音訊播放器 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/library/core-utils/src/main/java/im/vector/lib/core/utils/flow/TimingOperators.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/flow/TimingOperators.kt index 2efb439ace..aeb5ae7914 100644 --- a/library/core-utils/src/main/java/im/vector/lib/core/utils/flow/TimingOperators.kt +++ b/library/core-utils/src/main/java/im/vector/lib/core/utils/flow/TimingOperators.kt @@ -16,6 +16,7 @@ package im.vector.lib.core.utils.flow +import android.os.SystemClock import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.Channel @@ -68,10 +69,10 @@ fun Flow.chunk(durationInMillis: Long): Flow> { @ExperimentalCoroutinesApi fun Flow.throttleFirst(windowDuration: Long): Flow = flow { - var windowStartTime = System.currentTimeMillis() + var windowStartTime = SystemClock.elapsedRealtime() var emitted = false collect { value -> - val currentTime = System.currentTimeMillis() + val currentTime = SystemClock.elapsedRealtime() val delta = currentTime - windowStartTime if (delta >= windowDuration) { windowStartTime += delta / windowDuration * windowDuration diff --git a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/ValueItem.kt b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/ValueItem.kt index fac7099b37..66dfcc5dc3 100644 --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/ValueItem.kt +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/ValueItem.kt @@ -18,11 +18,11 @@ package org.billcarsonfr.jsonviewer import android.content.ClipData import android.content.ClipboardManager -import android.content.Context import android.view.ContextMenu import android.view.View import android.widget.LinearLayout import android.widget.TextView +import androidx.core.content.getSystemService import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyHolder import com.airbnb.epoxy.EpoxyModelClass @@ -77,8 +77,7 @@ internal abstract class ValueItem : EpoxyModelWithHolder() { ) { if (copyValue != null) { val menuItem = menu?.add(R.string.copy_value) - val clipService = - v?.context?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager + val clipService = v?.context?.getSystemService() menuItem?.setOnMenuItemClickListener { clipService?.setPrimaryClip(ClipData.newPlainText("", copyValue)) true diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml index 2104b49ab5..e72d02f51e 100644 --- a/library/ui-styles/src/main/res/values/colors.xml +++ b/library/ui-styles/src/main/res/values/colors.xml @@ -144,4 +144,9 @@ #17191C #FF4B55 + + + @color/palette_white + @color/palette_black_950 + diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml index 81d5a77297..70d051b457 100644 --- a/library/ui-styles/src/main/res/values/dimens.xml +++ b/library/ui-styles/src/main/res/values/dimens.xml @@ -9,6 +9,7 @@ 32dp 50dp + 20dp 16dp 32dp @@ -40,7 +41,7 @@ 24dp 48dp 48dp - 38dp + 34dp 56dp diff --git a/library/ui-styles/src/main/res/values/styles_location.xml b/library/ui-styles/src/main/res/values/styles_location.xml index 5563d28342..7571265241 100644 --- a/library/ui-styles/src/main/res/values/styles_location.xml +++ b/library/ui-styles/src/main/res/values/styles_location.xml @@ -2,10 +2,20 @@ + + diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml index eeff039b71..733f7e8eb5 100644 --- a/library/ui-styles/src/main/res/values/theme_dark.xml +++ b/library/ui-styles/src/main/res/values/theme_dark.xml @@ -30,6 +30,7 @@ @color/element_system_dark @color/vctr_message_bubble_inbound_dark @color/vctr_message_bubble_outbound_dark + @color/vctr_badge_color_border_dark #61708B diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml index 0c363b583d..77996c8ce5 100644 --- a/library/ui-styles/src/main/res/values/theme_light.xml +++ b/library/ui-styles/src/main/res/values/theme_light.xml @@ -30,6 +30,7 @@ @color/element_background_light @color/vctr_message_bubble_inbound_light @color/vctr_message_bubble_outbound_light + @color/vctr_badge_color_border_light #61708B diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index dfa99d0f8a..33798d1357 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -56,7 +56,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.4.16\"" + buildConfigField "String", "SDK_VERSION", "\"1.4.18\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/Util.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/Util.kt new file mode 100644 index 0000000000..5e2c2ba25f --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/Util.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 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 + +import junit.framework.TestCase.fail + +/** + * Will fail the test if invoking [block] is not throwing a Throwable. + * + * @param message the failure message, if the block does not throw any Throwable + * @param failureBlock a Lambda to be able to do extra check on the thrown Throwable + * @param block the block to test + */ +internal suspend fun mustFail( + message: String = "must fail", + failureBlock: ((Throwable) -> Unit)? = null, + block: suspend () -> Unit, +) { + val isSuccess = try { + block.invoke() + true + } catch (throwable: Throwable) { + failureBlock?.invoke(throwable) + false + } + + if (isSuccess) { + fail(message) + } +} 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 e33e4faea2..96ea99d92f 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 @@ -96,7 +96,7 @@ class CommonTestHelper(context: Context) { /** * This methods init the event stream and check for initial sync * - * @param session the session to sync + * @param session the session to sync */ fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis * 10) { val lock = CountDownLatch(1) @@ -119,7 +119,7 @@ class CommonTestHelper(context: Context) { /** * This methods clear the cache and waits for initialSync * - * @param session the session to sync + * @param session the session to sync */ fun clearCacheAndSync(session: Session, timeout: Long = TestConstants.timeOutMillis) { waitWithLatch(timeout) { latch -> @@ -142,8 +142,8 @@ class CommonTestHelper(context: Context) { /** * Sends text messages in a room * - * @param room the room where to send the messages - * @param message the message to send + * @param room the room where to send the messages + * @param message the message to send * @param nbOfMessages the number of time the message will be sent */ fun sendTextMessage(room: Room, message: String, nbOfMessages: Int, timeout: Long = TestConstants.timeOutMillis): List { @@ -207,8 +207,8 @@ class CommonTestHelper(context: Context) { /** * Reply in a thread - * @param room the room where to send the messages - * @param message the message to send + * @param room the room where to send the messages + * @param message the message to send * @param numberOfMessages the number of time the message will be sent */ fun replyInThreadMessage( @@ -232,8 +232,8 @@ class CommonTestHelper(context: Context) { * Creates a unique account * * @param userNamePrefix the user name prefix - * @param password the password - * @param testParams test params about the session + * @param password the password + * @param testParams test params about the session * @return the session associated with the newly created account */ private fun createAccount(userNamePrefix: String, @@ -251,8 +251,8 @@ class CommonTestHelper(context: Context) { /** * Logs into an existing account * - * @param userId the userId to log in - * @param password the password to log in + * @param userId the userId to log in + * @param password the password to log in * @param testParams test params about the session * @return the session associated with the existing account */ @@ -267,8 +267,8 @@ class CommonTestHelper(context: Context) { /** * Create an account and a dedicated session * - * @param userName the account username - * @param password the password + * @param userName the account username + * @param password the password * @param sessionTestParams parameters for the test */ private fun createAccountAndSync(userName: String, @@ -297,7 +297,7 @@ class CommonTestHelper(context: Context) { val session = (registrationResult as RegistrationResult.Success).session session.open() if (sessionTestParams.withInitialSync) { - syncSession(session, 60_000) + syncSession(session, 120_000) } return session } @@ -305,8 +305,8 @@ class CommonTestHelper(context: Context) { /** * Start an account login * - * @param userName the account username - * @param password the password + * @param userName the account username + * @param password the password * @param sessionTestParams session test params */ private fun logAccountAndSync(userName: String, @@ -378,7 +378,10 @@ class CommonTestHelper(context: Context) { * @throws InterruptedException */ fun await(latch: CountDownLatch, timeout: Long? = TestConstants.timeOutMillis) { - assertTrue(latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS)) + assertTrue( + "Timed out after " + timeout + "ms waiting for something to happen. See stacktrace for cause.", + latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS) + ) } suspend fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt index 0f79896b2c..89c965c31a 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt @@ -23,7 +23,7 @@ object TestConstants { const val TESTS_HOME_SERVER_URL = "http://10.0.2.2:8080" // Time out to use when waiting for server response. - private const val AWAIT_TIME_OUT_MILLIS = 60_000 + private const val AWAIT_TIME_OUT_MILLIS = 120_000 // Time out to use when waiting for server response, when the debugger is connected. 10 minutes private const val AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS = 10 * 60_000 diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrix.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrix.kt index e663cc1865..5864a801e6 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrix.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrix.kt @@ -21,7 +21,8 @@ import android.os.Handler import android.os.Looper import androidx.lifecycle.ProcessLifecycleOwner import androidx.work.Configuration -import androidx.work.WorkManager +import androidx.work.impl.WorkManagerImpl +import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.MatrixConfiguration @@ -66,7 +67,12 @@ internal class TestMatrix(context: Context, matrixConfiguration: MatrixConfigura .setExecutor(Executors.newCachedThreadPool()) .setWorkerFactory(matrixWorkerFactory) .build() - WorkManager.initialize(appContext, configuration) + val delegate = WorkManagerImpl( + context, + configuration, + WorkManagerTaskExecutor(configuration.taskExecutor) + ) + WorkManagerImpl.setDelegate(delegate) uiHandler.post { ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver) } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt index e823aa39a1..cd6c146f03 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt @@ -22,9 +22,11 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Assert.assertNull import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.common.RetryTestRule import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.util.time.DefaultClock @@ -37,6 +39,8 @@ private const val DUMMY_DEVICE_KEY = "DeviceKey" @RunWith(AndroidJUnit4::class) class CryptoStoreTest : InstrumentedTest { + @get:Rule val rule = RetryTestRule(3) + private val cryptoStoreHelper = CryptoStoreHelper() private val clock = DefaultClock() diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt index ebe4c5ff6f..552936971f 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt @@ -23,6 +23,7 @@ import org.amshove.kluent.fail import org.amshove.kluent.internal.assertEquals import org.junit.Assert import org.junit.FixMethodOrder +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -57,9 +58,11 @@ import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.RetryTestRule import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.common.TestMatrixCallback +import org.matrix.android.sdk.mustFail import java.util.concurrent.CountDownLatch @RunWith(JUnit4::class) @@ -67,6 +70,8 @@ import java.util.concurrent.CountDownLatch @LargeTest class E2eeSanityTests : InstrumentedTest { + @get:Rule val rule = RetryTestRule(3) + /** * Simple test that create an e2ee room. * Some new members are added, and a message is sent. @@ -521,10 +526,8 @@ class E2eeSanityTests : InstrumentedTest { // Confirm we can decrypt one but not the other testHelper.runBlockingTest { - try { + mustFail(message = "Should not be able to decrypt event") { newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "") - fail("Should not be able to decrypt event") - } catch (_: MXCryptoError) { } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt index a37626dc20..abcf1714b8 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt @@ -25,7 +25,6 @@ import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.FixMethodOrder -import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -143,7 +142,6 @@ class XSigningTest : InstrumentedTest { } @Test - @Ignore("This test will be ignored until it is fixed") fun test_CrossSigningTestAliceTrustBobNewDevice() { val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt index 2e4fd62822..895f95aeac 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt @@ -21,11 +21,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import junit.framework.TestCase.assertNotNull import junit.framework.TestCase.assertTrue -import junit.framework.TestCase.fail import org.amshove.kluent.internal.assertEquals import org.junit.Assert import org.junit.Assert.assertNull import org.junit.FixMethodOrder +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -43,14 +43,18 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.RetryTestRule import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants +import org.matrix.android.sdk.mustFail @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest class KeyShareTests : InstrumentedTest { + @get:Rule val rule = RetryTestRule(3) + @Test fun test_DoNotSelfShareIfNotTrusted() { val commonTestHelper = CommonTestHelper(context()) @@ -91,12 +95,10 @@ class KeyShareTests : InstrumentedTest { assertNotNull(receivedEvent) assert(receivedEvent!!.isEncrypted()) - try { - commonTestHelper.runBlockingTest { + commonTestHelper.runBlockingTest { + mustFail { aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo") } - fail("should fail") - } catch (failure: Throwable) { } val outgoingRequestsBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequests() @@ -164,12 +166,10 @@ class KeyShareTests : InstrumentedTest { } } - try { - commonTestHelper.runBlockingTest { + commonTestHelper.runBlockingTest { + mustFail { aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo") } - fail("should fail") - } catch (failure: Throwable) { } // Mark the device as trusted diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt index cb31a2232f..13133b726c 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt @@ -21,6 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import org.junit.Assert import org.junit.FixMethodOrder +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -38,14 +39,18 @@ import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.MockOkHttpInterceptor +import org.matrix.android.sdk.common.RetryTestRule import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants +import org.matrix.android.sdk.mustFail @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest class WithHeldTests : InstrumentedTest { + @get:Rule val rule = RetryTestRule(3) + @Test fun test_WithHeldUnverifiedReason() { val testHelper = CommonTestHelper(context()) @@ -92,17 +97,19 @@ class WithHeldTests : InstrumentedTest { // ============================= // Bob should not be able to decrypt because the keys is withheld - try { - // .. might need to wait a bit for stability? - testHelper.runBlockingTest { + // .. might need to wait a bit for stability? + testHelper.runBlockingTest { + mustFail( + message = "This session should not be able to decrypt", + failureBlock = { failure -> + val type = (failure as MXCryptoError.Base).errorType + val technicalMessage = failure.technicalMessage + Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type) + Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage) + } + ) { bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "") } - Assert.fail("This session should not be able to decrypt") - } catch (failure: Throwable) { - val type = (failure as MXCryptoError.Base).errorType - val technicalMessage = failure.technicalMessage - Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type) - Assert.assertEquals("Cause should be unverified", WithHeldCode.UNAUTHORISED.value, technicalMessage) } // Let's see if the reply we got from bob first session is unverified @@ -133,17 +140,18 @@ class WithHeldTests : InstrumentedTest { } // Previous message should still be undecryptable (partially withheld session) - try { - // .. might need to wait a bit for stability? - testHelper.runBlockingTest { + // .. might need to wait a bit for stability? + testHelper.runBlockingTest { + mustFail( + message = "This session should not be able to decrypt", + failureBlock = { failure -> + val type = (failure as MXCryptoError.Base).errorType + val technicalMessage = failure.technicalMessage + Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type) + Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage) + }) { bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "") } - Assert.fail("This session should not be able to decrypt") - } catch (failure: Throwable) { - val type = (failure as MXCryptoError.Base).errorType - val technicalMessage = failure.technicalMessage - Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type) - Assert.assertEquals("Cause should be unverified", WithHeldCode.UNAUTHORISED.value, technicalMessage) } testHelper.signOutAndClose(aliceSession) @@ -186,17 +194,18 @@ class WithHeldTests : InstrumentedTest { // Previous message should still be undecryptable (partially withheld session) val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId) - try { - // .. might need to wait a bit for stability? - testHelper.runBlockingTest { + // .. might need to wait a bit for stability? + testHelper.runBlockingTest { + mustFail( + message = "This session should not be able to decrypt", + failureBlock = { failure -> + val type = (failure as MXCryptoError.Base).errorType + val technicalMessage = failure.technicalMessage + Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type) + Assert.assertEquals("Cause should be unverified", WithHeldCode.NO_OLM.value, technicalMessage) + }) { bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "") } - Assert.fail("This session should not be able to decrypt") - } catch (failure: Throwable) { - val type = (failure as MXCryptoError.Base).errorType - val technicalMessage = failure.technicalMessage - Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type) - Assert.assertEquals("Cause should be unverified", WithHeldCode.NO_OLM.value, technicalMessage) } // Ensure that alice has marked the session to be shared with bob diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt index df3b2ffe27..ceebc3cd01 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt @@ -27,11 +27,13 @@ import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse +import org.matrix.android.sdk.api.session.crypto.verification.CancelCode import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants import java.util.concurrent.CountDownLatch import kotlin.coroutines.Continuation @@ -252,4 +254,48 @@ class VerificationTest : InstrumentedTest { cryptoTestData.cleanUp(testHelper) } + + @Test + fun test_selfVerificationAcceptedCancelsItForOtherSessions() { + val defaultSessionParams = SessionTestParams(true) + val testHelper = CommonTestHelper(context()) + + val aliceSessionToVerify = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) + val aliceSessionThatVerifies = testHelper.logIntoAccount(aliceSessionToVerify.myUserId, TestConstants.PASSWORD, defaultSessionParams) + val aliceSessionThatReceivesCanceledEvent = testHelper.logIntoAccount(aliceSessionToVerify.myUserId, TestConstants.PASSWORD, defaultSessionParams) + + val verificationMethods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW) + + val serviceOfVerified = aliceSessionToVerify.cryptoService().verificationService() + val serviceOfVerifier = aliceSessionThatVerifies.cryptoService().verificationService() + val serviceOfUserWhoReceivesCancellation = aliceSessionThatReceivesCanceledEvent.cryptoService().verificationService() + + serviceOfVerifier.addListener(object : VerificationService.Listener { + override fun verificationRequestCreated(pr: PendingVerificationRequest) { + // Accept verification request + serviceOfVerifier.readyPendingVerification( + verificationMethods, + pr.otherUserId, + pr.transactionId!!, + ) + } + }) + + serviceOfVerified.requestKeyVerification( + methods = verificationMethods, + otherUserId = aliceSessionToVerify.myUserId, + otherDevices = listOfNotNull(aliceSessionThatVerifies.sessionParams.deviceId, aliceSessionThatReceivesCanceledEvent.sessionParams.deviceId), + ) + + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val requests = serviceOfUserWhoReceivesCancellation.getExistingVerificationRequests(aliceSessionToVerify.myUserId) + requests.any { it.cancelConclusion == CancelCode.AcceptedByAnotherDevice } + } + } + + testHelper.signOutAndClose(aliceSessionToVerify) + testHelper.signOutAndClose(aliceSessionThatVerifies) + testHelper.signOutAndClose(aliceSessionThatReceivesCanceledEvent) + } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt index d5b4a07fc0..e407c1b42d 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt @@ -140,9 +140,24 @@ class TimelineForwardPaginationTest : InstrumentedTest { aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse() assertEquals(EventType.STATE_ROOM_CREATE, snapshot.lastOrNull()?.root?.getClearType()) - // 6 for room creation item (backward pagination), 1 for the context, and 50 for the forward pagination - // 6 + 1 + 50 - assertEquals(57, snapshot.size) + + // We explicitly test all the types we expect here, as we expect 51 messages and "some" state events + // But state events can change over time. So this acts as a kinda documentation of what we expect and + // provides a good error message if it doesn't match + + val snapshotTypes = mutableMapOf() + snapshot.groupingBy { it -> it.root.type }.eachCountTo(snapshotTypes) + // Some state events on room creation + assertEquals("m.room.name", 1, snapshotTypes.remove("m.room.name")) + assertEquals("m.room.guest_access", 1, snapshotTypes.remove("m.room.guest_access")) + assertEquals("m.room.history_visibility", 1, snapshotTypes.remove("m.room.history_visibility")) + assertEquals("m.room.join_rules", 1, snapshotTypes.remove("m.room.join_rules")) + assertEquals("m.room.power_levels", 1, snapshotTypes.remove("m.room.power_levels")) + assertEquals("m.room.create", 1, snapshotTypes.remove("m.room.create")) + assertEquals("m.room.member", 1, snapshotTypes.remove("m.room.member")) + // 50 from pagination + 1 context + assertEquals("m.room.message", 51, snapshotTypes.remove("m.room.message")) + assertEquals("Additional events found in timeline", setOf(), snapshotTypes.keys) } // Alice paginates once again FORWARD for 50 events @@ -152,8 +167,8 @@ class TimelineForwardPaginationTest : InstrumentedTest { val snapshot = runBlocking { aliceTimeline.awaitPaginate(Timeline.Direction.FORWARDS, 50) } - // 6 for room creation item (backward pagination),and numberOfMessagesToSend (all the message of the room) - snapshot.size == 6 + numberOfMessagesToSend && + // 7 for room creation item (backward pagination),and numberOfMessagesToSend (all the message of the room) + snapshot.size == 7 + numberOfMessagesToSend && snapshot.checkSendOrder(message, numberOfMessagesToSend, 0) // The timeline is fully loaded diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt index 6e5fed8df9..1a36adec44 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt @@ -74,8 +74,12 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { Timber.w(" event ${it.root}") } - // Ok, we have the 8 first messages of the initial sync (room creation and bob invite and join events) - snapshot.size == 8 + // Ok, we have the 9 first messages of the initial sync (room creation and bob invite and join events) + // create + // join alice + // power_levels, join_rules, history_visibility, guest_access, name + // invite, join bob + snapshot.size == 9 } bobTimeline.addListener(eventsListener) @@ -192,7 +196,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { Timber.w(" event ${it.root}") } - snapshot.size == 44 // 8 + 1 + 35 + snapshot.size == 45 // 9 + 1 + 35 } bobTimeline.addListener(eventsListener) @@ -220,8 +224,8 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { // Bob can see the first event of the room (so Back pagination has worked) snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE && - // 8 for room creation item 60 message from Alice - snapshot.size == 68 && // 8 + 60 + // 9 for room creation item 60 message from Alice + snapshot.size == 69 && // 9 + 60U snapshot.checkSendOrder(secondMessage, 30, 0) && snapshot.checkSendOrder(firstMessage, 30, 30) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt index 867e066e60..82f39806c0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt @@ -177,7 +177,7 @@ object MatrixPatterns { * - "@alice:domain.org".getDomain() will return "domain.org" * - "@bob:domain.org:3455".getDomain() will return "domain.org:3455" */ - fun String.getDomain(): String { + fun String.getServerName(): String { if (BuildConfig.DEBUG && !isUserId(this)) { // They are some invalid userId localpart in the wild, but the domain part should be there anyway Timber.w("Not a valid user ID: $this") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt index c2c1f043bb..c3f0221bb8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt @@ -195,7 +195,7 @@ data class HomeServerConnectionConfig( * - https://www.ssi.gouv.fr/uploads/2017/07/anssi-guide-recommandations_de_securite_relatives_a_tls-v1.2.pdf * - https://developer.android.com/reference/javax/net/ssl/SSLEngine * - * @param tlsLimitations true to use Tls limitations + * @param tlsLimitations true to use Tls limitations * @param enableCompatibilityMode set to true for Android < 20 * @return this builder */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt index e59e676ed9..20f977e86e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt @@ -36,7 +36,7 @@ interface ContentUrlResolver { /** * Get the actual URL for accessing the full-size image of a Matrix media content URI. * - * @param contentUrl the Matrix media content URI (in the form of "mxc://..."). + * @param contentUrl the Matrix media content URI (in the form of "mxc://..."). * @return the URL to access the described resource, or null if the url is invalid. */ fun resolveFullSize(contentUrl: String?): String? @@ -44,7 +44,7 @@ interface ContentUrlResolver { /** * Get the ResolvedMethod to download a URL. * - * @param contentUrl the Matrix media content URI (in the form of "mxc://..."). + * @param contentUrl the Matrix media content URI (in the form of "mxc://..."). * @param elementToDecrypt Encryption data may be required if you use a content scanner * @return the Method to access resource, or null if invalid */ @@ -54,9 +54,9 @@ interface ContentUrlResolver { * Get the actual URL for accessing the thumbnail image of a given Matrix media content URI. * * @param contentUrl the Matrix media content URI (in the form of "mxc://..."). - * @param width the desired width - * @param height the desired height - * @param method the desired method (METHOD_CROP or METHOD_SCALE) + * @param width the desired width + * @param height the desired height + * @param method the desired method (METHOD_CROP or METHOD_SCALE) * @return the URL to access the described resource, or null if the url is invalid. */ fun resolveThumbnail(contentUrl: String?, width: Int, height: Int, method: ThumbnailMethod): String? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerService.kt index 7a85a89058..22250628d5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerService.kt @@ -33,7 +33,7 @@ interface ContentScannerService { /** * Get the current public curve25519 key that the AV server is advertising. - * @param callback on success callback containing the server public key + * @param forceDownload true to force the SDK to download again the server public key */ suspend fun getServerPublicKey(forceDownload: Boolean = false): String? suspend fun getScanResultForAttachment(mxcUrl: String, fileInfo: ElementToDecrypt? = null): ScanStatusInfo diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt index 0d40490c3e..9029c7f8a3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt @@ -34,7 +34,7 @@ interface KeysBackupService { * Create a new keys backup version and enable it, using the information return from [prepareKeysBackupVersion]. * * @param keysBackupCreationInfo the info object from [prepareKeysBackupVersion]. - * @param callback Asynchronous callback + * @param callback Asynchronous callback */ fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, callback: MatrixCallback) @@ -122,7 +122,7 @@ interface KeysBackupService { * Delete a keys backup version. It will delete all backed up keys on the server, and the backup itself. * If we are backing up to this version. Backup will be stopped. * - * @param version the backup version to delete. + * @param version the backup version to delete. * @param callback Asynchronous callback */ fun deleteBackup(version: String, @@ -173,12 +173,12 @@ interface KeysBackupService { /** * Restore a backup with a recovery key from a given backup version stored on the homeserver. * - * @param keysVersionResult the backup version to restore from. - * @param recoveryKey the recovery key to decrypt the retrieved backup. - * @param roomId the id of the room to get backup data from. - * @param sessionId the id of the session to restore. + * @param keysVersionResult the backup version to restore from. + * @param recoveryKey the recovery key to decrypt the retrieved backup. + * @param roomId the id of the room to get backup data from. + * @param sessionId the id of the session to restore. * @param stepProgressListener the step progress listener - * @param callback Callback. It provides the number of found keys and the number of successfully imported keys. + * @param callback Callback. It provides the number of found keys and the number of successfully imported keys. */ fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, recoveryKey: String, roomId: String?, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt index 0c19d275cc..4ff196dd07 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt @@ -48,8 +48,7 @@ data class IncomingRoomKeyRequest( /** * Factory. * - * @param event the event - * @param currentTimeMillis the current time in milliseconds + * @param trail the AuditTrail data */ fun fromEvent(trail: AuditTrail): IncomingRoomKeyRequest? { return trail diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt index 744fe74d0d..736ae6b318 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt @@ -46,8 +46,8 @@ class MXUsersDevicesMap { /** * Provides the object for a device id and a user Id. * + * @param userId the user id * @param deviceId the device id - * @param userId the object id * @return the object */ fun getObject(userId: String?, deviceId: String?): E? { @@ -59,9 +59,9 @@ class MXUsersDevicesMap { /** * Set an object for a dedicated user Id and device Id. * - * @param userId the user Id + * @param userId the user Id * @param deviceId the device id - * @param o the object to set + * @param o the object to set */ fun setObject(userId: String?, deviceId: String?, o: E?) { if (null != o && userId?.isNotBlank() == true && deviceId?.isNotBlank() == true) { @@ -73,8 +73,8 @@ class MXUsersDevicesMap { /** * Defines the objects map for a user Id. * + * @param userId the user id * @param objectsPerDevices the objects maps - * @param userId the user id */ fun setObjects(userId: String?, objectsPerDevices: Map?) { if (!userId.isNullOrBlank()) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/CancelCode.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/CancelCode.kt index 5a025f37e1..e4716d7794 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/CancelCode.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/CancelCode.kt @@ -28,7 +28,8 @@ enum class CancelCode(val value: String, val humanReadable: String) { MismatchedKeys("m.key_mismatch", "Key mismatch"), UserError("m.user_error", "User error"), MismatchedUser("m.user_mismatch", "User mismatch"), - QrCodeInvalid("m.qr_code.invalid", "Invalid QR code") + QrCodeInvalid("m.qr_code.invalid", "Invalid QR code"), + AcceptedByAnotherDevice("m.accepted", "Verification request accepted by another device") } fun safeValueOf(code: String?): CancelCode { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 16bdbd3432..7124d8a1a3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent @@ -375,11 +376,11 @@ fun Event.getRelationContent(): RelationDefaultContent? { content.toModel()?.relatesTo } else { content.toModel()?.relatesTo ?: run { - // Special case to handle stickers, while there is only a local msgtype for stickers - if (getClearType() == EventType.STICKER) { - getClearContent().toModel()?.relatesTo - } else { - null + // Special cases when there is only a local msgtype for some event types + when (getClearType()) { + EventType.STICKER -> getClearContent().toModel()?.relatesTo + in EventType.BEACON_LOCATION_DATA -> getClearContent().toModel()?.relatesTo + else -> null } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt index 84a9990826..a7c81136e3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt @@ -33,7 +33,7 @@ interface FileService { /** * The original file is in cache, but the decrypted files can be deleted for security reason. * To decrypt the file again, call [downloadFile], the encrypted file will not be downloaded again - * @param decryptedFileInCache true if the decrypted file is available. Always true for clear files. + * @property decryptedFileInCache true if the decrypted file is available. Always true for clear files. */ data class InCache(val decryptedFileInCache: Boolean) : FileState() object Downloading : FileState() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt index c03b42e6c8..2fb35d38e3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt @@ -74,6 +74,7 @@ interface IdentityService { /** * Submit the code that the identity server has sent to the user (in email or SMS). * Once successful, you will have to call [finalizeBindThreePid] + * @param threePid the three pid * @param code the code sent to the user */ suspend fun submitValidationToken(threePid: ThreePid, code: String) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerService.kt index 60af93888e..5b15a0cb13 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerService.kt @@ -99,6 +99,7 @@ interface IntegrationManagerService { * Offers to allow or disallow a native widget domain. * @param widgetType the widget type to check for * @param domain the domain to check for + * @param allowed true or false */ suspend fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt index c5d919407a..c428e40203 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt @@ -29,6 +29,7 @@ object MatrixLinkify { * Find the matrix spans i.e matrix id , user id ... to display them as URL. * * @param spannable the text in which the matrix items has to be clickable. + * @param callback listener to be notified when the span is clicked */ @Suppress("UNUSED_PARAMETER") fun addLinks(spannable: Spannable, callback: MatrixPermalinkSpan.Callback?): Boolean { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixPermalinkSpan.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixPermalinkSpan.kt index 2f8f5f99a5..48b30dfa21 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixPermalinkSpan.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixPermalinkSpan.kt @@ -22,8 +22,8 @@ import org.matrix.android.sdk.api.session.permalinks.MatrixPermalinkSpan.Callbac /** * This MatrixPermalinkSpan is a clickable span which use a [Callback] to communicate back. - * @param url the permalink url tied to the span - * @param callback the callback to use. + * @property url the permalink url tied to the span + * @property callback the callback to use. */ class MatrixPermalinkSpan(private val url: String, private val callback: Callback? = null) : ClickableSpan() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt index b49b80df09..1788bf7bd2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt @@ -60,6 +60,7 @@ interface PermalinkService { * Creates a permalink for a roomId, including the via parameters. * * @param roomId the room id + * @param viaServers the via parameter * @param forceMatrixTo whether we should force using matrix.to base URL * * @return the permalink, or null in case of error @@ -70,7 +71,7 @@ interface PermalinkService { * Creates a permalink for an event. If you have an event you can use [createPermalink] * Ex: "https://matrix.to/#/!nbzmcXAqpxBXjAdgoX:matrix.org/$1531497316352799BevdV:matrix.org?via=matrix.org" * - * @param roomId the id of the room + * @param roomId the id of the room * @param eventId the id of the event * @param forceMatrixTo whether we should force using matrix.to base URL * @@ -90,7 +91,7 @@ interface PermalinkService { * Creates a HTML or Markdown mention span template. Can be used to replace a mention with a permalink to mentioned user. * Ex: "%2\$s" or "[%2\$s](https://matrix.to/#/%1\$s)" * - * @param type: type of template to create + * @param type type of template to create * @param forceMatrixTo whether we should force using matrix.to base URL * * @return the created template diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt index 5f9857eb2f..5cb7857021 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt @@ -47,12 +47,12 @@ interface PushersService { * Add a new Email pusher. * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set * - * @param email The email address to send notifications to. - * @param lang The preferred language for receiving notifications (e.g. "en" or "en-US"). - * @param emailBranding The branding placeholder to include in the email communications. - * @param appDisplayName A human readable string that will allow the user to identify what application owns this pusher. + * @param email The email address to send notifications to. + * @param lang The preferred language for receiving notifications (e.g. "en" or "en-US"). + * @param emailBranding The branding placeholder to include in the email communications. + * @param appDisplayName A human readable string that will allow the user to identify what application owns this pusher. * @param deviceDisplayName A human readable string that will allow the user to identify what device owns this pusher. - * @param append If true, the homeserver should add another pusher with the given pushkey and App ID in addition + * @param append If true, the homeserver should add another pusher with the given pushkey and App ID in addition * to any others with different user IDs. Otherwise, the homeserver must remove any other pushers * with the same App ID and pushkey for different users. Typically We always want to append for * email pushers since we don't want to stop other accounts notifying to the same email address. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt index bc4860be11..7ffbc89559 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt @@ -34,10 +34,11 @@ interface PushRuleService { /** * Enables/Disables a push rule and updates the actions if necessary. + * @param kind the rule kind + * @param ruleId the rule id * @param enable Enables/Disables the rule * @param actions Actions to update if not null */ - suspend fun updatePushRuleActions(kind: RuleKind, ruleId: String, enable: Boolean, actions: List?) suspend fun removePushRule(kind: RuleKind, ruleId: String) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt index 5bf42b8252..9498ed002c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt @@ -67,7 +67,7 @@ data class RuleSet( /** * Find a rule from its rule Id. * - * @param rules the rules list. + * @param rules the rules list. * @param ruleId the rule Id. * @return the bing rule if it exists, else null. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/crypto/RoomCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/crypto/RoomCryptoService.kt index 6967e0c455..6064643820 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/crypto/RoomCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/crypto/RoomCryptoService.kt @@ -28,7 +28,8 @@ interface RoomCryptoService { /** * Enable encryption of the room. - * @param Use force to ensure that this algorithm will be used. Otherwise this call + * @param algorithm the algorithm to set, default to [MXCRYPTO_ALGORITHM_MEGOLM] + * @param force Use force to ensure that this algorithm will be used. Otherwise this call * will throw if encryption is already setup or if the algorithm is not supported. Only to * be used by admins to fix misconfigured encryption. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt index 0d094b835b..02c597ee63 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt @@ -71,8 +71,8 @@ interface RelationService { /** * Edit a poll. - * @param pollType indicates open or closed polls * @param targetEvent The poll event to edit + * @param pollType indicates open or closed polls * @param question The edited question * @param options The edited options */ @@ -84,7 +84,9 @@ interface RelationService { /** * Edit a text message body. Limited to "m.text" contentType. * @param targetEvent The event to edit + * @param msgType the message type * @param newBodyText The edited body + * @param newBodyAutoMarkdown true to parse markdown on the new body * @param compatibilityBodyText The text that will appear on clients that don't support yet edition */ fun editTextMessage(targetEvent: TimelineEvent, @@ -153,8 +155,8 @@ interface RelationService { * @param rootThreadEventId the root thread eventId * @param replyInThreadText the reply text * @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE - * @param formattedText The formatted body using MessageType#FORMAT_MATRIX_HTML * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present + * @param formattedText The formatted body using MessageType#FORMAT_MATRIX_HTML * @param eventReplied the event referenced by the reply within a thread */ fun replyInThread(rootThreadEventId: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt index 165a912b7f..36993074aa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt @@ -58,7 +58,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { /** * Tell if an user can send an event of a certain type. * - * @param userId the id of the user to check for. + * @param userId the id of the user to check for. * @param isState true if the event is a state event (ie. state key is not null) * @param eventType the event type to check for * @return true if the user can send this type of event diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt index 036628c02f..dac1a1a773 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt @@ -71,7 +71,7 @@ interface ReadService { /** * Returns a live list of read receipts for a given event. - * @param eventId: the event + * @param eventId the event */ fun getEventReadReceiptsLive(eventId: String): LiveData> } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index 4bb8abef8a..c2e3ded2fa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -62,6 +62,7 @@ interface SendService { * @param quotedEvent The event to which we will quote it's content. * @param text the text message to send * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present + * @param rootThreadEventId when this param is not null, the message will be sent in this specific thread * @return a [Cancelable] */ fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean, rootThreadEventId: String? = null): Cancelable diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt index f6b56128d3..c79171f156 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt @@ -90,23 +90,29 @@ interface StateService { /** * Get a state event of the room. + * @param eventType An eventType. + * @param stateKey the query which will be done on the stateKey */ fun getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event? /** * Get a live state event of the room. + * @param eventType An eventType. + * @param stateKey the query which will be done on the stateKey */ fun getStateEventLive(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData> /** * Get state events of the room. * @param eventTypes Set of eventType. If empty, all state events will be returned + * @param stateKey the query which will be done on the stateKey */ fun getStateEvents(eventTypes: Set, stateKey: QueryStringValue = QueryStringValue.NoCondition): List /** * Get live state events of the room. * @param eventTypes Set of eventType to observe. If empty, all state events will be observed + * @param stateKey the query which will be done on the stateKey */ fun getStateEventsLive(eventTypes: Set, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index b87bc25435..d4ade9b5b9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent @@ -89,6 +90,7 @@ data class TimelineEvent( /** * Get the metadata associated with a key. + * @param T type to cast the metadata to * @param key the key to get the metadata * @return the metadata */ @@ -140,6 +142,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? { EventType.STICKER -> root.getClearContent().toModel() in EventType.POLL_START -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() in EventType.STATE_ROOM_BEACON_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() + in EventType.BEACON_LOCATION_DATA -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt index 528e071966..e3a9860523 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt @@ -92,7 +92,7 @@ interface SharedSecretStorageService { * Clients MUST ensure that the key is trusted before using it to encrypt secrets. * * @param name The name of the secret - * @param secret The secret contents. + * @param secretBase64 The secret contents. * @param keys The list of (ID,privateKey) of the keys to use to encrypt the secret. */ suspend fun storeSecret(name: String, secretBase64: String, keys: List) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt index 8f16b3b9c3..38e55664d2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt @@ -44,8 +44,8 @@ interface SpaceService { roomAliasLocalPart: String? = null): String /** - * Get a space from a roomId. - * @param spaceId the roomId to look for. + * Get a space from a spaceId. + * @param spaceId the spaceId to look for. * @return a space with spaceId or null if room type is not space */ fun getSpace(spaceId: String): Space? @@ -54,21 +54,24 @@ interface SpaceService { * Try to resolve (peek) rooms and subspace in this space. * Use this call get preview of children of this space, particularly useful to get a * preview of rooms that you did not join yet. + * @param spaceId the spaceId to look for. */ suspend fun peekSpace(spaceId: String): SpacePeekResult /** * Get's information of a space by querying the server. + * + * @param spaceId the spaceId to look for. * @param suggestedOnly If true, return only child events and rooms where the m.space.child event has suggested: true. * @param limit a client-defined limit to the maximum number of rooms to return per page. Must be a non-negative integer. - * @param from: Optional. Pagination token given to retrieve the next set of rooms. Note that if a pagination token is provided, + * @param from Optional. Pagination token given to retrieve the next set of rooms. Note that if a pagination token is provided, * then the parameters given for suggested_only and max_depth must be the same. + * @param knownStateList when paginating, pass back the m.space.child state events */ suspend fun querySpaceChildren(spaceId: String, suggestedOnly: Boolean? = null, limit: Int? = null, from: String? = null, - // when paginating, pass back the m.space.child state events knownStateList: List? = null): SpaceHierarchyData /** @@ -98,7 +101,10 @@ interface SpaceService { /** * Let this room declare that it has a parent. + * @param childRoomId the space to set as a child + * @param parentSpaceId the parentId which will be set * @param canonical true if it should be the main parent of this room + * @param viaServers list of candidate servers that can be used to set the parent * In practice, well behaved rooms should only have one canonical parent, but given this is not enforced: * if multiple are present the client should select the one with the lowest room ID, as determined via a lexicographic utf-8 ordering. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetPostAPIMediator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetPostAPIMediator.kt index edb49f4797..0c224ff17c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetPostAPIMediator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetPostAPIMediator.kt @@ -49,7 +49,7 @@ interface WidgetPostAPIMediator { /** * Send a boolean response. * - * @param response the response + * @param response the response * @param eventData the modular data */ fun sendBoolResponse(response: Boolean, eventData: JsonDict) @@ -57,7 +57,7 @@ interface WidgetPostAPIMediator { /** * Send an integer response. * - * @param response the response + * @param response the response * @param eventData the modular data */ fun sendIntegerResponse(response: Int, eventData: JsonDict) @@ -65,8 +65,9 @@ interface WidgetPostAPIMediator { /** * Send an object response. * - * @param klass the class of the response - * @param response the response + * @param T the generic type + * @param type the type of the response + * @param response the response * @param eventData the modular data */ fun sendObjectResponse(type: Type, response: T?, eventData: JsonDict) @@ -81,7 +82,7 @@ interface WidgetPostAPIMediator { /** * Send an error. * - * @param message the error message + * @param message the error message * @param eventData the modular data */ fun sendError(message: String, eventData: JsonDict) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt index b06f8f7bc6..8ad6500d25 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt @@ -111,8 +111,8 @@ interface WidgetService { /** * Deactivate a widget in a room. It makes sure you have the rights to handle this. * - * @param roomId: the room where you want to deactivate the widget. - * @param widgetId: the widget to deactivate. + * @param roomId the room where you want to deactivate the widget. + * @param widgetId the widget to deactivate. */ suspend fun destroyRoomWidget(roomId: String, widgetId: String) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index f1cfe3fee5..02dfce04b5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -20,7 +20,7 @@ import android.net.Uri import dagger.Lazy import okhttp3.OkHttpClient import org.matrix.android.sdk.api.MatrixPatterns -import org.matrix.android.sdk.api.MatrixPatterns.getDomain +import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig @@ -381,7 +381,7 @@ internal class DefaultAuthenticationService @Inject constructor( return getWellknownTask.execute( GetWellknownTask.Params( - domain = matrixId.getDomain(), + domain = matrixId.getServerName().substringBeforeLast(":"), homeServerConnectionConfig = homeServerConnectionConfig.orWellKnownDefaults() ) ) 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 b3e9eab988..eee1ee70aa 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 @@ -46,6 +46,7 @@ internal class CryptoSessionInfoProvider @Inject constructor( } /** + * @param roomId the room Id * @param allActive if true return joined as well as invited, if false, only joined */ fun getRoomUserIds(roomId: String, allActive: Boolean): List { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 11fa93dbe0..824478f1d3 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -508,7 +508,7 @@ internal class DefaultCryptoService @Inject constructor( /** * Provides the device information for a user id and a device Id. * - * @param userId the user id + * @param userId the user id * @param deviceId the device id */ override fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? { @@ -538,7 +538,7 @@ internal class DefaultCryptoService @Inject constructor( /** * Set the devices as known. * - * @param devices the devices. Note that the verified member of the devices in this list will not be updated by this method. + * @param devices the devices. Note that the verified member of the devices in this list will not be updated by this method. * @param callback the asynchronous callback */ override fun setDevicesKnown(devices: List, callback: MatrixCallback?) { @@ -576,8 +576,8 @@ internal class DefaultCryptoService @Inject constructor( * Update the blocked/verified state of the given device. * * @param trustLevel the new trust level - * @param userId the owner of the device - * @param deviceId the unique identifier for the device. + * @param userId the owner of the device + * @param deviceId the unique identifier for the device. */ override fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) { setDeviceVerificationAction.handle(trustLevel, userId, deviceId) @@ -586,10 +586,10 @@ internal class DefaultCryptoService @Inject constructor( /** * Configure a room to use encryption. * - * @param roomId the room id to enable encryption in. - * @param algorithm the encryption config for the room. + * @param roomId the room id to enable encryption in. + * @param algorithm the encryption config for the room. * @param inhibitDeviceQuery true to suppress device list query for users in the room (for now) - * @param membersId list of members to start tracking their devices + * @param membersId list of members to start tracking their devices * @return true if the operation succeeds. */ private suspend fun setEncryptionInRoom(roomId: String, @@ -687,9 +687,9 @@ internal class DefaultCryptoService @Inject constructor( * Encrypt an event content according to the configuration of the room. * * @param eventContent the content of the event. - * @param eventType the type of the event. - * @param roomId the room identifier the event will be sent. - * @param callback the asynchronous callback + * @param eventType the type of the event. + * @param roomId the room identifier the event will be sent. + * @param callback the asynchronous callback */ override fun encryptEventContent(eventContent: Content, eventType: String, @@ -742,7 +742,7 @@ internal class DefaultCryptoService @Inject constructor( /** * Decrypt an event. * - * @param event the raw event. + * @param event the raw event. * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. * @return the MXEventDecryptionResult data, or throw in case of error */ @@ -754,7 +754,7 @@ internal class DefaultCryptoService @Inject constructor( /** * Decrypt an event asynchronously. * - * @param event the raw event. + * @param event the raw event. * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. * @param callback the callback to return data or null */ @@ -765,7 +765,7 @@ internal class DefaultCryptoService @Inject constructor( /** * Decrypt an event. * - * @param event the raw event. + * @param event the raw event. * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. * @return the MXEventDecryptionResult data, or null in case of error */ @@ -905,6 +905,7 @@ internal class DefaultCryptoService @Inject constructor( /** * Handle an m.room.encryption event. * + * @param roomId the room Id * @param event the encryption event. */ private fun onRoomEncryptionEvent(roomId: String, event: Event) { @@ -928,6 +929,7 @@ internal class DefaultCryptoService @Inject constructor( /** * Handle a change in the membership state of a member of a room. * + * @param roomId the room Id * @param event the membership event causing the change */ private fun onRoomMembershipEvent(roomId: String, event: Event) { @@ -996,7 +998,7 @@ internal class DefaultCryptoService @Inject constructor( /** * Export the crypto keys. * - * @param password the password + * @param password the password * @param anIterationCount the encryption iteration count (0 means no encryption) */ private suspend fun exportRoomKeys(password: String, anIterationCount: Int): ByteArray { @@ -1015,8 +1017,8 @@ internal class DefaultCryptoService @Inject constructor( /** * Import the room keys. * - * @param roomKeysAsArray the room keys as array. - * @param password the password + * @param roomKeysAsArray the room keys as array. + * @param password the password * @param progressListener the progress listener * @return the result ImportRoomKeysResult */ @@ -1066,7 +1068,7 @@ internal class DefaultCryptoService @Inject constructor( * A success means there is no unknown devices. * If there are some unknown devices, a MXCryptoError.UnknownDevice exception is triggered. * - * @param userIds the user ids list + * @param userIds the user ids list * @param callback the asynchronous callback. */ fun checkUnknownDevices(userIds: List, callback: MatrixCallback) { @@ -1089,7 +1091,7 @@ internal class DefaultCryptoService @Inject constructor( * If false, it can still be overridden per-room. * If true, it overrides the per-room settings. * - * @param block true to unilaterally blacklist all + * @param block true to unilaterally blacklist all */ override fun setGlobalBlacklistUnverifiedDevices(block: Boolean) { cryptoStore.setGlobalBlacklistUnverifiedDevices(block) @@ -1129,8 +1131,8 @@ internal class DefaultCryptoService @Inject constructor( /** * Manages the room black-listing for unverified devices. * - * @param roomId the room id - * @param add true to add the room id to the list, false to remove it. + * @param roomId the room id + * @param add true to add the room id to the list, false to remove it. */ private fun setRoomBlacklistUnverifiedDevices(roomId: String, add: Boolean) { val roomIds = cryptoStore.getRoomsListBlacklistUnverifiedDevices().toMutableList() @@ -1149,7 +1151,7 @@ internal class DefaultCryptoService @Inject constructor( /** * Add this room to the ones which don't encrypt messages to unverified devices. * - * @param roomId the room id + * @param roomId the room id */ override fun setRoomBlacklistUnverifiedDevices(roomId: String) { setRoomBlacklistUnverifiedDevices(roomId, true) @@ -1158,7 +1160,7 @@ internal class DefaultCryptoService @Inject constructor( /** * Remove this room to the ones which don't encrypt messages to unverified devices. * - * @param roomId the room id + * @param roomId the room id */ override fun setRoomUnBlacklistUnverifiedDevices(roomId: String) { setRoomBlacklistUnverifiedDevices(roomId, false) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index cd4e2a6d52..18b815b3d8 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -170,7 +170,7 @@ internal class DeviceListManager @Inject constructor( * Update the devices list statuses. * * @param changed the user ids list which have new devices - * @param left the user ids list which left a room + * @param left the user ids list which left a room */ fun handleDeviceListsChanges(changed: Collection, left: Collection) { Timber.v("## CRYPTO: handleDeviceListsChanges changed: ${changed.logLimit()} / left: ${left.logLimit()}") @@ -223,7 +223,7 @@ internal class DeviceListManager @Inject constructor( /** * The keys download succeeded. * - * @param userIds the userIds list + * @param userIds the userIds list * @param failures the failure map. */ private fun onKeysDownloadSucceed(userIds: List, failures: Map>?): MXUsersDevicesMap { @@ -276,7 +276,7 @@ internal class DeviceListManager @Inject constructor( * Download the device keys for a list of users and stores the keys in the MXStore. * It must be called in getEncryptingThreadHandler() thread. * - * @param userIds The users to fetch. + * @param userIds The users to fetch. * @param forceDownload Always download the keys even if cached. */ suspend fun downloadKeys(userIds: List?, forceDownload: Boolean): MXUsersDevicesMap { @@ -421,9 +421,9 @@ internal class DeviceListManager @Inject constructor( * Validate device keys. * This method must called on getEncryptingThreadHandler() thread. * - * @param deviceKeys the device keys to validate. - * @param userId the id of the user of the device. - * @param deviceId the id of the device. + * @param deviceKeys the device keys to validate. + * @param userId the id of the user of the device. + * @param deviceId the id of the device. * @param previouslyStoredDeviceKeys the device keys we received before for this device * @return true if succeeds */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt index d6f881211c..cb61bbe1de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt @@ -72,7 +72,7 @@ internal class EventDecryptor @Inject constructor( /** * Decrypt an event. * - * @param event the raw event. + * @param event the raw event. * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. * @return the MXEventDecryptionResult data, or throw in case of error */ @@ -84,7 +84,7 @@ internal class EventDecryptor @Inject constructor( /** * Decrypt an event asynchronously. * - * @param event the raw event. + * @param event the raw event. * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. * @param callback the callback to return data or null */ @@ -100,7 +100,7 @@ internal class EventDecryptor @Inject constructor( /** * Decrypt an event. * - * @param event the raw event. + * @param event the raw event. * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. * @return the MXEventDecryptionResult data, or null in case of error */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt index e4a0f0376e..e0d6c25d70 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt @@ -68,7 +68,7 @@ internal object MXMegolmExportEncryption { /** * Decrypt a megolm key file. * - * @param data the data to decrypt + * @param data the data to decrypt * @param password the password. * @return the decrypted output. * @throws Exception the failure reason @@ -138,9 +138,9 @@ internal object MXMegolmExportEncryption { /** * Encrypt a string into the megolm export format. * - * @param data the data to encrypt. - * @param password the password - * @param kdf_rounds the iteration count + * @param data the data to encrypt. + * @param password the password + * @param kdfRounds the iteration count * @return the encrypted data * @throws Exception the failure reason */ @@ -304,9 +304,9 @@ internal object MXMegolmExportEncryption { /** * Derive the AES and HMAC-SHA-256 keys for the file. * - * @param salt salt for pbkdf + * @param salt salt for pbkdf * @param iterations number of pbkdf iterations - * @param password password + * @param password password * @return the derived keys */ @Throws(Exception::class) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt index 779113a7bc..37d9a9f567 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt @@ -261,7 +261,7 @@ internal class MXOlmDevice @Inject constructor( * The new session will be stored in the MXStore. * * @param theirIdentityKey the remote user's Curve25519 identity key - * @param theirOneTimeKey the remote user's one-time Curve25519 key + * @param theirOneTimeKey the remote user's one-time Curve25519 key * @return the session id for the outbound session. */ fun createOutboundSession(theirIdentityKey: String, theirOneTimeKey: String): String? { @@ -300,8 +300,8 @@ internal class MXOlmDevice @Inject constructor( * Generate a new inbound session, given an incoming message. * * @param theirDeviceIdentityKey the remote user's Curve25519 identity key. - * @param messageType the message_type field from the received message (must be 0). - * @param ciphertext base64-encoded body from the received message. + * @param messageType the message_type field from the received message (must be 0). + * @param ciphertext base64-encoded body from the received message. * @return {{payload: string, session_id: string}} decrypted payload, and session id of new session. */ fun createInboundSession(theirDeviceIdentityKey: String, messageType: Int, ciphertext: String): Map? { @@ -395,8 +395,8 @@ internal class MXOlmDevice @Inject constructor( * Encrypt an outgoing message using an existing session. * * @param theirDeviceIdentityKey the Curve25519 identity key for the remote device. - * @param sessionId the id of the active session - * @param payloadString the payload to be encrypted and sent + * @param sessionId the id of the active session + * @param payloadString the payload to be encrypted and sent * @return the cipher text */ suspend fun encryptMessage(theirDeviceIdentityKey: String, sessionId: String, payloadString: String): Map? { @@ -428,10 +428,10 @@ internal class MXOlmDevice @Inject constructor( /** * Decrypt an incoming message using an existing session. * - * @param ciphertext the base64-encoded body from the received message. - * @param messageType message_type field from the received message. + * @param ciphertext the base64-encoded body from the received message. + * @param messageType message_type field from the received message. + * @param sessionId the id of the active session. * @param theirDeviceIdentityKey the Curve25519 identity key for the remote device. - * @param sessionId the id of the active session. * @return the decrypted payload. */ @kotlin.jvm.Throws @@ -461,9 +461,9 @@ internal class MXOlmDevice @Inject constructor( * Determine if an incoming messages is a prekey message matching an existing session. * * @param theirDeviceIdentityKey the Curve25519 identity key for the remote device. - * @param sessionId the id of the active session. - * @param messageType message_type field from the received message. - * @param ciphertext the base64-encoded body from the received message. + * @param sessionId the id of the active session. + * @param messageType message_type field from the received message. + * @param ciphertext the base64-encoded body from the received message. * @return YES if the received message is a prekey message which matchesthe given session. */ fun matchesSession(theirDeviceIdentityKey: String, sessionId: String, messageType: Int, ciphertext: String): Boolean { @@ -564,7 +564,7 @@ internal class MXOlmDevice @Inject constructor( /** * Encrypt an outgoing message with an outbound group session. * - * @param sessionId the id of the outbound group session. + * @param sessionId the id of the outbound group session. * @param payloadString the payload to be encrypted and sent. * @return ciphertext */ @@ -591,13 +591,13 @@ internal class MXOlmDevice @Inject constructor( /** * Add an inbound group session to the session store. * - * @param sessionId the session identifier. - * @param sessionKey base64-encoded secret key. - * @param roomId the id of the room in which this session will be used. - * @param senderKey the base64-encoded curve25519 key of the sender. + * @param sessionId the session identifier. + * @param sessionKey base64-encoded secret key. + * @param roomId the id of the room in which this session will be used. + * @param senderKey the base64-encoded curve25519 key of the sender. * @param forwardingCurve25519KeyChain Devices involved in forwarding this session to us. - * @param keysClaimed Other keys the sender claims. - * @param exportFormat true if the megolm keys are in export format + * @param keysClaimed Other keys the sender claims. + * @param exportFormat true if the megolm keys are in export format * @return true if the operation succeeds. */ fun addInboundGroupSession(sessionId: String, @@ -840,9 +840,9 @@ internal class MXOlmDevice @Inject constructor( /** * Verify an ed25519 signature on a JSON object. * - * @param key the ed25519 key. + * @param key the ed25519 key. * @param jsonDictionary the JSON object which was signed. - * @param signature the base64-encoded signature to be checked. + * @param signature the base64-encoded signature to be checked. * @throws Exception the exception */ @Throws(Exception::class) @@ -865,7 +865,7 @@ internal class MXOlmDevice @Inject constructor( * Search an OlmSession. * * @param theirDeviceIdentityKey the device key - * @param sessionId the session Id + * @param sessionId the session Id * @return the olm session */ private fun getSessionForDevice(theirDeviceIdentityKey: String, sessionId: String): OlmSessionWrapper? { @@ -879,9 +879,9 @@ internal class MXOlmDevice @Inject constructor( * Extract an InboundGroupSession from the session store and do some check. * inboundGroupSessionWithIdError describes the failure reason. * - * @param roomId the room where the session is used. * @param sessionId the session identifier. * @param senderKey the base64-encoded curve25519 key of the sender. + * @param roomId the room where the session is used. * @return the inbound group session. */ fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): InboundGroupSessionHolder { @@ -911,7 +911,7 @@ internal class MXOlmDevice @Inject constructor( /** * Determine if we have the keys for a given megolm session. * - * @param roomId room in which the message was received + * @param roomId room in which the message was received * @param senderKey base64-encoded curve25519 key of the sender * @param sessionId session identifier * @return true if the unbound session keys are known. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt index fe280416ea..4401a07192 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt @@ -39,7 +39,7 @@ internal class OlmSessionStore @Inject constructor(private val store: IMXCryptoS * Store a session between our own device and another device. * This will be called after the session has been created but also every time it has been used * in order to persist the correct state for next run - * @param olmSessionWrapper the end-to-end session. + * @param olmSessionWrapper the end-to-end session. * @param deviceKey the public key of the other device. */ @Synchronized diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt index c2f494b4b3..a80bafbe79 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt @@ -49,7 +49,7 @@ internal class RoomDecryptorProvider @Inject constructor( * If we already have a decryptor for the given room and algorithm, return * it. Otherwise try to instantiate it. * - * @param roomId the room id + * @param roomId the room id * @param algorithm the crypto algorithm * @return the decryptor * // TODO Create another method for the case of roomId is null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt index fc211537a6..4c5720daf2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt @@ -29,7 +29,7 @@ internal class EnsureOlmSessionsForUsersAction @Inject constructor(private val o /** * Try to make sure we have established olm sessions for the given users. - * @param users a list of user ids. + * @param users a list of user ids. */ suspend fun handle(users: List): MXUsersDevicesMap { Timber.v("## ensureOlmSessionsForUsers() : ensureOlmSessionsForUsers $users") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt index 22c4e59b18..67d73c21ed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt @@ -45,8 +45,8 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi * Must be call on the crypto coroutine thread * * @param megolmSessionsData megolm sessions. - * @param fromBackup true if the imported keys are already backed up on the server. - * @param progressListener the progress listener + * @param fromBackup true if the imported keys are already backed up on the server. + * @param progressListener the progress listener * @return import room keys result */ @WorkerThread diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt index 9bbbab4992..919e38c391 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt @@ -42,7 +42,7 @@ internal class MessageEncrypter @Inject constructor( * This method must be called from the getCryptoHandler() thread. * * @param payloadFields fields to include in the encrypted payload. - * @param deviceInfos list of device infos to encrypt for. + * @param deviceInfos list of device infos to encrypt for. * @return the content for an m.room.encrypted event. */ suspend fun encryptMessage(payloadFields: Content, deviceInfos: List): EncryptedMessage { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt index 34006ecfde..6847a46369 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt @@ -29,7 +29,7 @@ internal interface IMXDecrypting { /** * Decrypt an event. * - * @param event the raw event. + * @param event the raw event. * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. * @return the decryption information, or an error */ @@ -40,6 +40,7 @@ internal interface IMXDecrypting { * Handle a key event. * * @param event the key event. + * @param defaultKeysBackupService the keys backup service */ fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {} } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt index 1d84120208..73ce5a5004 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt @@ -27,8 +27,8 @@ internal interface IMXEncrypting { * Encrypt an event content according to the configuration of the room. * * @param eventContent the content of the event. - * @param eventType the type of the event. - * @param userIds the room members the event will be sent to. + * @param eventType the type of the event. + * @param userIds the room members the event will be sent to. * @return the encrypted content */ suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List): Content diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt index 6f488def0a..8cf01f1972 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt @@ -38,7 +38,7 @@ internal interface IMXGroupEncryption { * Re-shares a session key with devices if the key has already been * sent to them. * - * @param sessionId The id of the outbound session to share. + * @param groupSessionId The id of the outbound session to share. * @param userId The id of the user who owns the target device. * @param deviceId The id of the target device. * @param senderKey The key of the originating device for the session. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index c7f8acbff3..722462bf0e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -177,6 +177,7 @@ internal class MXMegolmDecryption( * Handle a key event. * * @param event the key event. + * @param defaultKeysBackupService the keys backup service */ override fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) { Timber.tag(loggerTag.value).v("onRoomKeyEvent()") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index 79e907945f..8b4e9df607 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -198,7 +198,7 @@ internal class MXMegolmEncryption( /** * Share the device key to a list of users. * - * @param session the session info + * @param session the session info * @param devicesByUsers the devices map */ private suspend fun shareKey(session: MXOutboundSessionInfo, @@ -227,7 +227,7 @@ internal class MXMegolmEncryption( /** * Share the device keys of a an user. * - * @param session the session info + * @param session the session info * @param devicesByUser the devices map */ private suspend fun shareUserDevicesKey(session: MXOutboundSessionInfo, @@ -387,7 +387,7 @@ internal class MXMegolmEncryption( * Get the list of devices which can encrypt data to. * This method must be called in getDecryptingThreadHandler() thread. * - * @param userIds the user ids whose devices must be checked. + * @param userIds the user ids whose devices must be checked. */ private suspend fun getDevicesInRoom(userIds: List): DeviceInRoomInfo { // We are happy to use a cached version here: we assume that if we already diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt index 1e66fe84c9..23c8f0e905 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt @@ -180,8 +180,8 @@ internal class MXOlmDecryption( /** * Attempt to decrypt an Olm message. * + * @param message message object, with 'type' and 'body' fields. * @param theirDeviceIdentityKey the Curve25519 identity key of the sender. - * @param message message object, with 'type' and 'body' fields. * @return payload, if decrypted successfully. */ private suspend fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt index 3c9706abe1..bde1d65093 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt @@ -70,7 +70,7 @@ internal class MXOlmEncryption( /** * Ensure that the session. * - * @param users the user ids list + * @param users the user ids list */ private suspend fun ensureSession(users: List) { deviceListManager.downloadKeys(users, false) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt index f21f5e05e1..f5ead35933 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt @@ -103,7 +103,7 @@ internal interface CryptoApi { * Claim one-time keys. * Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-keys-claim * - * @param params the params. + * @param body the Json body. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/claim") suspend fun claimOneTimeKeysForUsersDevices(@Body body: KeysClaimBody): KeysClaimResponse @@ -112,9 +112,9 @@ internal interface CryptoApi { * Send an event to a specific list of devices * Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#put-matrix-client-r0-sendtodevice-eventtype-txnid * - * @param eventType the type of event to send + * @param eventType the type of event to send * @param transactionId the transaction ID for this event - * @param body the body + * @param body the body */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sendToDevice/{eventType}/{txnId}") suspend fun sendToDevice(@Path("eventType") eventType: String, @@ -126,7 +126,7 @@ internal interface CryptoApi { * Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#delete-matrix-client-r0-devices-deviceid * * @param deviceId the device id - * @param params the deletion parameters + * @param params the deletion parameters */ @HTTP(path = NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{device_id}", method = "DELETE", hasBody = true) suspend fun deleteDevice(@Path("device_id") deviceId: String, @@ -137,7 +137,7 @@ internal interface CryptoApi { * Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#put-matrix-client-r0-devices-deviceid * * @param deviceId the device id - * @param params the params + * @param params the params */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{device_id}") suspend fun updateDeviceInfo(@Path("device_id") deviceId: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt index b4cbd15109..7ff08cd127 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt @@ -159,6 +159,7 @@ internal object MXEncryptedAttachments { * Encrypt an attachment stream. * DO NOT USE for big files, it will load all in memory * @param attachmentStream the attachment stream. Will be closed after this method call. + * @param clock a clock to retrieve current time * @return the encryption file info */ fun encryptAttachment(attachmentStream: InputStream, clock: Clock): EncryptionResult { @@ -231,7 +232,8 @@ internal object MXEncryptedAttachments { * * @param attachmentStream the attachment stream. Will be closed after this method call. * @param elementToDecrypt the elementToDecrypt info - * @param outputStream the outputStream where the decrypted attachment will be write. + * @param outputStream the outputStream where the decrypted attachment will be write. + * @param clock a clock to retrieve current time * @return true in case of success, false in case of error */ fun decryptAttachment(attachmentStream: InputStream?, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt index 5ea4695da2..813adf7459 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt @@ -1105,6 +1105,7 @@ internal class DefaultKeysBackupService @Inject constructor( * * @param password the password. * @param keysBackupData the backup and its auth data. + * @param progressListener listener to track progress * * @return the recovery key if successful, null in other cases */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt index d5bab33180..f821fdcf6d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt @@ -44,6 +44,7 @@ internal data class GeneratePrivateKeyResult( * Compute a private key from a password. * * @param password the password to use. + * @param progressListener a listener to track progress * * @return a {privateKey, salt, iterations} tuple. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt index ea23be5923..d9c63b46ab 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt @@ -60,19 +60,19 @@ internal interface RoomKeysApi { * Get information about the given version. * If not supported by the server, an error is returned: {"errcode":"M_NOT_FOUND","error":"No backup found"} * - * @param version version + * @param version version */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}") suspend fun getKeysBackupVersion(@Path("version") version: String): KeysVersionResult /** * Update information about the given version. - * @param version version + * @param version version * @param updateKeysBackupVersionBody the body */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}") suspend fun updateKeysBackupVersion(@Path("version") version: String, - @Body keysBackupVersionBody: UpdateKeysBackupVersionBody) + @Body updateKeysBackupVersionBody: UpdateKeysBackupVersionBody) /* ========================================================================================== * Storing keys @@ -87,9 +87,9 @@ internal interface RoomKeysApi { * flag (true is better than false), then by the first_message_index (a lower number is better), and finally by * forwarded_count (a lower number is better). * - * @param roomId the room id - * @param sessionId the session id - * @param version the version of the backup + * @param roomId the room id + * @param sessionId the session id + * @param version the version of the backup * @param keyBackupData the data to send */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}") @@ -101,8 +101,8 @@ internal interface RoomKeysApi { /** * Store several keys for the given room, using the given backup version. * - * @param roomId the room id - * @param version the version of the backup + * @param roomId the room id + * @param version the version of the backup * @param roomKeysBackupData the data to send */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}") @@ -113,7 +113,7 @@ internal interface RoomKeysApi { /** * Store several keys, using the given backup version. * - * @param version the version of the backup + * @param version the version of the backup * @param keysBackupData the data to send */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys") @@ -127,9 +127,9 @@ internal interface RoomKeysApi { /** * Retrieve the key for the given session in the given room from the backup. * - * @param roomId the room id + * @param roomId the room id * @param sessionId the session id - * @param version the version of the backup, or empty String to retrieve the last version + * @param version the version of the backup, or empty String to retrieve the last version */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}") suspend fun getRoomSessionData(@Path("roomId") roomId: String, @@ -139,8 +139,8 @@ internal interface RoomKeysApi { /** * Retrieve all the keys for the given room from the backup. * - * @param roomId the room id - * @param version the version of the backup, or empty String to retrieve the last version + * @param roomId the room id + * @param version the version of the backup, or empty String to retrieve the last version */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}") suspend fun getRoomSessionsData(@Path("roomId") roomId: String, @@ -149,7 +149,7 @@ internal interface RoomKeysApi { /** * Retrieve all the keys from the backup. * - * @param version the version of the backup, or empty String to retrieve the last version + * @param version the version of the backup, or empty String to retrieve the last version */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys") suspend fun getSessionsData(@Query("version") version: String): KeysBackupData diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXKey.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXKey.kt index 6bfa56ae8d..6b747d19f2 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXKey.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXKey.kt @@ -59,7 +59,7 @@ internal data class MXKey( /** * Returns a signature for an user Id and a signkey. * - * @param userId the user id + * @param userId the user id * @param signkey the sign key * @return the signature */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 480009dbce..9b1c785059 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -164,16 +164,14 @@ internal interface IMXCryptoStore { /** * Store the end to end account for the logged-in user. - * - * @param account the account to save */ fun saveOlmAccount() /** * Retrieve a device for a user. * + * @param userId the user's id. * @param deviceId the device id. - * @param userId the user's id. * @return the device */ fun getUserDevice(userId: String, deviceId: String): CryptoDeviceInfo? @@ -189,7 +187,7 @@ internal interface IMXCryptoStore { /** * Store the known devices for a user. * - * @param userId The user's id. + * @param userId The user's id. * @param devices A map from device id to 'MXDevice' object for the device. */ fun storeUserDevices(userId: String, devices: Map?) @@ -225,7 +223,7 @@ internal interface IMXCryptoStore { /** * Store the crypto algorithm for a room. * - * @param roomId the id of the room. + * @param roomId the id of the room. * @param algorithm the algorithm. */ fun storeRoomAlgorithm(roomId: String, algorithm: String?) @@ -253,7 +251,7 @@ internal interface IMXCryptoStore { /** * Store a session between the logged-in user and another device. * - * @param olmSessionWrapper the end-to-end session. + * @param olmSessionWrapper the end-to-end session. * @param deviceKey the public key of the other device. */ fun storeSession(olmSessionWrapper: OlmSessionWrapper, deviceKey: String) @@ -331,7 +329,7 @@ internal interface IMXCryptoStore { /** * Mark inbound group sessions as backed up on the user homeserver. * - * @param sessions the sessions + * @param olmInboundGroupSessionWrappers the sessions */ fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List) @@ -361,7 +359,7 @@ internal interface IMXCryptoStore { /** * Get the tracking status of a specified userId devices. * - * @param userId the user id + * @param userId the user id * @param defaultValue the default value * @return the tracking status */ @@ -380,7 +378,9 @@ internal interface IMXCryptoStore { /** * Look for an existing outgoing room key request, and if none is found, add a new one. * - * @param request the request + * @param requestBody the request + * @param recipients list of recipients + * @param fromIndex start index * @return either the same instance as passed in, or the existing one. */ fun getOrAddOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map>, fromIndex: Int): OutgoingKeyRequest diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt index 4ab7e0e30c..04fb6c4858 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt @@ -35,7 +35,7 @@ internal object HkdfSha256 { /** * HkdfSha256-Extract(salt, IKM) -> PRK. * - * @param salt optional salt value (a non-secret random value); + * @param salt optional salt value (a non-secret random value); * if not provided, it is set to a string of HashLen (size in octets) zeros. * @param ikm input keying material */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt index 6da674d6e4..af48283767 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt @@ -939,9 +939,25 @@ internal class DefaultVerificationService @Inject constructor( updatePendingRequest( existingRequest.copy( - readyInfo = readyReq + readyInfo = readyReq ) ) + + notifyOthersOfAcceptance(readyReq.transactionId, readyReq.fromDevice) + } + + /** + * Gets a list of device ids excluding the current one. + */ + private fun getMyOtherDeviceIds(): List = cryptoStore.getUserDevices(userId)?.keys?.filter { it != deviceId }.orEmpty() + + /** + * Notifies other devices that the current verification transaction is being handled by [acceptedByDeviceId]. + */ + private fun notifyOthersOfAcceptance(transactionId: String, acceptedByDeviceId: String) { + val deviceIds = getMyOtherDeviceIds().filter { it != acceptedByDeviceId } + val transport = verificationTransportToDeviceFactory.createTransport(null) + transport.cancelTransaction(transactionId, userId, deviceIds, CancelCode.AcceptedByAnotherDevice) } private fun createQrCodeData(requestId: String?, otherUserId: String, otherDeviceId: String?): QrCodeData? { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransport.kt index c12aea9d52..69dec12ef3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransport.kt @@ -35,6 +35,11 @@ internal interface VerificationTransport { onDone: (() -> Unit)?) /** + * @param supportedMethods list of supported method by this client + * @param localId a local Id + * @param otherUserId the user id to send the verification request to + * @param roomId a room Id to use to send verification message + * @param toDevices list of device Ids * @param callback will be called with eventId and ValidVerificationInfoRequest in case of success */ fun sendVerificationRequest(supportedMethods: List, @@ -49,6 +54,11 @@ internal interface VerificationTransport { otherUserDeviceId: String?, code: CancelCode) + fun cancelTransaction(transactionId: String, + otherUserId: String, + otherUserDeviceIds: List, + code: CancelCode) + fun done(transactionId: String, onDone: (() -> Unit)?) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt index e32828af23..03df849d22 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt @@ -160,6 +160,9 @@ internal class VerificationTransportRoomMessage( } } + override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceIds: List, code: CancelCode) = + cancelTransaction(transactionId, otherUserId, null, code) + override fun done(transactionId: String, onDone: (() -> Unit)?) { Timber.d("## SAS sending done for $transactionId") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt index bc24ef2966..974adf3888 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt @@ -193,6 +193,27 @@ internal class VerificationTransportToDevice( .executeBy(taskExecutor) } + override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceIds: List, code: CancelCode) { + Timber.d("## SAS canceling transaction $transactionId for reason $code") + val cancelMessage = KeyVerificationCancel.create(transactionId, code) + val contentMap = MXUsersDevicesMap() + val messages = otherUserDeviceIds.associateWith { cancelMessage } + contentMap.setObjects(otherUserId, messages) + sendToDeviceTask + .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap)) { + this.callback = object : MatrixCallback { + override fun onSuccess(data: Unit) { + Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}") + } + + override fun onFailure(failure: Throwable) { + Timber.e(failure, "## SAS verification [$transactionId] failed to cancel.") + } + } + } + .executeBy(taskExecutor) + } + override fun createAccept(tid: String, keyAgreementProtocol: String, hash: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt index db3647c3fa..5db859bce2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -96,7 +96,9 @@ internal fun EventEntity.markEventAsRoot( /** * Count the number of threads for the provided root thread eventId, and finds the latest event message. * Note: Redactions are handled by RedactionEventProcessor. + * @param realm the realm database * @param rootThreadEventId The root eventId that will find the number of threads + * @param chunkEntity the chunk entity * @return A ThreadSummary containing the counted threads and the latest event message */ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: String, chunkEntity: ChunkEntity?): Summary { @@ -184,6 +186,7 @@ private fun findLatestSortedChunkEvent(chunk: ChunkEntity, rootThreadEventId: St /** * Find all TimelineEventEntity that are root threads for the specified room. + * @param realm the realm instance * @param roomId The room that all stored root threads will be returned */ internal fun TimelineEventEntity.Companion.findAllThreadsForRoomId(realm: Realm, roomId: String): RealmQuery = @@ -218,6 +221,7 @@ internal fun List.mapEventsWithEdition(realm: Realm, roomId: Stri /** * Returns a list of all the marked unread threads that exists for the specified room. + * @param realm the realm instance * @param roomId The roomId that the user is currently in */ internal fun TimelineEventEntity.Companion.findAllLocalThreadNotificationsForRoomId(realm: Realm, roomId: String): RealmQuery = @@ -232,6 +236,7 @@ internal fun TimelineEventEntity.Companion.findAllLocalThreadNotificationsForRoo /** * Returns whether or not the given user is participating in a current thread. + * @param realm the realm instance * @param roomId the room that the thread exists * @param rootThreadEventId the thread that the search will be done * @param senderId the user that will try to find participation @@ -247,6 +252,7 @@ internal fun TimelineEventEntity.Companion.isUserParticipatingInThread(realm: Re /** * Returns whether or not the given user is mentioned in a current thread. + * @param realm the realm instance * @param roomId the room that the thread exists * @param rootThreadEventId the thread that the search will be done * @param userId the user that will try to find if there is a mention diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt index 3bf574c207..5b4fe287cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt @@ -303,6 +303,7 @@ private fun getLatestEvent(rootThreadEvent: Event): Event? { /** * Find all ThreadSummaryEntity for the specified roomId, sorted by origin server. * note: Sorting cannot be provided by server, so we have to use that unstable property. + * @param realm the realm instance * @param roomId The id of the room */ internal fun ThreadSummaryEntity.Companion.findAllThreadsForRoomId(realm: Realm, roomId: String): RealmQuery = diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java index a1b46f6c09..b2bb852cd1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java @@ -612,7 +612,7 @@ public class HomeServerConnectionConfig { * - https://www.ssi.gouv.fr/uploads/2017/02/security-recommendations-for-tls_v1.1.pdf * - https://developer.android.com/reference/javax/net/ssl/SSLEngine * - * @param tlsLimitations true to use Tls limitations + * @param tlsLimitations true to use Tls limitations * @param enableCompatibilityMode set to true for Android < 20 * @return this builder */ @@ -649,7 +649,7 @@ public class HomeServerConnectionConfig { /** * @param proxyHostname Proxy Hostname - * @param proxyPort Proxy Port + * @param proxyPort Proxy Port * @return this builder */ public Builder withProxy(@Nullable String proxyHostname, int proxyPort) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt index 695e7525af..87a98e03f6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt @@ -32,6 +32,7 @@ import java.io.IOException * Execute a request from the requestBlock and handle some of the Exception it could generate * Ref: https://github.com/matrix-org/matrix-js-sdk/blob/develop/src/scheduler.js#L138-L175 * + * @param DATA type of data return by the [requestBlock] * @param globalErrorReceiver will be use to notify error such as invalid token error. See [GlobalError] * @param canRetry if set to true, the request will be executed again in case of error, after a delay * @param maxDelayBeforeRetry the max delay to wait before a retry. Note that in the case of a 429, if the provided delay exceeds this value, the error will diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.kt index 40d174ee2d..dd41b9f6fc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.kt @@ -119,9 +119,11 @@ internal class RuntimeJsonAdapterFactory( companion object { /** + * @param T the generic type to pass to [RuntimeJsonAdapterFactory] * @param baseType The base type for which this factory will create adapters. Cannot be Object. * @param labelKey The key in the JSON object whose value determines the type to which to map the * JSON object. + * @param fallbackType alternative Type to try in case of the serialization fails */ @CheckReturnValue fun of(baseType: Class, labelKey: String, fallbackType: Class): RuntimeJsonAdapterFactory { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt index 2ef40fe2a3..e5659fd76b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt @@ -94,6 +94,7 @@ internal object CertUtil { * Convert the fingerprint to an hexa string. * * @param fingerprint the fingerprint + * @param sep the separator character, default to space * @return the hexa string. */ fun fingerprintToHexString(fingerprint: ByteArray, sep: Char = ' '): String { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManager.kt index ccae5ad14f..539570cdd9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManager.kt @@ -24,11 +24,9 @@ import javax.net.ssl.X509TrustManager /** * Implements a TrustManager that checks Certificates against an explicit list of known * fingerprints. - */ - -/** - * @param fingerprints Not empty array of SHA256 cert fingerprints - * @param defaultTrustManager Optional trust manager to fall back on if cert does not match + * + * @property fingerprints Not empty array of SHA256 cert fingerprints + * @property defaultTrustManager Optional trust manager to fall back on if cert does not match * any of the fingerprints. Can be null. */ internal class PinnedTrustManager(private val fingerprints: List, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManagerApi24.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManagerApi24.kt index 574f1ef81d..191bb90a67 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManagerApi24.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManagerApi24.kt @@ -28,11 +28,9 @@ import javax.net.ssl.X509ExtendedTrustManager /** * Implements a TrustManager that checks Certificates against an explicit list of known * fingerprints. - */ - -/** - * @param fingerprints An array of SHA256 cert fingerprints - * @param defaultTrustManager Optional trust manager to fall back on if cert does not match + * + * @property fingerprints An array of SHA256 cert fingerprints + * @property defaultTrustManager Optional trust manager to fall back on if cert does not match * any of the fingerprints. Can be null. */ @RequiresApi(Build.VERSION_CODES.N) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index 7ceb89e892..9208ff219b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -87,8 +87,8 @@ import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationMan import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService import org.matrix.android.sdk.internal.session.room.EventRelationsAggregationProcessor -import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.DefaultLiveLocationAggregationProcessor -import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor +import org.matrix.android.sdk.internal.session.room.aggregation.poll.DefaultPollAggregationProcessor +import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor import org.matrix.android.sdk.internal.session.room.create.RoomCreateEventProcessor import org.matrix.android.sdk.internal.session.room.prune.RedactionEventProcessor import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor @@ -389,5 +389,5 @@ internal abstract class SessionModule { abstract fun bindEventSenderProcessor(processor: EventSenderProcessorCoroutine): EventSenderProcessor @Binds - abstract fun bindLiveLocationAggregationProcessor(processor: DefaultLiveLocationAggregationProcessor): LiveLocationAggregationProcessor + abstract fun bindPollAggregationProcessor(processor: DefaultPollAggregationProcessor): PollAggregationProcessor } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt index 19b9130fc4..0db6812609 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt @@ -55,6 +55,7 @@ internal interface DirectoryAPI { /** * Add alias to the room. * @param roomAlias the room alias. + * @param body the Json body */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") suspend fun addRoomAlias(@Path("roomAlias") roomAlias: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt index dab801360f..d1df77d14a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt @@ -28,7 +28,7 @@ internal interface FilterApi { * Upload FilterBody to get a filter_id which can be used for /sync requests. * * @param userId the user id - * @param body the Json representation of a FilterBody object + * @param body the Json representation of a FilterBody object */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter") suspend fun uploadFilter(@Path("userId") userId: String, @@ -37,7 +37,7 @@ internal interface FilterApi { /** * Gets a filter with a given filterId from the homeserver. * - * @param userId the user id + * @param userId the user id * @param filterId the filterID * @return Filter */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterUtil.kt index 562fea88b6..2017a86c39 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterUtil.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterUtil.kt @@ -25,7 +25,7 @@ internal object FilterUtil { * FIXME New expected filter: * "{\"room\": {\"ephemeral\": {\"notTypes\": [\"m.typing\"]}}, \"presence\":{\"notTypes\": [\"*\"]}}" * - * @param filterBody filterBody to patch + * @param filterBody filterBody to patch * @param useDataSaveMode true to enable data save mode */ /* diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt index e9097e4d03..b1a518724c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.internal.session.homeserver import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.MatrixPatterns.getDomain +import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.extensions.orFalse @@ -93,10 +93,14 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( } }.getOrNull() + // Domain may include a port (eg, matrix.org:8080) + // Per https://spec.matrix.org/latest/client-server-api/#well-known-uri we should extract the hostname from the server name + // So we take everything before the last : as the domain for the well-known task. + // NB: This is not always the same endpoint as capabilities / mediaConfig uses. val wellknownResult = runCatching { getWellknownTask.execute( GetWellknownTask.Params( - domain = userId.getDomain(), + domain = userId.getServerName().substringBeforeLast(":"), homeServerConnectionConfig = homeServerConnectionConfig ) ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt index eb8c841d57..c3caaefdec 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt @@ -32,6 +32,7 @@ internal interface OpenIdAPI { * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-user-userid-openid-request-token * * @param userId the user id + * @param body an empty json body */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/openid/request_token") suspend fun openIdToken(@Path("userId") userId: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt index 0f667c65df..edc45fe945 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.internal.session.permalinks -import org.matrix.android.sdk.api.MatrixPatterns.getDomain +import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel @@ -55,9 +55,9 @@ internal class ViaParameterFinder @Inject constructor( } fun computeViaParams(userId: String, roomId: String, max: Int): List { - val userHomeserver = userId.getDomain() + val userHomeserver = userId.getServerName() return getUserIdsOfJoinedMembers(roomId) - .map { it.getDomain() } + .map { it.getServerName() } .groupBy { it } .mapValues { it.value.size } .toMutableMap() @@ -92,7 +92,7 @@ internal class ViaParameterFinder @Inject constructor( .orEmpty() .toSet() - return userThatCanInvite.map { it.getDomain() } + return userThatCanInvite.map { it.getServerName() } .groupBy { it } .mapValues { it.value.size } .toMutableMap() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt index 40b4ee269a..fbae04a1f1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt @@ -33,9 +33,9 @@ internal interface PushRulesApi { /** * Update the ruleID enable status. * - * @param kind the notification kind (sender, room...) + * @param kind the notification kind (sender, room...) * @param ruleId the ruleId - * @param enable the new enable status + * @param enabledBody the new enable status */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/enabled") suspend fun updateEnableRuleStatus(@Path("kind") kind: String, @@ -46,8 +46,8 @@ internal interface PushRulesApi { * Update the ruleID action. * Ref: https://matrix.org/docs/spec/client_server/latest#put-matrix-client-r0-pushrules-scope-kind-ruleid-actions * - * @param kind the notification kind (sender, room...) - * @param ruleId the ruleId + * @param kind the notification kind (sender, room...) + * @param ruleId the ruleId * @param actions the actions */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/actions") @@ -58,7 +58,7 @@ internal interface PushRulesApi { /** * Delete a rule. * - * @param kind the notification kind (sender, room...) + * @param kind the notification kind (sender, room...) * @param ruleId the ruleId */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}") @@ -68,9 +68,9 @@ internal interface PushRulesApi { /** * Add the ruleID enable status. * - * @param kind the notification kind (sender, room...) + * @param kind the notification kind (sender, room...) * @param ruleId the ruleId. - * @param rule the rule to add. + * @param rule the rule to add. */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}") suspend fun addRule(@Path("kind") kind: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index 16a63a9a96..3efeef7688 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.session.room import io.realm.Realm -import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.crypto.verification.VerificationState import org.matrix.android.sdk.api.session.events.model.AggregatedAnnotation @@ -28,23 +27,16 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventCon import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.getTimelineEvent -import org.matrix.android.sdk.api.session.room.model.PollSummaryContent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent -import org.matrix.android.sdk.api.session.room.model.VoteInfo -import org.matrix.android.sdk.api.session.room.model.VoteSummary import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent -import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent -import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.verification.toState import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent @@ -55,7 +47,6 @@ import org.matrix.android.sdk.internal.database.model.EditionOfEvent import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventInsertType -import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntity import org.matrix.android.sdk.internal.database.model.ReactionAggregatedSummaryEntity import org.matrix.android.sdk.internal.database.model.ReactionAggregatedSummaryEntityFields import org.matrix.android.sdk.internal.database.model.ReferencesAggregatedSummaryEntity @@ -68,6 +59,7 @@ import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor +import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber @@ -79,6 +71,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( @SessionId private val sessionId: String, private val sessionManager: SessionManager, private val liveLocationAggregationProcessor: LiveLocationAggregationProcessor, + private val pollAggregationProcessor: PollAggregationProcessor, private val clock: Clock, ) : EventInsertLiveProcessor { @@ -162,9 +155,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor( // A replace! handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) } else if (event.getClearType() in EventType.POLL_RESPONSE) { - event.getClearContent().toModel(catchError = true)?.let { pollResponseContent -> - Timber.v("###RESPONSE in room $roomId for event ${event.eventId}") - handleResponse(realm, event, pollResponseContent, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) + sessionManager.getSessionComponent(sessionId)?.session()?.let { session -> + pollAggregationProcessor.handlePollResponseEvent(session, realm, event) } } } @@ -184,18 +176,20 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } in EventType.POLL_RESPONSE -> { event.getClearContent().toModel(catchError = true)?.let { - handleResponse(realm, event, it, roomId, isLocalEcho, event.getRelationContent()?.eventId) + sessionManager.getSessionComponent(sessionId)?.session()?.let { session -> + pollAggregationProcessor.handlePollResponseEvent(session, realm, event) + } } } in EventType.POLL_END -> { - event.content.toModel(catchError = true)?.let { - handleEndPoll(realm, event, it, roomId, isLocalEcho) + sessionManager.getSessionComponent(sessionId)?.session()?.let { session -> + getPowerLevelsHelper(event.roomId)?.let { + pollAggregationProcessor.handlePollEndEvent(session, it, realm, event) + } } } in EventType.BEACON_LOCATION_DATA -> { - event.getClearContent().toModel(catchError = true)?.let { - liveLocationAggregationProcessor.handleBeaconLocationData(realm, event, it, roomId, isLocalEcho) - } + handleBeaconLocationData(event, realm, roomId, isLocalEcho) } } } else if (encryptedEventContent?.relatesTo?.type == RelationType.ANNOTATION) { @@ -247,12 +241,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } in EventType.POLL_RESPONSE -> { event.content.toModel(catchError = true)?.let { - handleResponse(realm, event, it, roomId, isLocalEcho) + sessionManager.getSessionComponent(sessionId)?.session()?.let { session -> + pollAggregationProcessor.handlePollResponseEvent(session, realm, event) + } } } in EventType.POLL_END -> { - event.content.toModel(catchError = true)?.let { - handleEndPoll(realm, event, it, roomId, isLocalEcho) + sessionManager.getSessionComponent(sessionId)?.session()?.let { session -> + getPowerLevelsHelper(event.roomId)?.let { + pollAggregationProcessor.handlePollEndEvent(session, it, realm, event) + } } } in EventType.STATE_ROOM_BEACON_INFO -> { @@ -260,6 +258,9 @@ internal class EventRelationsAggregationProcessor @Inject constructor( liveLocationAggregationProcessor.handleBeaconInfo(realm, event, it, roomId, isLocalEcho) } } + in EventType.BEACON_LOCATION_DATA -> { + handleBeaconLocationData(event, realm, roomId, isLocalEcho) + } else -> Timber.v("UnHandled event ${event.eventId}") } } catch (t: Throwable) { @@ -317,22 +318,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor( return } - ContentMapper - .map(eventAnnotationsSummaryEntity.pollResponseSummary?.aggregatedContent) - ?.toModel() - ?.let { existingPollSummaryContent -> - eventAnnotationsSummaryEntity.pollResponseSummary?.aggregatedContent = ContentMapper.map( - PollSummaryContent( - myVote = existingPollSummaryContent.myVote, - votes = emptyList(), - votesSummary = emptyMap(), - totalVotes = 0, - winnerVoteCount = 0, - ) - .toContent() - ) - } - val txId = event.unsignedData?.transactionId // is it a remote echo? if (!isLocalEcho && existingSummary.editions.any { it.eventId == txId }) { @@ -362,6 +347,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } + if (event.getClearType() in EventType.POLL_START) { + pollAggregationProcessor.handlePollStartEvent(realm, event) + } + if (!isLocalEcho) { val replaceEvent = TimelineEventEntity .where(realm, roomId, eventId) @@ -375,6 +364,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( * Check if the edition is on the latest thread event, and update it accordingly. * @param editedEvent The event that will be changed * @param replaceEvent The new event + * @param editions list of edition of event */ private fun handleThreadSummaryEdition(editedEvent: EventEntity?, replaceEvent: TimelineEventEntity?, @@ -391,173 +381,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } - private fun handleResponse(realm: Realm, - event: Event, - content: MessagePollResponseContent, - roomId: String, - isLocalEcho: Boolean, - relatedEventId: String? = null) { - val eventId = event.eventId ?: return - val senderId = event.senderId ?: return - val targetEventId = relatedEventId ?: content.relatesTo?.eventId ?: return - val eventTimestamp = event.originServerTs ?: return - - val targetPollContent = getPollContent(roomId, targetEventId) ?: return - - // ok, this is a poll response - var existing = EventAnnotationsSummaryEntity.where(realm, roomId, targetEventId).findFirst() - if (existing == null) { - Timber.v("## POLL creating new relation summary for $targetEventId") - existing = EventAnnotationsSummaryEntity.create(realm, roomId, targetEventId) - } - - // we have it - val existingPollSummary = existing.pollResponseSummary - ?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also { - existing.pollResponseSummary = it - } - - val closedTime = existingPollSummary.closedTime - if (closedTime != null && eventTimestamp > closedTime) { - Timber.v("## POLL is closed ignore event poll:$targetEventId, event :${event.eventId}") - return - } - - val currentModel = ContentMapper.map(existingPollSummary.aggregatedContent).toModel() - - if (existingPollSummary.sourceEvents.contains(eventId)) { - // ignore this event, we already know it (??) - Timber.v("## POLL ignoring event for summary, it's known eventId:$eventId") - return - } - val txId = event.unsignedData?.transactionId - // is it a remote echo? - if (!isLocalEcho && existingPollSummary.sourceLocalEchoEvents.contains(txId)) { - // ok it has already been managed - Timber.v("## POLL Receiving remote echo of response eventId:$eventId") - existingPollSummary.sourceLocalEchoEvents.remove(txId) - existingPollSummary.sourceEvents.add(event.eventId) - return - } - - val option = content.getBestResponse()?.answers?.first() ?: return Unit.also { - Timber.d("## POLL Ignoring malformed response no option eventId:$eventId content: ${event.content}") - } - - // Check if this option is in available options - if (!targetPollContent.getBestPollCreationInfo()?.answers?.map { it.id }?.contains(option).orFalse()) { - Timber.v("## POLL $targetEventId doesn't contain option $option") - return - } - - val votes = currentModel?.votes.orEmpty().toMutableList() - - var myVote: String? = null - val existingVoteIndex = votes.indexOfFirst { it.userId == senderId } - if (existingVoteIndex != -1) { - // Is the vote newer? - val existingVote = votes[existingVoteIndex] - if (existingVote.voteTimestamp < eventTimestamp) { - // Take the new one - votes[existingVoteIndex] = VoteInfo(senderId, option, eventTimestamp) - if (userId == senderId) { - myVote = option - } - Timber.v("## POLL adding vote $option for user $senderId in poll :$targetEventId ") - } else { - Timber.v("## POLL Ignoring vote (older than known one) eventId:$eventId ") - } - } else { - votes.add(VoteInfo(senderId, option, eventTimestamp)) - if (userId == senderId) { - myVote = option - } - Timber.v("## POLL adding vote $option for user $senderId in poll :$targetEventId ") - } - - // Precompute the percentage of votes for all options - val totalVotes = votes.size - val newVotesSummary = votes - .groupBy({ it.option }, { it.userId }) - .mapValues { - VoteSummary( - total = it.value.size, - percentage = if (totalVotes == 0 && it.value.isEmpty()) 0.0 else it.value.size.toDouble() / totalVotes - ) - } - val newWinnerVoteCount = newVotesSummary.maxOf { it.value.total } - - if (isLocalEcho) { - existingPollSummary.sourceLocalEchoEvents.add(eventId) - } else { - existingPollSummary.sourceEvents.add(eventId) - } - - val newSumModel = PollSummaryContent( - myVote = myVote, - votes = votes, - votesSummary = newVotesSummary, - totalVotes = totalVotes, - winnerVoteCount = newWinnerVoteCount - ) - - existingPollSummary.aggregatedContent = ContentMapper.map(newSumModel.toContent()) - } - - private fun handleEndPoll(realm: Realm, - event: Event, - content: MessageEndPollContent, - roomId: String, - isLocalEcho: Boolean) { - val pollEventId = content.relatesTo?.eventId ?: return - val pollOwnerId = getPollEvent(roomId, pollEventId)?.root?.senderId - val isPollOwner = pollOwnerId == event.senderId - val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + private fun getPowerLevelsHelper(roomId: String): PowerLevelsHelper? { + return stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) ?.content?.toModel() ?.let { PowerLevelsHelper(it) } - - if (!isPollOwner && !powerLevelsHelper?.isUserAbleToRedact(event.senderId ?: "").orFalse()) { - Timber.v("## Received poll.end event $pollEventId but user ${event.senderId} doesn't have enough power level in room $roomId") - return - } - - var existingPoll = EventAnnotationsSummaryEntity.where(realm, roomId, pollEventId).findFirst() - if (existingPoll == null) { - Timber.v("## POLL creating new relation summary for $pollEventId") - existingPoll = EventAnnotationsSummaryEntity.create(realm, roomId, pollEventId) - } - - // we have it - val existingPollSummary = existingPoll.pollResponseSummary - ?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also { - existingPoll.pollResponseSummary = it - } - - val txId = event.unsignedData?.transactionId - existingPollSummary.closedTime = event.originServerTs - - // is it a remote echo? - if (!isLocalEcho && existingPollSummary.sourceLocalEchoEvents.contains(txId)) { - // ok it has already been managed - Timber.v("## POLL Receiving remote echo of response eventId:$pollEventId") - existingPollSummary.sourceLocalEchoEvents.remove(txId) - existingPollSummary.sourceEvents.add(event.eventId) - } - } - - private fun getPollEvent(roomId: String, eventId: String): TimelineEvent? { - val session = sessionManager.getSessionComponent(sessionId)?.session() - return session?.roomService()?.getRoom(roomId)?.getTimelineEvent(eventId) ?: return null.also { - Timber.v("## POLL target poll event $eventId not found in room $roomId") - } - } - - private fun getPollContent(roomId: String, eventId: String): MessagePollContent? { - val pollEvent = getPollEvent(roomId, eventId) ?: return null - - return pollEvent.getLastMessageContent() as? MessagePollContent ?: return null.also { - Timber.v("## POLL target poll event $eventId content is malformed") - } } private fun handleInitialAggregatedRelations(realm: Realm, @@ -756,4 +583,17 @@ internal class EventRelationsAggregationProcessor @Inject constructor( verifSummary.sourceEvents.add(event.eventId) } } + + private fun handleBeaconLocationData(event: Event, realm: Realm, roomId: String, isLocalEcho: Boolean) { + event.getClearContent().toModel(catchError = true)?.let { + liveLocationAggregationProcessor.handleBeaconLocationData( + realm, + event, + it, + roomId, + event.getRelationContent()?.eventId, + isLocalEcho + ) + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index 72f56ddf68..ba7f4cf5ad 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt @@ -78,9 +78,9 @@ internal interface RoomAPI { * Get a list of messages starting from a reference. * * @param roomId the room id - * @param from the token identifying where to start. Required. - * @param dir The direction to return messages from. Required. - * @param limit the maximum number of messages to retrieve. Optional. + * @param from the token identifying where to start. Required. + * @param dir The direction to return messages from. Required. + * @param limit the maximum number of messages to retrieve. Optional. * @param filter A JSON RoomEventFilter to filter returned events with. Optional. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/messages") @@ -94,9 +94,9 @@ internal interface RoomAPI { /** * Get all members of a room. * - * @param roomId the room id where to get the members - * @param syncToken the sync token (optional) - * @param membership to include only one type of membership (optional) + * @param roomId the room id where to get the members + * @param syncToken the sync token (optional) + * @param membership to include only one type of membership (optional) * @param notMembership to exclude one type of membership (optional) */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/members") @@ -109,10 +109,10 @@ internal interface RoomAPI { /** * Send an event to a room. * - * @param txId the transaction Id - * @param roomId the room id + * @param txId the transaction Id + * @param roomId the room id * @param eventType the event type - * @param content the event content + * @param content the event content */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/send/{eventType}/{txId}") suspend fun send(@Path("txId") txId: String, @@ -124,10 +124,10 @@ internal interface RoomAPI { /** * Get the context surrounding an event. * - * @param roomId the room id + * @param roomId the room id * @param eventId the event Id - * @param limit the maximum number of messages to retrieve - * @param filter A JSON RoomEventFilter to filter returned events with. Optional. + * @param limit the maximum number of messages to retrieve + * @param filter A JSON RoomEventFilter to filter returned events with. Optional. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/context/{eventId}") suspend fun getContextOfEvent(@Path("roomId") roomId: String, @@ -138,7 +138,7 @@ internal interface RoomAPI { /** * Retrieve an event from its room id / events id. * - * @param roomId the room id + * @param roomId the room id * @param eventId the event Id */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/event/{eventId}") @@ -148,7 +148,7 @@ internal interface RoomAPI { /** * Send read markers. * - * @param roomId the room id + * @param roomId the room id * @param markers the read markers */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/read_markers") @@ -169,7 +169,7 @@ internal interface RoomAPI { * Ref: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-rooms-roomid-invite * * @param roomId the room id - * @param body a object that just contains a user id + * @param body a object that just contains a user id */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite") suspend fun invite(@Path("roomId") roomId: String, @@ -179,6 +179,7 @@ internal interface RoomAPI { * Invite a user to a room, using a ThreePid * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#id101 * @param roomId Required. The room identifier (not alias) to which to invite the user. + * @param body the Json body */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite") suspend fun invite3pid(@Path("roomId") roomId: String, @@ -187,9 +188,9 @@ internal interface RoomAPI { /** * Send a generic state event. * - * @param roomId the room id. + * @param roomId the room id. * @param stateEventType the state event type - * @param params the request parameters + * @param params the request parameters */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}") suspend fun sendStateEvent(@Path("roomId") roomId: String, @@ -200,10 +201,10 @@ internal interface RoomAPI { /** * Send a generic state event. * - * @param roomId the room id. + * @param roomId the room id. * @param stateEventType the state event type - * @param stateKey the state keys - * @param params the request parameters + * @param stateKey the state keys + * @param params the request parameters */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}/{state_key}") suspend fun sendStateEvent(@Path("roomId") roomId: String, @@ -221,8 +222,13 @@ internal interface RoomAPI { /** * Paginate relations for event based in normal topological order. + * @param roomId the room Id + * @param eventId the event Id * @param relationType filter for this relation type * @param eventType filter for this event type + * @param from from token + * @param to to token + * @param limit max number of Event to retrieve */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/relations/{eventId}/{relationType}/{eventType}") suspend fun getRelations(@Path("roomId") roomId: String, @@ -236,7 +242,13 @@ internal interface RoomAPI { /** * Paginate relations for thread events based in normal topological order. + * + * @param roomId the room Id + * @param eventId the event Id * @param relationType filter for this relation type + * @param from from token + * @param to to token + * @param limit max number of Event to retrieve */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/relations/{eventId}/{relationType}") suspend fun getThreadsRelations(@Path("roomId") roomId: String, @@ -262,7 +274,7 @@ internal interface RoomAPI { /** * Leave the given room. * - * @param roomId the room id + * @param roomId the room id * @param params the request body */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/leave") @@ -272,7 +284,7 @@ internal interface RoomAPI { /** * Ban a user from the given room. * - * @param roomId the room id + * @param roomId the room id * @param userIdAndReason the banned user object (userId and reason for ban) */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/ban") @@ -282,7 +294,7 @@ internal interface RoomAPI { /** * unban a user from the given room. * - * @param roomId the room id + * @param roomId the room id * @param userIdAndReason the unbanned user object (userId and reason for unban) */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/unban") @@ -292,7 +304,7 @@ internal interface RoomAPI { /** * Kick a user from the given room. * - * @param roomId the room id + * @param roomId the room id * @param userIdAndReason the kicked user object (userId and reason for kicking) */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/kick") @@ -304,10 +316,10 @@ internal interface RoomAPI { * This cannot be undone. * Users may redact their own events, and any user with a power level greater than or equal to the redact power level of the room may redact events there. * - * @param txId the transaction Id - * @param roomId the room id - * @param eventId the event to delete - * @param reason json containing reason key {"reason": "Indecent material"} + * @param txId the transaction Id + * @param roomId the room id + * @param eventId the event to delete + * @param reason json containing reason key {"reason": "Indecent material"} */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/redact/{eventId}/{txnId}") suspend fun redactEvent( @@ -320,9 +332,9 @@ internal interface RoomAPI { /** * Reports an event as inappropriate to the server, which may then notify the appropriate people. * - * @param roomId the room id + * @param roomId the room id * @param eventId the event to report content - * @param body body containing score and reason + * @param body body containing score and reason */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/report/{eventId}") suspend fun reportContent(@Path("roomId") roomId: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt index 29a303475b..c3d55b267a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt @@ -35,7 +35,7 @@ internal class RoomAvatarResolver @Inject constructor(@UserId private val userId /** * Compute the room avatar url. - * @param realm: the current instance of realm + * @param realm the current instance of realm * @param roomId the roomId of the room to resolve avatar * @return the room avatar url, can be a fallback to a room member avatar or null */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt deleted file mode 100644 index 997e31a109..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2022 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.session.room.aggregation.livelocation - -import io.realm.Realm -import org.matrix.android.sdk.api.extensions.orTrue -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.toContent -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent -import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent -import org.matrix.android.sdk.internal.database.mapper.ContentMapper -import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity -import org.matrix.android.sdk.internal.database.query.getOrCreate -import timber.log.Timber -import javax.inject.Inject - -internal class DefaultLiveLocationAggregationProcessor @Inject constructor() : LiveLocationAggregationProcessor { - - override fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean) { - if (event.senderId.isNullOrEmpty() || isLocalEcho) { - return - } - - val targetEventId = if (content.isLive.orTrue()) { - event.eventId - } else { - // when live is set to false, we use the id of the event that should have been replaced - event.unsignedData?.replacesState - } - - if (targetEventId.isNullOrEmpty()) { - Timber.w("no target event id found for the beacon content") - return - } - - val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate( - realm = realm, - roomId = roomId, - eventId = targetEventId - ) - - Timber.d("updating summary of id=$targetEventId with isLive=${content.isLive}") - - aggregatedSummary.endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) } - aggregatedSummary.isActive = content.isLive - } - - override fun handleBeaconLocationData(realm: Realm, event: Event, content: MessageBeaconLocationDataContent, roomId: String, isLocalEcho: Boolean) { - if (event.senderId.isNullOrEmpty() || isLocalEcho) { - return - } - - val targetEventId = content.relatesTo?.eventId - - if (targetEventId.isNullOrEmpty()) { - Timber.w("no target event id found for the live location content") - return - } - - val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate( - realm = realm, - roomId = roomId, - eventId = targetEventId - ) - val updatedLocationTimestamp = content.getBestTimestampMillis() ?: 0 - val currentLocationTimestamp = ContentMapper - .map(aggregatedSummary.lastLocationContent) - .toModel() - ?.getBestTimestampMillis() - ?: 0 - - if (updatedLocationTimestamp.isMoreRecentThan(currentLocationTimestamp)) { - Timber.d("updating last location of the summary of id=$targetEventId") - aggregatedSummary.lastLocationContent = ContentMapper.map(content.toContent()) - } - } - - private fun Long.isMoreRecentThan(timestamp: Long) = this > timestamp -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt index c0be96f83d..76b7a4ec8e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt @@ -17,24 +17,83 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation import io.realm.Realm +import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent +import org.matrix.android.sdk.internal.database.mapper.ContentMapper +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.query.getOrCreate +import timber.log.Timber +import javax.inject.Inject -internal interface LiveLocationAggregationProcessor { - fun handleBeaconInfo( - realm: Realm, - event: Event, - content: MessageBeaconInfoContent, - roomId: String, - isLocalEcho: Boolean, - ) +internal class LiveLocationAggregationProcessor @Inject constructor() { + + fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean) { + if (event.senderId.isNullOrEmpty() || isLocalEcho) { + return + } + + val targetEventId = if (content.isLive.orTrue()) { + event.eventId + } else { + // when live is set to false, we use the id of the event that should have been replaced + event.unsignedData?.replacesState + } + + if (targetEventId.isNullOrEmpty()) { + Timber.w("no target event id found for the beacon content") + return + } + + val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate( + realm = realm, + roomId = roomId, + eventId = targetEventId + ) + + Timber.d("updating summary of id=$targetEventId with isLive=${content.isLive}") + + aggregatedSummary.endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) } + aggregatedSummary.isActive = content.isLive + } fun handleBeaconLocationData( realm: Realm, event: Event, content: MessageBeaconLocationDataContent, roomId: String, - isLocalEcho: Boolean, - ) + relatedEventId: String?, + isLocalEcho: Boolean + ) { + if (event.senderId.isNullOrEmpty() || isLocalEcho) { + return + } + + if (relatedEventId.isNullOrEmpty()) { + Timber.w("no related event id found for the live location content") + return + } + + val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate( + realm = realm, + roomId = roomId, + eventId = relatedEventId + ) + val updatedLocationTimestamp = content.getBestTimestampMillis() ?: 0 + val currentLocationTimestamp = ContentMapper + .map(aggregatedSummary.lastLocationContent) + .toModel() + ?.getBestTimestampMillis() + ?: 0 + + if (updatedLocationTimestamp.isMoreRecentThan(currentLocationTimestamp)) { + Timber.d("updating last location of the summary of id=$relatedEventId") + aggregatedSummary.lastLocationContent = ContentMapper.map(content.toContent()) + } + } + + private fun Long.isMoreRecentThan(timestamp: Long) = this > timestamp } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt new file mode 100644 index 0000000000..d4b414aaea --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt @@ -0,0 +1,203 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.aggregation.poll + +import io.realm.Realm +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.LocalEcho +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.getRelationContent +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.getTimelineEvent +import org.matrix.android.sdk.api.session.room.model.PollSummaryContent +import org.matrix.android.sdk.api.session.room.model.VoteInfo +import org.matrix.android.sdk.api.session.room.model.VoteSummary +import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent +import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent +import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent +import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent +import org.matrix.android.sdk.internal.database.mapper.ContentMapper +import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity +import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.query.create +import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.database.query.where +import javax.inject.Inject + +class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationProcessor { + + override fun handlePollStartEvent(realm: Realm, event: Event): Boolean { + val content = event.getClearContent()?.toModel() + if (content?.relatesTo?.type != RelationType.REPLACE) { + return false + } + + val roomId = event.roomId ?: return false + val targetEventId = content.relatesTo.eventId ?: return false + + EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, targetEventId).let { eventAnnotationsSummaryEntity -> + ContentMapper + .map(eventAnnotationsSummaryEntity.pollResponseSummary?.aggregatedContent) + ?.toModel() + ?.let { existingPollSummaryContent -> + eventAnnotationsSummaryEntity.pollResponseSummary?.aggregatedContent = ContentMapper.map( + PollSummaryContent( + myVote = existingPollSummaryContent.myVote, + votes = emptyList(), + votesSummary = emptyMap(), + totalVotes = 0, + winnerVoteCount = 0, + ) + .toContent() + ) + } + } + return true + } + + override fun handlePollResponseEvent(session: Session, realm: Realm, event: Event): Boolean { + val content = event.getClearContent()?.toModel() ?: return false + val roomId = event.roomId ?: return false + val senderId = event.senderId ?: return false + val targetEventId = (event.getRelationContent() ?: content.relatesTo)?.eventId ?: return false + val targetPollContent = getPollContent(session, roomId, targetEventId) ?: return false + + val annotationsSummaryEntity = getAnnotationsSummaryEntity(realm, roomId, targetEventId) + val aggregatedPollSummaryEntity = getAggregatedPollSummaryEntity(realm, annotationsSummaryEntity) + + val closedTime = aggregatedPollSummaryEntity.closedTime + val responseTime = event.originServerTs ?: return false + if (closedTime != null && responseTime > closedTime) { + return false + } + + if (aggregatedPollSummaryEntity.sourceEvents.contains(event.eventId)) { + return false + } + + val txId = event.unsignedData?.transactionId + val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "") + if (!isLocalEcho && aggregatedPollSummaryEntity.sourceLocalEchoEvents.contains(txId)) { + aggregatedPollSummaryEntity.sourceLocalEchoEvents.remove(txId) + aggregatedPollSummaryEntity.sourceEvents.add(event.eventId) + return false + } + + val vote = content.getBestResponse()?.answers?.first() ?: return false + if (!targetPollContent.getBestPollCreationInfo()?.answers?.map { it.id }?.contains(vote).orFalse()) { + return false + } + + val pollSummaryModel = ContentMapper.map(aggregatedPollSummaryEntity.aggregatedContent).toModel() + val existingVotes = pollSummaryModel?.votes.orEmpty().toMutableList() + val existingVoteIndex = existingVotes.indexOfFirst { it.userId == senderId } + + if (existingVoteIndex != -1) { + val existingVote = existingVotes[existingVoteIndex] + if (existingVote.voteTimestamp > responseTime) { + return false + } + existingVotes[existingVoteIndex] = VoteInfo(senderId, vote, responseTime) + } else { + existingVotes.add(VoteInfo(senderId, vote, responseTime)) + } + + // Precompute the percentage of votes for all options + val totalVotes = existingVotes.size + val newVotesSummary = existingVotes + .groupBy({ it.option }, { it.userId }) + .mapValues { + VoteSummary( + total = it.value.size, + percentage = if (totalVotes == 0 && it.value.isEmpty()) 0.0 else it.value.size.toDouble() / totalVotes + ) + } + val newWinnerVoteCount = newVotesSummary.maxOf { it.value.total } + + if (isLocalEcho) { + aggregatedPollSummaryEntity.sourceLocalEchoEvents.add(event.eventId) + } else { + aggregatedPollSummaryEntity.sourceEvents.add(event.eventId) + } + + val myVote = existingVotes.find { it.userId == session.myUserId }?.option + + val newSumModel = PollSummaryContent( + myVote = myVote, + votes = existingVotes, + votesSummary = newVotesSummary, + totalVotes = totalVotes, + winnerVoteCount = newWinnerVoteCount + ) + aggregatedPollSummaryEntity.aggregatedContent = ContentMapper.map(newSumModel.toContent()) + + return true + } + + override fun handlePollEndEvent(session: Session, powerLevelsHelper: PowerLevelsHelper, realm: Realm, event: Event): Boolean { + val content = event.getClearContent()?.toModel() ?: return false + val roomId = event.roomId ?: return false + val pollEventId = content.relatesTo?.eventId ?: return false + val pollOwnerId = getPollEvent(session, roomId, pollEventId)?.root?.senderId + val isPollOwner = pollOwnerId == event.senderId + + if (!isPollOwner && !powerLevelsHelper.isUserAbleToRedact(event.senderId ?: "")) { + return false + } + + val annotationsSummaryEntity = getAnnotationsSummaryEntity(realm, roomId, pollEventId) + val aggregatedPollSummaryEntity = getAggregatedPollSummaryEntity(realm, annotationsSummaryEntity) + + val txId = event.unsignedData?.transactionId + aggregatedPollSummaryEntity.closedTime = event.originServerTs + + val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "") + if (!isLocalEcho && aggregatedPollSummaryEntity.sourceLocalEchoEvents.contains(txId)) { + aggregatedPollSummaryEntity.sourceLocalEchoEvents.remove(txId) + aggregatedPollSummaryEntity.sourceEvents.add(event.eventId) + } + + return true + } + + private fun getPollEvent(session: Session, roomId: String, eventId: String): TimelineEvent? { + return session.roomService().getRoom(roomId)?.getTimelineEvent(eventId) + } + + private fun getPollContent(session: Session, roomId: String, eventId: String): MessagePollContent? { + val pollEvent = getPollEvent(session, roomId, eventId) + return pollEvent?.getLastMessageContent() as? MessagePollContent + } + + private fun getAnnotationsSummaryEntity(realm: Realm, roomId: String, eventId: String): EventAnnotationsSummaryEntity { + return EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst() + ?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId) + } + + private fun getAggregatedPollSummaryEntity(realm: Realm, + eventAnnotationsSummaryEntity: EventAnnotationsSummaryEntity): PollResponseAggregatedSummaryEntity { + return eventAnnotationsSummaryEntity.pollResponseSummary + ?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also { + eventAnnotationsSummaryEntity.pollResponseSummary = it + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt new file mode 100644 index 0000000000..848643b435 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.aggregation.poll + +import io.realm.Realm +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper + +interface PollAggregationProcessor { + /** + * Poll start events don't need to be processed by the aggregator. + * This function will only handle if the poll is edited and will update the poll summary entity. + * Returns true if the event is aggregated. + */ + fun handlePollStartEvent( + realm: Realm, + event: Event + ): Boolean + + /** + * Aggregates poll response event after many conditional checks like if the poll is ended, if the user is changing his/her vote etc. + * Returns true if the event is aggregated. + */ + fun handlePollResponseEvent( + session: Session, + realm: Realm, + event: Event + ): Boolean + + /** + * Updates poll summary entity and mark it is ended after many conditional checks like if the poll is already ended etc. + * Returns true if the event is aggregated. + */ + fun handlePollEndEvent( + session: Session, + powerLevelsHelper: PowerLevelsHelper, + realm: Realm, + event: Event + ): Boolean +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt index 7c137a8102..fa19b4f9cf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.internal.session.room.alias -import org.matrix.android.sdk.api.MatrixPatterns.getDomain +import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.internal.di.UserId @@ -65,6 +65,6 @@ internal class RoomAliasAvailabilityChecker @Inject constructor( } companion object { - internal fun String.toFullLocalAlias(userId: String) = "#" + this + ":" + userId.getDomain() + internal fun String.toFullLocalAlias(userId: String) = "#" + this + ":" + userId.getServerName() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt index 59e0f81ece..9e672dcc5c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -52,8 +52,8 @@ internal class RoomDisplayNameResolver @Inject constructor( /** * Compute the room display name. * - * @param realm: the current instance of realm - * @param roomId: the roomId to resolve the name of. + * @param realm the current instance of realm + * @param roomId the roomId to resolve the name of. * @return the room display name */ fun resolve(realm: Realm, roomId: String): RoomName { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTask.kt index 948786677d..983701857f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTask.kt @@ -21,8 +21,8 @@ import timber.log.Timber import java.util.concurrent.atomic.AtomicInteger /** - * @param queueIdentifier String value to identify a unique Queue - * @param taskIdentifier String value to identify a unique Task. Should be different from queueIdentifier + * @property queueIdentifier String value to identify a unique Queue + * @property taskIdentifier String value to identify a unique Task. Should be different from queueIdentifier */ internal abstract class QueuedTask( val queueIdentifier: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt index d8daa55e15..33c3c3929f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt @@ -24,11 +24,12 @@ import retrofit2.http.Query internal interface SpaceApi { /** + * @param spaceId the space Id * @param suggestedOnly Optional. If true, return only child events and rooms where the m.space.child event has suggested: true. - * @param limit: Optional: a client-defined limit to the maximum number of rooms to return per page. Must be a non-negative integer. - * @param maxDepth: Optional: The maximum depth in the tree (from the root room) to return. + * @param limit Optional: a client-defined limit to the maximum number of rooms to return per page. Must be a non-negative integer. + * @param maxDepth Optional: The maximum depth in the tree (from the root room) to return. * The deepest depth returned will not include children events. Defaults to no-limit. Must be a non-negative integer. - * @param from: Optional. Pagination token given to retrieve the next set of rooms. + * @param from Optional. Pagination token given to retrieve the next set of rooms. * Note that if a pagination token is provided, then the parameters given for suggested_only and max_depth must be the same. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_V1 + "rooms/{roomId}/hierarchy") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt index dd95762166..e5a5a0bbad 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt @@ -62,7 +62,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService: /** * Decrypt an encrypted event. * - * @param event the event to decrypt + * @param event the event to decrypt * @param timelineId the timeline identifier * @return true if the event has been decrypted */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt index 03e076c217..9beb8333a4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt @@ -206,6 +206,8 @@ internal class ThreadsAwarenessHandler @Inject constructor( /** * Handle for not thread events that we have marked them as root. * Find relations and inject them accordingly + * @param realm the realm instance + * @param roomId the current room Id * @param eventEntity the current eventEntity received * @param event the current event received * @return The content to inject in the roomSyncHandler live events @@ -229,9 +231,12 @@ internal class ThreadsAwarenessHandler @Inject constructor( * This function is responsible to check if there is any event that relates to our current event. * This is useful when we receive an event that relates to a missing parent, so when later we receive the parent * we can update the child as well. + * @param realm the realm instance + * @param roomId the current room Id * @param event the current event that we examine * @param eventBody the current body of the event * @param isFromCache determines whether or not we already know this is root thread event + * @param threadRelation the information about thread * @return The content to inject in the roomSyncHandler live events */ private fun handleEventsThatRelatesTo( @@ -291,9 +296,12 @@ internal class ThreadsAwarenessHandler @Inject constructor( } /** - * Injecting $eventToInject decrypted content as a reply to $event. - * @param eventToInject the event that will inject + * Injecting [eventToInject] decrypted content as a reply to event. + * @param roomId the room id * @param eventBody the actual event body + * @param eventToInject the event that will inject + * @param eventToInjectBody the event body to inject + * @param threadRelation the information about thread * @return The final content with the injected event */ private fun injectEvent(roomId: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt index bbeff18c01..178f349ec8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt @@ -27,7 +27,7 @@ internal interface AccountDataAPI { * Set some account_data for the client. * * @param userId the user id - * @param type the type + * @param type the type * @param params the put params */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/account_data/{type}") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt index 1da6827916..857105f6ef 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt @@ -95,7 +95,7 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh /** * Send a boolean response. * - * @param response the response + * @param response the response * @param eventData the modular data */ override fun sendBoolResponse(response: Boolean, eventData: JsonDict) { @@ -106,7 +106,7 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh /** * Send an integer response. * - * @param response the response + * @param response the response * @param eventData the modular data */ override fun sendIntegerResponse(response: Int, eventData: JsonDict) { @@ -116,7 +116,9 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh /** * Send an object response. * - * @param response the response + * @param T the Json type + * @param type the type + * @param response the response * @param eventData the modular data */ override fun sendObjectResponse(type: Type, response: T?, eventData: JsonDict) { @@ -145,7 +147,7 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh /** * Send an error. * - * @param message the error message + * @param message the error message * @param eventData the modular data */ override fun sendError(message: String, eventData: JsonDict) { @@ -162,7 +164,7 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh /** * Send the response to the javascript. * - * @param jsString the response data + * @param jsString the response data * @param eventData the modular data */ private fun sendResponse(jsString: String, eventData: JsonDict) = uiHandler.post { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt index b871a317c8..97b40e545e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt @@ -27,6 +27,7 @@ internal interface WidgetsAPI { * Register to the server. * * @param body the body content (Ref: https://github.com/matrix-org/matrix-doc/pull/1961) + * @param version the widget API version */ @POST("register") suspend fun register(@Body body: OpenIdToken, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/CoroutineSequencer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/CoroutineSequencer.kt index 80081e3186..dd4c5e7623 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/CoroutineSequencer.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/CoroutineSequencer.kt @@ -24,6 +24,7 @@ import kotlinx.coroutines.sync.withPermit */ internal interface CoroutineSequencer { /** + * @param T generic type * @param block the suspendable block to execute * @return the result of the block */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/JsonCanonicalizer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/JsonCanonicalizer.kt index 94aa238789..c50b7fe675 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/JsonCanonicalizer.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/JsonCanonicalizer.kt @@ -53,7 +53,7 @@ internal object JsonCanonicalizer { /** * Canonicalize a JSON element. * - * @param src the src + * @param any the src * @return the canonicalize element */ private fun canonicalizeRecursive(any: Any): String { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt index d9fd312a6f..c6a417f6eb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt @@ -54,7 +54,7 @@ internal fun convertFromUTF8(s: String): String { /** * Returns whether a string contains an occurrence of another, as a standalone word, regardless of case. * - * @param subString the string to search for + * @param subString the string to search for * @return whether a match was found */ internal fun String.caseInsensitiveFind(subString: String): Boolean { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt index 0d4a5ac28f..31549155d3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt @@ -75,7 +75,8 @@ internal class DefaultGetWellknownTask @Inject constructor( * - validate homeserver url and identity server url if provide in .well-known result * - return action and .well-known data * - * @param domain: homeserver domain, deduced from mx userId (ex: "matrix.org" from userId "@user:matrix.org") + * @param domain homeserver domain, deduced from mx userId (ex: "matrix.org" from userId "@user:matrix.org") + * @param client Http client to perform the request */ private suspend fun findClientConfig(domain: String, client: OkHttpClient): WellknownResult { val wellKnownAPI = retrofitFactory.create(client, "https://dummy.org") diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessorTest.kt new file mode 100644 index 0000000000..837bbeea26 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessorTest.kt @@ -0,0 +1,162 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.aggregation.poll + +import io.mockk.every +import io.mockk.mockk +import io.realm.RealmList +import io.realm.RealmModel +import io.realm.RealmQuery +import org.amshove.kluent.shouldBeFalse +import org.amshove.kluent.shouldBeTrue +import org.junit.Before +import org.junit.Test +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.getTimelineEvent +import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity +import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntity +import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.AN_EVENT_ID +import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.AN_INVALID_POLL_RESPONSE_EVENT +import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_BROKEN_POLL_REPLACE_EVENT +import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_POLL_END_EVENT +import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_POLL_REFERENCE_EVENT +import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_POLL_REPLACE_EVENT +import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_POLL_RESPONSE_EVENT +import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_POLL_RESPONSE_EVENT_WITH_A_WRONG_REFERENCE +import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_POLL_START_EVENT +import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_ROOM_ID +import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_TIMELINE_EVENT +import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_USER_ID_1 +import org.matrix.android.sdk.test.fakes.FakeRealm + +class PollAggregationProcessorTest { + + private val pollAggregationProcessor: PollAggregationProcessor = DefaultPollAggregationProcessor() + private val realm = FakeRealm() + private val session = mockk() + + @Before + fun setup() { + mockEventAnnotationsSummaryEntity() + mockRoom(A_ROOM_ID, AN_EVENT_ID) + every { session.myUserId } returns A_USER_ID_1 + } + + @Test + fun `given a poll start event, when processing, then is ignored and returns false`() { + pollAggregationProcessor.handlePollStartEvent(realm.instance, A_POLL_START_EVENT).shouldBeFalse() + } + + @Test + fun `given a poll start event with a reference, when processing, then is ignored and returns false`() { + pollAggregationProcessor.handlePollStartEvent(realm.instance, A_POLL_REFERENCE_EVENT).shouldBeFalse() + } + + @Test + fun `given a poll start event with a replace relation but without a target event id, when processing, then is ignored and returns false`() { + pollAggregationProcessor.handlePollStartEvent(realm.instance, A_BROKEN_POLL_REPLACE_EVENT).shouldBeFalse() + } + + @Test + fun `given a poll start event with a replace, when processing, then is processed and returns true`() { + pollAggregationProcessor.handlePollStartEvent(realm.instance, A_POLL_REPLACE_EVENT).shouldBeTrue() + } + + @Test + fun `given a poll response event with a broken reference, when processing, then is ignored and returns false`() { + pollAggregationProcessor.handlePollResponseEvent(session, realm.instance, A_POLL_RESPONSE_EVENT_WITH_A_WRONG_REFERENCE).shouldBeFalse() + } + + @Test + fun `given a poll response event with a reference, when processing, then is processed and returns true`() { + every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity() + pollAggregationProcessor.handlePollResponseEvent(session, realm.instance, A_POLL_RESPONSE_EVENT).shouldBeTrue() + } + + @Test + fun `given a poll response event after poll is closed, when processing, then is ignored and returns false`() { + every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity().apply { + closedTime = (A_POLL_RESPONSE_EVENT.originServerTs ?: 0) - 1 + } + pollAggregationProcessor.handlePollResponseEvent(session, realm.instance, A_POLL_RESPONSE_EVENT).shouldBeFalse() + } + + @Test + fun `given a poll response event which is already processed, when processing, then is ignored and returns false`() { + every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity().apply { + sourceEvents = RealmList(A_POLL_RESPONSE_EVENT.eventId) + } + pollAggregationProcessor.handlePollResponseEvent(session, realm.instance, A_POLL_RESPONSE_EVENT).shouldBeFalse() + } + + @Test + fun `given a poll response event which is not one of the options, when processing, then is ignored and returns false`() { + every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity() + pollAggregationProcessor.handlePollResponseEvent(session, realm.instance, AN_INVALID_POLL_RESPONSE_EVENT).shouldBeFalse() + } + + @Test + fun `given a poll end event, when processing, then is processed and return true`() { + every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity() + val powerLevelsHelper = mockRedactionPowerLevels(A_USER_ID_1, true) + pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT).shouldBeTrue() + } + + @Test + fun `given a poll end event for my own poll without enough redaction power level, when processing, then is processed and returns true`() { + every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity() + val powerLevelsHelper = mockRedactionPowerLevels(A_USER_ID_1, false) + pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT).shouldBeTrue() + } + + @Test + fun `given a poll end event without enough redaction power level, when is processed, then is ignored and return false`() { + every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity() + val powerLevelsHelper = mockRedactionPowerLevels("another-sender-id", false) + val event = A_POLL_END_EVENT.copy(senderId = "another-sender-id") + pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, event).shouldBeFalse() + } + + private inline fun RealmQuery.givenEqualTo(fieldName: String, value: String, result: RealmQuery) { + every { equalTo(fieldName, value) } returns result + } + + private fun mockEventAnnotationsSummaryEntity() { + val queryResult = realm.givenWhereReturns(result = EventAnnotationsSummaryEntity()) + queryResult.givenEqualTo(EventAnnotationsSummaryEntityFields.ROOM_ID, A_POLL_REPLACE_EVENT.roomId!!, queryResult) + queryResult.givenEqualTo(EventAnnotationsSummaryEntityFields.EVENT_ID, A_POLL_REPLACE_EVENT.eventId!!, queryResult) + } + + private fun mockRoom( + roomId: String, + eventId: String + ) { + val room = mockk() + every { session.getRoom(roomId) } returns room + every { room.getTimelineEvent(eventId) } returns A_TIMELINE_EVENT + } + + private fun mockRedactionPowerLevels(userId: String, isAbleToRedact: Boolean): PowerLevelsHelper { + val powerLevelsHelper = mockk() + every { powerLevelsHelper.isUserAbleToRedact(userId) } returns isAbleToRedact + return powerLevelsHelper + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollEventsTestData.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollEventsTestData.kt new file mode 100644 index 0000000000..129d49633e --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollEventsTestData.kt @@ -0,0 +1,171 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.aggregation.poll + +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent +import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent +import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent +import org.matrix.android.sdk.api.session.room.model.message.PollAnswer +import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo +import org.matrix.android.sdk.api.session.room.model.message.PollQuestion +import org.matrix.android.sdk.api.session.room.model.message.PollResponse +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent +import org.matrix.android.sdk.api.session.room.sender.SenderInfo +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent + +object PollEventsTestData { + internal const val A_USER_ID_1 = "@user_1:matrix.org" + internal const val A_ROOM_ID = "!sUeOGZKsBValPTUMax:matrix.org" + internal const val AN_EVENT_ID = "\$vApgexcL8Vfh-WxYKsFKCDooo67ttbjm3TiVKXaWijU" + + internal val A_POLL_CONTENT = MessagePollContent( + unstablePollCreationInfo = PollCreationInfo( + question = PollQuestion( + unstableQuestion = "What is your favourite coffee?" + ), + maxSelections = 1, + answers = listOf( + PollAnswer( + id = "5ef5f7b0-c9a1-49cf-a0b3-374729a43e76", + unstableAnswer = "Double Espresso" + ), + PollAnswer( + id = "ec1a4db0-46d8-4d7a-9bb6-d80724715938", + unstableAnswer = "Macchiato" + ), + PollAnswer( + id = "3677ca8e-061b-40ab-bffe-b22e4e88fcad", + unstableAnswer = "Iced Coffee" + ) + ) + ) + ) + + internal val A_POLL_RESPONSE_CONTENT = MessagePollResponseContent( + unstableResponse = PollResponse( + answers = listOf("5ef5f7b0-c9a1-49cf-a0b3-374729a43e76") + ), + relatesTo = RelationDefaultContent( + type = RelationType.REFERENCE, + eventId = AN_EVENT_ID + ) + ) + + internal val A_POLL_END_CONTENT = MessageEndPollContent( + relatesTo = RelationDefaultContent( + type = RelationType.REFERENCE, + eventId = AN_EVENT_ID + ) + ) + + internal val AN_INVALID_POLL_RESPONSE_CONTENT = MessagePollResponseContent( + unstableResponse = PollResponse( + answers = listOf("fake-option-id") + ), + relatesTo = RelationDefaultContent( + type = RelationType.REFERENCE, + eventId = AN_EVENT_ID + ) + ) + + internal val A_POLL_START_EVENT = Event( + type = EventType.POLL_START.first(), + eventId = AN_EVENT_ID, + originServerTs = 1652435922563, + senderId = A_USER_ID_1, + roomId = A_ROOM_ID, + content = A_POLL_CONTENT.toContent() + ) + + internal val A_POLL_RESPONSE_EVENT = Event( + type = EventType.POLL_RESPONSE.first(), + eventId = AN_EVENT_ID, + originServerTs = 1652435922563, + senderId = A_USER_ID_1, + roomId = A_ROOM_ID, + content = A_POLL_RESPONSE_CONTENT.toContent() + ) + + internal val A_POLL_END_EVENT = Event( + type = EventType.POLL_END.first(), + eventId = AN_EVENT_ID, + originServerTs = 1652435922563, + senderId = A_USER_ID_1, + roomId = A_ROOM_ID, + content = A_POLL_END_CONTENT.toContent() + ) + + internal val A_TIMELINE_EVENT = TimelineEvent( + root = A_POLL_START_EVENT, + localId = 1234, + eventId = AN_EVENT_ID, + displayIndex = 0, + senderInfo = SenderInfo(A_USER_ID_1, "A_USER_ID_1", true, null) + ) + + internal val A_POLL_RESPONSE_EVENT_WITH_A_WRONG_REFERENCE = A_POLL_RESPONSE_EVENT.copy( + content = A_POLL_RESPONSE_CONTENT + .copy( + relatesTo = RelationDefaultContent( + type = RelationType.REPLACE, + eventId = null + ) + ) + .toContent() + ) + + internal val A_POLL_REPLACE_EVENT = A_POLL_START_EVENT.copy( + content = A_POLL_CONTENT + .copy( + relatesTo = RelationDefaultContent( + type = RelationType.REPLACE, + eventId = AN_EVENT_ID + ) + ) + .toContent() + ) + + internal val A_BROKEN_POLL_REPLACE_EVENT = A_POLL_START_EVENT.copy( + content = A_POLL_CONTENT + .copy( + relatesTo = RelationDefaultContent( + type = RelationType.REPLACE, + eventId = null + ) + ) + .toContent() + ) + + internal val A_POLL_REFERENCE_EVENT = A_POLL_START_EVENT.copy( + content = A_POLL_CONTENT + .copy( + relatesTo = RelationDefaultContent( + type = RelationType.REFERENCE, + eventId = AN_EVENT_ID + ) + ) + .toContent() + ) + + internal val AN_INVALID_POLL_RESPONSE_EVENT = A_POLL_RESPONSE_EVENT.copy( + content = AN_INVALID_POLL_RESPONSE_CONTENT.toContent() + ) +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt new file mode 100644 index 0000000000..c07f8e1873 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 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.test.fakes + +import io.mockk.every +import io.mockk.mockk +import io.realm.Realm +import io.realm.RealmModel +import io.realm.RealmQuery +import io.realm.kotlin.where + +internal class FakeRealm { + + val instance = mockk(relaxed = true) + + inline fun givenWhereReturns(result: T?): RealmQuery { + val queryResult = mockk>() + every { queryResult.findFirst() } returns result + every { instance.where() } returns queryResult + return queryResult + } +} diff --git a/tools/check/check_code_quality.sh b/tools/check/check_code_quality.sh index e40d3635e8..910616176c 100755 --- a/tools/check/check_code_quality.sh +++ b/tools/check/check_code_quality.sh @@ -67,6 +67,9 @@ echo "Search for forbidden patterns in code..." ${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_code.txt \ ./matrix-sdk-android/src/main/java \ ./matrix-sdk-android-flow/src/main/java \ + ./library/core-utils/src/main/java \ + ./library/jsonviewer/src/main/java \ + ./library/ui-styles/src/main/java \ ./vector/src/main/java \ ./vector/src/debug/java \ ./vector/src/release/java \ @@ -100,6 +103,7 @@ echo echo "Search for forbidden patterns in resources..." ${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_resources.txt \ + ./library/ui-styles/src/main/res/values \ ./vector/src/main/res/color \ ./vector/src/main/res/layout \ ./vector/src/main/res/values \ diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index 7362ff2d10..962a14843d 100755 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -177,3 +177,6 @@ R\.string\.template_ ### Use the Clock interface, or use `measureTimeMillis` System\.currentTimeMillis\(\)===2 + +### Remove extra space between the name and the description +\* @\w+ \w+ + diff --git a/tools/detekt/detekt.yml b/tools/detekt/detekt.yml index 322f29e5b7..a836edc47a 100644 --- a/tools/detekt/detekt.yml +++ b/tools/detekt/detekt.yml @@ -87,8 +87,7 @@ comments: EndOfSentenceFormat: active: true OutdatedDocumentation: - # TODO Enable it - active: false + active: true UndocumentedPublicClass: active: false UndocumentedPublicFunction: diff --git a/vector/build.gradle b/vector/build.gradle index affe10e57b..4ae6527e0b 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -31,7 +31,7 @@ ext.versionMinor = 4 // 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 = 16 +ext.versionPatch = 18 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' @@ -452,9 +452,6 @@ dependencies { kapt libs.github.glideCompiler implementation 'com.github.yalantis:ucrop:2.2.8' - // Badge for compatibility - implementation 'me.leolin:ShortcutBadger:1.1.22@aar' - // Chat effects implementation 'nl.dionsegijn:konfetti-xml:2.0.2' diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt index 3c5de8b221..b9292bd916 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt @@ -120,6 +120,26 @@ class ElementRobot { .perform(ViewActions.closeSoftKeyboard(), click()) } } + // at this point we are in a race with the app restarting. The steps that happen are: + // - (initially) app has started, app has initial synched + // - (restart) app has strted, app has not initial synched + // - (racey) app shows some UI but overlays with initial sync ui + // - (initial sync finishes) app has started, has initial synched + + // We need to wait for the initial sync to complete; but we can't + // use waitForHome() like login does. + + // waitForHome() -- does not work because we have already fufilled the initialSync + // so we can racily have an IllegalStateException that we have transitioned from busy -> idle + // but never having sent the signal. + + // So we need to not start waiting for an initial sync until we have restarted + // then we do need to wait for the sync to complete. + + // Which is convoluted especially as it involves the app state refreshing + // so; in order to make this be more stable + // I hereby cheat and write: + Thread.sleep(30_000) } else -> { } diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceMenuRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceMenuRobot.kt index 4d35e3c550..289c6e21b4 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceMenuRobot.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceMenuRobot.kt @@ -104,11 +104,10 @@ class SpaceMenuRobot { fun leaveSpace() { clickOnSheet(R.id.leaveSpace) - waitUntilDialogVisible(ViewMatchers.withId(R.id.leaveButton)) - clickOn(R.id.leave_selected) waitUntilActivityVisible { waitUntilViewVisible(ViewMatchers.withId(R.id.roomList)) } + clickOn(R.id.spaceLeaveSelectAll) clickOn(R.id.spaceLeaveButton) waitUntilViewVisible(ViewMatchers.withId(R.id.groupListView)) } diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt index f2904e4b1a..00a073f832 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt @@ -60,11 +60,6 @@ class DebugFeaturesStateFactory @Inject constructor( key = DebugFeatureKeys.onboardingCombinedRegister, factory = VectorFeatures::isOnboardingCombinedRegisterEnabled ), - createBooleanFeature( - label = "Live location sharing", - key = DebugFeatureKeys.liveLocationSharing, - factory = VectorFeatures::isLiveLocationEnabled - ), ) ) } diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt index 07fab8a58d..1bc37ff97e 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt @@ -57,9 +57,6 @@ class DebugVectorFeatures( override fun isOnboardingCombinedRegisterEnabled(): Boolean = read(DebugFeatureKeys.onboardingCombinedRegister) ?: vectorFeatures.isOnboardingCombinedRegisterEnabled() - override fun isLiveLocationEnabled(): Boolean = read(DebugFeatureKeys.liveLocationSharing) - ?: vectorFeatures.isLiveLocationEnabled() - override fun isScreenSharingEnabled(): Boolean = read(DebugFeatureKeys.screenSharing) ?: vectorFeatures.isScreenSharingEnabled() diff --git a/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt b/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt index 425fd1081a..6e36d5dd81 100755 --- a/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt +++ b/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt @@ -46,7 +46,7 @@ object FcmHelper { * Store FCM token to the SharedPrefs * * @param context android context - * @param token the token to store + * @param token the token to store */ fun storeFcmToken(context: Context, token: String?) { // No op diff --git a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt index b62520278a..a7d814052a 100755 --- a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt @@ -1,15 +1,12 @@ /* * Copyright 2019 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. @@ -32,7 +29,6 @@ import im.vector.app.BuildConfig import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.network.WifiDetector import im.vector.app.core.pushers.PushersManager -import im.vector.app.features.badge.BadgeProxy import im.vector.app.features.notifications.NotifiableEventResolver import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.notifications.NotificationUtils @@ -152,10 +148,6 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { Timber.tag(loggerTag.value).d("## onMessageReceivedInternal()") } - // update the badge counter - val unreadCount = data["unread"]?.let { Integer.parseInt(it) } ?: 0 - BadgeProxy.updateBadgeCount(applicationContext, unreadCount) - val session = activeSessionHolder.getSafeActiveSession() if (session == null) { diff --git a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt b/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt index 3d44f10f76..74ab3b38f1 100755 --- a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt +++ b/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt @@ -53,7 +53,7 @@ object FcmHelper { * TODO Store in realm * * @param context android context - * @param token the token to store + * @param token the token to store */ fun storeFcmToken(context: Context, token: String?) { diff --git a/vector/src/main/assets/open_source_licenses.html b/vector/src/main/assets/open_source_licenses.html index 0bead1f826..8f27776fbf 100755 --- a/vector/src/main/assets/open_source_licenses.html +++ b/vector/src/main/assets/open_source_licenses.html @@ -369,11 +369,6 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Copyright 2012 Square, Inc. -
  • - ShortcutBadger -
    - Copyright 2014 Leo Lin -
  • diff-match-patch
    diff --git a/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt b/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt index c43b2e4f09..8eaced1c48 100644 --- a/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt +++ b/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt @@ -39,8 +39,9 @@ class UnrecognizedCertificateDialog @Inject constructor( * Display a certificate dialog box, asking the user about an unknown certificate * To use when user is currently logged in. * + * @param activity the Android activity * @param unrecognizedFingerprint the fingerprint for the unknown certificate - * @param callback callback to fire when the user makes a decision + * @param callback callback to fire when the user makes a decision */ fun show(activity: Activity, unrecognizedFingerprint: Fingerprint, @@ -80,9 +81,13 @@ class UnrecognizedCertificateDialog @Inject constructor( /** * Display a certificate dialog box, asking the user about an unknown certificate. * + * @param activity the Activity * @param unrecognizedFingerprint the fingerprint for the unknown certificate - * @param existing the current session already exist, so it mean that something has changed server side - * @param callback callback to fire when the user makes a decision + * @param existing the current session already exist, so it mean that something has changed server side + * @param callback callback to fire when the user makes a decision + * @param userId the matrix userId + * @param homeServerUrl the homeserver url + * @param homeServerConnectionConfigHasFingerprints true if the homeServerConnectionConfig has fingerprint */ private fun internalShow(activity: Activity, unrecognizedFingerprint: Fingerprint, diff --git a/vector/src/main/java/im/vector/app/core/intent/ExternalIntentData.kt b/vector/src/main/java/im/vector/app/core/intent/ExternalIntentData.kt index 142a7a6782..1d7247d758 100644 --- a/vector/src/main/java/im/vector/app/core/intent/ExternalIntentData.kt +++ b/vector/src/main/java/im/vector/app/core/intent/ExternalIntentData.kt @@ -29,9 +29,11 @@ sealed class ExternalIntentData { /** * Constructor for a text message. * - * @param text the text - * @param htmlText the HTML text - * @param format the formatted text format + * @property text the text + * @property htmlText the HTML text + * @property format the formatted text format + * @property clipDataItem the ClipData + * @property mimeType the mimetype */ data class IntentDataText( val text: CharSequence? = null, @@ -52,8 +54,8 @@ sealed class ExternalIntentData { /** * Constructor from a media Uri/. * - * @param uri the media uri - * @param filename the media file name + * @property uri the media uri + * @property filename the media file name */ data class IntentDataUri( val uri: Uri, diff --git a/vector/src/main/java/im/vector/app/core/intent/VectorMimeType.kt b/vector/src/main/java/im/vector/app/core/intent/VectorMimeType.kt index e68b5e1b07..38e304e1ce 100644 --- a/vector/src/main/java/im/vector/app/core/intent/VectorMimeType.kt +++ b/vector/src/main/java/im/vector/app/core/intent/VectorMimeType.kt @@ -27,6 +27,7 @@ import java.util.Locale * Returns the mimetype from a uri. * * @param context the context + * @param uri the uri * @return the mimetype */ fun getMimeTypeFromUri(context: Context, uri: Uri): String? { diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index d904a5a4de..8a4aaa4b26 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -55,6 +55,7 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActivityEntryPoint import im.vector.app.core.dialogs.DialogLocker import im.vector.app.core.dialogs.UnrecognizedCertificateDialog +import im.vector.app.core.error.fatalError import im.vector.app.core.extensions.observeEvent import im.vector.app.core.extensions.observeNotNull import im.vector.app.core.extensions.registerStartForActivityResult @@ -611,11 +612,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver } }.show() } else { - if (vectorPreferences.failFast()) { - error("No CoordinatorLayout to display this snackbar!") - } else { - Timber.w("No CoordinatorLayout to display this snackbar!") - } + fatalError("No CoordinatorLayout to display this snackbar!", vectorPreferences.failFast()) } } diff --git a/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt b/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt index dad7f26560..bea29195c9 100644 --- a/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt +++ b/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt @@ -45,7 +45,7 @@ class PushRulePreference : VectorPreference { /** * Update the notification index. * - * @param pushRule + * @param notificationIndex the new notification index */ fun setIndex(notificationIndex: NotificationIndex?) { index = notificationIndex diff --git a/vector/src/main/java/im/vector/app/core/resources/DateProvider.kt b/vector/src/main/java/im/vector/app/core/resources/DateProvider.kt index 30cb1dcae4..6762bd68da 100644 --- a/vector/src/main/java/im/vector/app/core/resources/DateProvider.kt +++ b/vector/src/main/java/im/vector/app/core/resources/DateProvider.kt @@ -19,27 +19,30 @@ package im.vector.app.core.resources import org.threeten.bp.Instant import org.threeten.bp.LocalDateTime import org.threeten.bp.ZoneId +import org.threeten.bp.ZoneOffset object DateProvider { - private val zoneId = ZoneId.systemDefault() - private val zoneOffset by lazy { - val now = currentLocalDateTime() - zoneId.rules.getOffset(now) - } + // recompute the zoneId each time we access it to handle change of timezones + private val defaultZoneId: ZoneId + get() = ZoneId.systemDefault() + + // recompute the zoneOffset each time we access it to handle change of timezones + private val defaultZoneOffset: ZoneOffset + get() = defaultZoneId.rules.getOffset(currentLocalDateTime()) fun toLocalDateTime(timestamp: Long?): LocalDateTime { val instant = Instant.ofEpochMilli(timestamp ?: 0) - return LocalDateTime.ofInstant(instant, zoneId) + return LocalDateTime.ofInstant(instant, defaultZoneId) } fun currentLocalDateTime(): LocalDateTime { val instant = Instant.now() - return LocalDateTime.ofInstant(instant, zoneId) + return LocalDateTime.ofInstant(instant, defaultZoneId) } fun toTimestamp(localDateTime: LocalDateTime): Long { - return localDateTime.toInstant(zoneOffset).toEpochMilli() + return localDateTime.toInstant(defaultZoneOffset).toEpochMilli() } } diff --git a/vector/src/main/java/im/vector/app/core/resources/Resource.kt b/vector/src/main/java/im/vector/app/core/resources/Resource.kt index f14c9b834d..861dfdb781 100644 --- a/vector/src/main/java/im/vector/app/core/resources/Resource.kt +++ b/vector/src/main/java/im/vector/app/core/resources/Resource.kt @@ -56,8 +56,8 @@ data class Resource( /** * Get a resource stream and metadata about it given its URI returned from onActivityResult. * - * @param context the context. - * @param uri the URI + * @param context the context. + * @param uri the URI * @param providedMimetype the mimetype * @return a [Resource] encapsulating the opened resource stream and associated metadata * or `null` if opening the resource stream failed. diff --git a/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt b/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt index f2ea79984e..80603aa3bf 100755 --- a/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt @@ -54,6 +54,7 @@ class KeysBackupBanner @JvmOverloads constructor( * This methods is responsible for rendering the view according to the newState. * * @param newState the newState representing the view + * @param force true to force the rendering of the view */ fun render(newState: State, force: Boolean = false) { if (newState == state && !force) { diff --git a/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageAvatar.kt b/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageAvatar.kt index 22312f36fa..1f78584a6b 100644 --- a/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageAvatar.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageAvatar.kt @@ -22,7 +22,7 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.LinearLayout -import im.vector.app.core.utils.DimensionConverter +import im.vector.app.R import im.vector.app.features.home.AvatarRenderer import org.matrix.android.sdk.api.session.room.sender.SenderInfo import org.matrix.android.sdk.api.util.toMatrixItem @@ -34,19 +34,22 @@ class TypingMessageAvatar @JvmOverloads constructor( ) : LinearLayout(context, attrs, defStyleAttr) { companion object { - const val AVATAR_SIZE_DP = 20 const val OVERLAP_FACT0R = -3 // =~ 30% to left } + private val typingAvatarSize by lazy(LazyThreadSafetyMode.NONE) { + context.resources.getDimension(R.dimen.typing_avatar_size).toInt() + } + fun render(typingUsers: List, avatarRenderer: AvatarRenderer) { removeAllViews() for ((index, value) in typingUsers.withIndex()) { val avatar = ImageView(context) avatar.id = View.generateViewId() val layoutParams = MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) - if (index != 0) layoutParams.marginStart = DimensionConverter(resources).dpToPx(AVATAR_SIZE_DP / OVERLAP_FACT0R) - layoutParams.width = DimensionConverter(resources).dpToPx(AVATAR_SIZE_DP) - layoutParams.height = DimensionConverter(resources).dpToPx(AVATAR_SIZE_DP) + if (index != 0) layoutParams.marginStart = typingAvatarSize / OVERLAP_FACT0R + layoutParams.width = typingAvatarSize + layoutParams.height = typingAvatarSize avatar.layoutParams = layoutParams avatarRenderer.render(value.toMatrixItem(), avatar) addView(avatar) diff --git a/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageView.kt b/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageView.kt index 2dc9eedd99..263f043fad 100644 --- a/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageView.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageView.kt @@ -31,7 +31,8 @@ import javax.inject.Inject class TypingMessageView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, - defStyleAttr: Int = 0) : ConstraintLayout(context, attrs, defStyleAttr) { + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr) { val views: TypingMessageLayoutBinding @@ -44,8 +45,8 @@ class TypingMessageView @JvmOverloads constructor( } fun render(typingUsers: List, avatarRenderer: AvatarRenderer) { - views.usersName.text = typingHelper.getNotificationTypingMessage(typingUsers) - views.avatars.render(typingUsers, avatarRenderer) + views.typingUserText.text = typingHelper.getNotificationTypingMessage(typingUsers) + views.typingUserAvatars.render(typingUsers, avatarRenderer) } override fun onDetachedFromWindow() { diff --git a/vector/src/main/java/im/vector/app/core/utils/Dialogs.kt b/vector/src/main/java/im/vector/app/core/utils/Dialogs.kt index 10ab0fc027..9f3e6a91cf 100644 --- a/vector/src/main/java/im/vector/app/core/utils/Dialogs.kt +++ b/vector/src/main/java/im/vector/app/core/utils/Dialogs.kt @@ -30,7 +30,7 @@ import me.gujun.android.span.span /** * Open a web view above the current activity. * - * @param url the url to open + * @param url the url to open */ fun Context.displayInWebView(url: String) { val wv = WebView(this) diff --git a/vector/src/main/java/im/vector/app/core/utils/EvenBetterLinkMovementMethod.kt b/vector/src/main/java/im/vector/app/core/utils/EvenBetterLinkMovementMethod.kt index b9c1386933..a53c8161b1 100644 --- a/vector/src/main/java/im/vector/app/core/utils/EvenBetterLinkMovementMethod.kt +++ b/vector/src/main/java/im/vector/app/core/utils/EvenBetterLinkMovementMethod.kt @@ -26,9 +26,9 @@ class EvenBetterLinkMovementMethod(private val onLinkClickListener: OnLinkClickL interface OnLinkClickListener { /** - * @param textView The TextView on which a click was registered. - * @param span The ClickableSpan which is clicked on. - * @param url The clicked URL. + * @param textView The TextView on which a click was registered. + * @param span The ClickableSpan which is clicked on. + * @param url The clicked URL. * @param actualText The original text which is spanned. Can be used to compare actualText and target url to prevent misleading urls. * @return true if this click was handled, false to let Android handle the URL. */ diff --git a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt index 8bfbcaeb92..9616e35840 100644 --- a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt +++ b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt @@ -170,9 +170,9 @@ fun openUri(activity: Activity, uri: String) { /** * Send media to a third party application. * - * @param activity the activity + * @param activity the activity * @param savedMediaPath the media path - * @param mimeType the media mime type. + * @param mimeType the media mime type. */ fun openMedia(activity: Activity, savedMediaPath: String, mimeType: String) { val file = File(savedMediaPath) @@ -415,8 +415,8 @@ fun selectTxtFileToWrite( * * ~~ This is copied from the old matrix sdk ~~ * - * @param sourceFile the file source path - * @param dstDirPath the dst path + * @param sourceFile the file source path + * @param dstDirPath the dst path * @param outputFilename optional the output filename * @param currentTimeMillis the current time in milliseconds * @return the created file diff --git a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt index b4f8de2485..a41abba7ab 100644 --- a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt +++ b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt @@ -101,9 +101,9 @@ private fun onPermissionResult(result: Map, lambda: (allGranted * explain why vector needs the corresponding permission. * * @param permissionsToBeGranted the permissions to be granted - * @param activity the calling Activity that is requesting the permissions (or fragment parent) + * @param activity the calling Activity that is requesting the permissions (or fragment parent) * @param activityResultLauncher from the calling fragment/Activity that is requesting the permissions - * @param rationaleMessage message to be displayed BEFORE requesting for the permission + * @param rationaleMessage message to be displayed BEFORE requesting for the permission * @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow) */ fun checkPermissions(permissionsToBeGranted: List, @@ -145,7 +145,7 @@ fun checkPermissions(permissionsToBeGranted: List, * To be call after the permission request. * * @param permissionsToBeGranted the permissions to be granted - * @param activity the calling Activity that is requesting the permissions (or fragment parent) + * @param activity the calling Activity that is requesting the permissions (or fragment parent) * * @return true if one of the permission has been denied and the user check the do not ask again checkbox */ diff --git a/vector/src/main/java/im/vector/app/core/utils/RingtoneUtils.kt b/vector/src/main/java/im/vector/app/core/utils/RingtoneUtils.kt index a0fd3addac..bbed2f6000 100644 --- a/vector/src/main/java/im/vector/app/core/utils/RingtoneUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/RingtoneUtils.kt @@ -90,6 +90,7 @@ fun getCallRingtoneName(context: Context): String? { /** * Sets the selected ringtone for riot calls. * + * @param context Android context * @param ringtoneUri * @see Ringtone */ diff --git a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt index 18a467d8d0..1d9ac6c3ef 100644 --- a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt @@ -82,7 +82,9 @@ fun requestDisablingBatteryOptimization(activity: Activity, activityResultLaunch * Copy a text to the clipboard, and display a Toast when done. * * @param context the context - * @param text the text to copy + * @param text the text to copy + * @param showToast true to also show a Toast to the user + * @param toastMessage content of the toast message as a String resource */ fun copyToClipboard(context: Context, text: CharSequence, showToast: Boolean = true, @StringRes toastMessage: Int = R.string.copied_to_clipboard) { val clipboard = context.getSystemService()!! diff --git a/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt b/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt index fb386e0876..bd1e396126 100644 --- a/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt +++ b/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt @@ -23,7 +23,8 @@ const val THREE_MINUTES = 3 * 60_000L /** * Store an object T for a specific period of time. - * @param delay delay to keep the data, in millis + * @param T type of the data to store + * @property delay delay to keep the data, in millis */ open class TemporaryStore(private val delay: Long = THREE_MINUTES) { diff --git a/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt b/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt index 992a85679c..d2f8c4022b 100644 --- a/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/TextUtils.kt @@ -19,11 +19,15 @@ package im.vector.app.core.utils import android.content.Context import android.os.Build import android.text.format.Formatter +import im.vector.app.R import org.threeten.bp.Duration import java.util.TreeMap object TextUtils { + private const val MINUTES_PER_HOUR = 60 + private const val SECONDS_PER_MINUTE = 60 + private val suffixes = TreeMap().also { it[1000] = "k" it[1000000] = "M" @@ -71,13 +75,63 @@ object TextUtils { } fun formatDuration(duration: Duration): String { - val hours = duration.seconds / 3600 - val minutes = (duration.seconds % 3600) / 60 - val seconds = duration.seconds % 60 + val hours = getHours(duration) + val minutes = getMinutes(duration) + val seconds = getSeconds(duration) return if (hours > 0) { String.format("%d:%02d:%02d", hours, minutes, seconds) } else { String.format("%02d:%02d", minutes, seconds) } } + + fun formatDurationWithUnits(context: Context, duration: Duration): String { + val hours = getHours(duration) + val minutes = getMinutes(duration) + val seconds = getSeconds(duration) + val builder = StringBuilder() + when { + hours > 0 -> { + appendHours(context, builder, hours) + if (minutes > 0) { + builder.append(" ") + appendMinutes(context, builder, minutes) + } + if (seconds > 0) { + builder.append(" ") + appendSeconds(context, builder, seconds) + } + } + minutes > 0 -> { + appendMinutes(context, builder, minutes) + if (seconds > 0) { + builder.append(" ") + appendSeconds(context, builder, seconds) + } + } + else -> { + appendSeconds(context, builder, seconds) + } + } + return builder.toString() + } + + private fun appendHours(context: Context, builder: StringBuilder, hours: Int) { + builder.append(hours) + builder.append(context.resources.getString(R.string.time_unit_hour_short)) + } + + private fun appendMinutes(context: Context, builder: StringBuilder, minutes: Int) { + builder.append(minutes) + builder.append(context.getString(R.string.time_unit_minute_short)) + } + + private fun appendSeconds(context: Context, builder: StringBuilder, seconds: Int) { + builder.append(seconds) + builder.append(context.getString(R.string.time_unit_second_short)) + } + + private fun getHours(duration: Duration): Int = duration.toHours().toInt() + private fun getMinutes(duration: Duration): Int = duration.toMinutes().toInt() % MINUTES_PER_HOUR + private fun getSeconds(duration: Duration): Int = (duration.seconds % SECONDS_PER_MINUTE).toInt() } diff --git a/vector/src/main/java/im/vector/app/core/utils/ToggleableAppBarLayoutBehavior.kt b/vector/src/main/java/im/vector/app/core/utils/ToggleableAppBarLayoutBehavior.kt new file mode 100644 index 0000000000..c829313256 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/utils/ToggleableAppBarLayoutBehavior.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022 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.core.utils + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import androidx.coordinatorlayout.widget.CoordinatorLayout +import com.google.android.material.appbar.AppBarLayout + +/** + * [AppBarLayout.Behavior] subclass with a possibility to disable behavior. + * Useful for cases when in some view state we want prevent toolbar from collapsing/expanding by scroll events + */ +class ToggleableAppBarLayoutBehavior : AppBarLayout.Behavior { + constructor() : super() + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + + var isEnabled = true + + override fun onStartNestedScroll(parent: CoordinatorLayout, + child: AppBarLayout, + directTargetChild: View, + target: View, + nestedScrollAxes: Int, + type: Int): Boolean { + return isEnabled && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type) + } + + override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, + child: AppBarLayout, + target: View, + dxConsumed: Int, + dyConsumed: Int, + dxUnconsumed: Int, + dyUnconsumed: Int, + type: Int, + consumed: IntArray) { + if (!isEnabled) return + super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed) + } + + override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, + child: AppBarLayout, + target: View, + dx: Int, + dy: Int, + consumed: IntArray, + type: Int) { + if (!isEnabled) return + super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type) + } +} diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt index 42693a53f9..e3fded2824 100644 --- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt +++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt @@ -26,7 +26,6 @@ interface VectorFeatures { fun isOnboardingUseCaseEnabled(): Boolean fun isOnboardingPersonalizeEnabled(): Boolean fun isOnboardingCombinedRegisterEnabled(): Boolean - fun isLiveLocationEnabled(): Boolean fun isScreenSharingEnabled(): Boolean enum class OnboardingVariant { @@ -43,6 +42,5 @@ class DefaultVectorFeatures : VectorFeatures { override fun isOnboardingUseCaseEnabled() = true override fun isOnboardingPersonalizeEnabled() = false override fun isOnboardingCombinedRegisterEnabled() = false - override fun isLiveLocationEnabled(): Boolean = false override fun isScreenSharingEnabled(): Boolean = true } diff --git a/vector/src/main/java/im/vector/app/features/badge/BadgeProxy.kt b/vector/src/main/java/im/vector/app/features/badge/BadgeProxy.kt deleted file mode 100644 index c15ec8bdf4..0000000000 --- a/vector/src/main/java/im/vector/app/features/badge/BadgeProxy.kt +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2019 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. - */ - -@file:Suppress("UNUSED_PARAMETER") - -package im.vector.app.features.badge - -import android.content.Context -import android.os.Build -import me.leolin.shortcutbadger.ShortcutBadger -import org.matrix.android.sdk.api.session.Session - -/** - * Manage application badge (displayed in the launcher). - */ -object BadgeProxy { - - /** - * Badge is now managed by notification channel, so no need to use compatibility library in recent versions. - * - * @return true if library ShortcutBadger can be used - */ - private fun useShortcutBadger() = Build.VERSION.SDK_INT < Build.VERSION_CODES.O - - /** - * Update the application badge value. - * - * @param context the context - * @param badgeValue the new badge value - */ - fun updateBadgeCount(context: Context, badgeValue: Int) { - if (!useShortcutBadger()) { - return - } - - ShortcutBadger.applyCount(context, badgeValue) - } - - /** - * Refresh the badge count for specific configurations.

    - * The refresh is only effective if the device is: - * * offline * does not support FCM - * * FCM registration failed - *

    Notifications rooms are parsed to track the notification count value. - * - * @param aSession session value - * @param aContext App context - */ - fun specificUpdateBadgeUnreadCount(aSession: Session?, aContext: Context?) { - if (!useShortcutBadger()) { - return - } - - /* TODO - val dataHandler: MXDataHandler - - // sanity check - if (null == aContext || null == aSession) { - Timber.w("## specificUpdateBadgeUnreadCount(): invalid input null values") - } else { - dataHandler = aSession.dataHandler - - if (dataHandler == null) { - Timber.w("## specificUpdateBadgeUnreadCount(): invalid DataHandler instance") - } else { - if (aSession.isAlive) { - var isRefreshRequired: Boolean - val pushManager = Matrix.getInstance(aContext)!!.pushManager - - // update the badge count if the device is offline, FCM is not supported or FCM registration failed - isRefreshRequired = !Matrix.getInstance(aContext)!!.isConnected - isRefreshRequired = isRefreshRequired or (null != pushManager && (!pushManager.useFcm() || !pushManager.hasRegistrationToken())) - - if (isRefreshRequired) { - updateBadgeCount(aContext, dataHandler) - } - } - } - } - */ - } - - /** - * Update the badge count value according to the rooms content. - * - * @param aContext App context - * @param aDataHandler data handler instance - */ - private fun updateBadgeCount(aSession: Session?, aContext: Context?) { - if (!useShortcutBadger()) { - return - } - - /* TODO - //sanity check - if (null == aContext || null == aDataHandler) { - Timber.w("## updateBadgeCount(): invalid input null values") - } else if (null == aDataHandler.store) { - Timber.w("## updateBadgeCount(): invalid store instance") - } else { - val roomCompleteList = ArrayList(aDataHandler.store.rooms) - var unreadRoomsCount = 0 - - for (room in roomCompleteList) { - if (room.notificationCount > 0) { - unreadRoomsCount++ - } - } - - // update the badge counter - Timber.v("## updateBadgeCount(): badge update count=$unreadRoomsCount") - updateBadgeCount(aContext, unreadRoomsCount) - } - */ - } -} diff --git a/vector/src/main/java/im/vector/app/features/call/audio/CallAudioManager.kt b/vector/src/main/java/im/vector/app/features/call/audio/CallAudioManager.kt index d4793640d3..6577d0374d 100644 --- a/vector/src/main/java/im/vector/app/features/call/audio/CallAudioManager.kt +++ b/vector/src/main/java/im/vector/app/features/call/audio/CallAudioManager.kt @@ -122,6 +122,7 @@ class CallAudioManager(private val context: Context, val configChange: (() -> Un * Updates the audio route for the given mode. * * @param mode the audio mode to be used when computing the audio route. + * @param force true to force setting the audio route * @return `true` if the audio route was updated successfully; * `false`, otherwise. */ diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt index 672e9ca849..b92316c292 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt @@ -683,6 +683,8 @@ class WebRtcCall( direction = RtpTransceiver.RtpTransceiverDirection.SEND_RECV } for (transceiver in peerConnection?.transceivers ?: emptyList()) { + transceiver.sender.track()?.setEnabled(!onHold) + transceiver.receiver.track()?.setEnabled(!onHold) transceiver.direction = direction } updateMuteStatus() diff --git a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt index 49e35687f4..17b8087601 100644 --- a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt @@ -30,7 +30,8 @@ class CommandParser @Inject constructor() { /** * Convert the text message into a Slash command. * - * @param textMessage the text message + * @param textMessage the text message + * @param isInThreadTimeline true if the user is currently typing in a thread * @return a parsed slash command (ok or error) */ fun parseSlashCommand(textMessage: CharSequence, isInThreadTimeline: Boolean): ParsedCommand { @@ -412,8 +413,8 @@ class CommandParser @Inject constructor() { /** * Checks whether or not the current command is not supported by threads. - * @param slashCommand the slash command that will be checked * @param isInThreadTimeline if its true we are in a thread timeline + * @param slashCommand the slash command that will be checked * @return The command that is not supported */ private fun getNotSupportedByThreads(isInThreadTimeline: Boolean, slashCommand: String): Command? { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 2bb620623c..635b00c05d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -1529,7 +1529,7 @@ class TimelineFragment @Inject constructor( views.composerLayout.views.composerEmojiButton.isVisible = vectorPreferences.showEmojiKeyboard() - if (isThreadTimeLine() && timelineArgs.threadTimelineArgs?.startsThread == true) { + if (isThreadTimeLine() && timelineArgs.threadTimelineArgs?.showKeyboard == true) { // Show keyboard when the user started a thread views.composerLayout.views.composerEditText.showKeyboard(andRequestFocus = true) } @@ -2443,7 +2443,11 @@ class TimelineFragment @Inject constructor( private fun onReplyInThreadClicked(action: EventSharedAction.ReplyInThread) { if (vectorPreferences.areThreadMessagesEnabled()) { - navigateToThreadTimeline(action.eventId, action.startsThread) + navigateToThreadTimeline( + rootThreadEventId = action.eventId, + startsThread = action.startsThread, + showKeyboard = true + ) } else { displayThreadsBetaOptInDialog() } @@ -2453,7 +2457,7 @@ class TimelineFragment @Inject constructor( * Navigate to Threads timeline for the specified rootThreadEventId * using the ThreadsActivity. */ - private fun navigateToThreadTimeline(rootThreadEventId: String, startsThread: Boolean = false) { + private fun navigateToThreadTimeline(rootThreadEventId: String, startsThread: Boolean = false, showKeyboard: Boolean = false) { analyticsTracker.capture(Interaction.Name.MobileRoomThreadSummaryItem.toAnalyticsInteraction()) context?.let { val roomThreadDetailArgs = ThreadTimelineArgs( @@ -2462,7 +2466,8 @@ class TimelineFragment @Inject constructor( displayName = timelineViewModel.getRoomSummary()?.displayName, avatarUrl = timelineViewModel.getRoomSummary()?.avatarUrl, roomEncryptionTrustLevel = timelineViewModel.getRoomSummary()?.roomEncryptionTrustLevel, - rootThreadEventId = rootThreadEventId + rootThreadEventId = rootThreadEventId, + showKeyboard = showKeyboard ) navigator.openThread(it, roomThreadDetailArgs) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt index 1952e598a6..011258f126 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt @@ -121,14 +121,25 @@ class SearchFragment @Inject constructor( override fun onItemClicked(event: Event) = navigateToEvent(event) + override fun onThreadSummaryClicked(event: Event) { + navigateToEvent(event, true) + } + /** * Navigate and highlight the event. If this is a thread event, * user will be redirected to the appropriate thread room * @param event the event to navigate and highlight + * @param forceNavigateToThread force navigate within the thread (ex. when user clicks on thread summary) */ - private fun navigateToEvent(event: Event) { + private fun navigateToEvent(event: Event, forceNavigateToThread: Boolean = false) { val roomId = event.roomId ?: return - event.getRootThreadEventId()?.let { + val rootThreadEventId = if (forceNavigateToThread) { + event.eventId + } else { + event.getRootThreadEventId() + } + + rootThreadEventId?.let { val threadTimelineArgs = ThreadTimelineArgs( roomId = roomId, displayName = fragmentArgs.roomDisplayName, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt index 913e440a20..81e4d8fd5f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt @@ -58,6 +58,7 @@ class SearchResultController @Inject constructor( interface Listener { fun onItemClicked(event: Event) + fun onThreadSummaryClicked(event: Event) fun loadMore() } @@ -134,6 +135,7 @@ class SearchResultController @Inject constructor( .threadSummaryFormatted(displayableEventFormatter.formatThreadSummary(event.threadDetails?.threadSummaryLatestEvent).toString()) .areThreadMessagesEnabled(userPreferencesProvider.areThreadMessagesEnabled()) .listener { listener?.onItemClicked(eventAndSender.event) } + .threadSummaryListener { listener?.onThreadSummaryClicked(eventAndSender.event) } .let { result.add(it) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt index 3e141ab0e9..d92dcdd3ef 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt @@ -46,6 +46,7 @@ abstract class SearchResultItem : VectorEpoxyModel() { @EpoxyAttribute var areThreadMessagesEnabled: Boolean = false @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var threadSummaryListener: ClickListener? = null override fun bind(holder: Holder) { super.bind(holder) @@ -66,23 +67,24 @@ abstract class SearchResultItem : VectorEpoxyModel() { val displayName = it.threadSummarySenderInfo?.displayName val avatarUrl = it.threadSummarySenderInfo?.avatarUrl avatarRenderer.render(MatrixItem.UserItem(userId, displayName, avatarUrl), holder.threadSummaryAvatarImageView) + holder.threadSummaryContainer.onClick(threadSummaryListener) } else { showFromThread(holder) } } ?: run { - holder.threadSummaryConstraintLayout.isVisible = false + holder.threadSummaryContainer.isVisible = false holder.fromThreadConstraintLayout.isVisible = false } } } private fun showThreadSummary(holder: Holder, show: Boolean = true) { - holder.threadSummaryConstraintLayout.isVisible = show + holder.threadSummaryContainer.isVisible = show holder.fromThreadConstraintLayout.isVisible = !show } private fun showFromThread(holder: Holder, show: Boolean = true) { - holder.threadSummaryConstraintLayout.isVisible = !show + holder.threadSummaryContainer.isVisible = !show holder.fromThreadConstraintLayout.isVisible = show } @@ -91,7 +93,7 @@ abstract class SearchResultItem : VectorEpoxyModel() { val memberNameView by bind(R.id.messageMemberNameView) val timeView by bind(R.id.messageTimeView) val contentView by bind(R.id.messageContentView) - val threadSummaryConstraintLayout by bind(R.id.searchThreadSummaryConstraintLayout) + val threadSummaryContainer by bind(R.id.searchThreadSummaryContainer) val threadSummaryCounterTextView by bind(R.id.messageThreadSummaryCounterTextView) val threadSummaryAvatarImageView by bind(R.id.messageThreadSummaryAvatarImageView) val threadSummaryInfoTextView by bind(R.id.messageThreadSummaryInfoTextView) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt deleted file mode 100644 index d233deffb8..0000000000 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2022 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.timeline.factory - -import im.vector.app.core.epoxy.VectorEpoxyModel -import im.vector.app.core.utils.DimensionConverter -import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider -import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider -import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem -import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem -import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem_ -import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent -import javax.inject.Inject - -class LiveLocationMessageItemFactory @Inject constructor( - private val dimensionConverter: DimensionConverter, - private val timelineMediaSizeProvider: TimelineMediaSizeProvider, - private val avatarSizeProvider: AvatarSizeProvider, -) { - - fun create( - beaconInfoContent: MessageBeaconInfoContent, - highlight: Boolean, - attributes: AbsMessageItem.Attributes, - ): VectorEpoxyModel<*>? { - // TODO handle location received and stopped states - return when { - isLiveRunning(beaconInfoContent) -> buildStartLiveItem(highlight, attributes) - else -> null - } - } - - private fun isLiveRunning(beaconInfoContent: MessageBeaconInfoContent): Boolean { - // TODO when we will use aggregatedSummary, check if the live has timed out as well - return beaconInfoContent.isLive.orFalse() - } - - private fun buildStartLiveItem( - highlight: Boolean, - attributes: AbsMessageItem.Attributes, - ): MessageLiveLocationStartItem { - val width = timelineMediaSizeProvider.getMaxSize().first - val height = dimensionConverter.dpToPx(MessageItemFactory.MESSAGE_LOCATION_ITEM_HEIGHT_IN_DP) - - return MessageLiveLocationStartItem_() - .attributes(attributes) - .mapWidth(width) - .mapHeight(height) - .highlighted(highlight) - .leftGuideline(avatarSizeProvider.leftGuideline) - } -} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt new file mode 100644 index 0000000000..479a742369 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2022 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.timeline.factory + +import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.resources.DateProvider +import im.vector.app.core.utils.DimensionConverter +import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider +import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider +import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider +import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem +import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationInactiveItem +import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationInactiveItem_ +import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationItem +import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationItem_ +import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem +import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem_ +import im.vector.app.features.location.INITIAL_MAP_ZOOM_IN_TIMELINE +import im.vector.app.features.location.UrlMapProvider +import im.vector.app.features.location.toLocationData +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.threeten.bp.LocalDateTime +import timber.log.Timber +import javax.inject.Inject + +class LiveLocationShareMessageItemFactory @Inject constructor( + private val session: Session, + private val dimensionConverter: DimensionConverter, + private val timelineMediaSizeProvider: TimelineMediaSizeProvider, + private val avatarSizeProvider: AvatarSizeProvider, + private val urlMapProvider: UrlMapProvider, + private val locationPinProvider: LocationPinProvider, + private val vectorDateFormatter: VectorDateFormatter, +) { + + fun create( + event: TimelineEvent, + highlight: Boolean, + attributes: AbsMessageItem.Attributes, + ): VectorEpoxyModel<*>? { + val liveLocationShareSummaryData = getLiveLocationShareSummaryData(event) + val item = when (val currentState = getViewState(liveLocationShareSummaryData)) { + LiveLocationShareViewState.Inactive -> buildInactiveItem(highlight, attributes) + LiveLocationShareViewState.Loading -> buildLoadingItem(highlight, attributes) + is LiveLocationShareViewState.Running -> buildRunningItem(highlight, attributes, currentState) + LiveLocationShareViewState.Unkwown -> null + } + item?.layout(attributes.informationData.messageLayout.layoutRes) + + return item + } + + private fun buildInactiveItem( + highlight: Boolean, + attributes: AbsMessageItem.Attributes, + ): MessageLiveLocationInactiveItem { + val width = timelineMediaSizeProvider.getMaxSize().first + val height = dimensionConverter.dpToPx(MessageItemFactory.MESSAGE_LOCATION_ITEM_HEIGHT_IN_DP) + + return MessageLiveLocationInactiveItem_() + .attributes(attributes) + .mapWidth(width) + .mapHeight(height) + .highlighted(highlight) + .leftGuideline(avatarSizeProvider.leftGuideline) + } + + private fun buildLoadingItem( + highlight: Boolean, + attributes: AbsMessageItem.Attributes, + ): MessageLiveLocationStartItem { + val width = timelineMediaSizeProvider.getMaxSize().first + val height = dimensionConverter.dpToPx(MessageItemFactory.MESSAGE_LOCATION_ITEM_HEIGHT_IN_DP) + + return MessageLiveLocationStartItem_() + .attributes(attributes) + .mapWidth(width) + .mapHeight(height) + .highlighted(highlight) + .leftGuideline(avatarSizeProvider.leftGuideline) + } + + private fun buildRunningItem( + highlight: Boolean, + attributes: AbsMessageItem.Attributes, + runningState: LiveLocationShareViewState.Running, + ): MessageLiveLocationItem { + // TODO only render location if enabled in preferences: to be handled in a next PR + val width = timelineMediaSizeProvider.getMaxSize().first + val height = dimensionConverter.dpToPx(MessageItemFactory.MESSAGE_LOCATION_ITEM_HEIGHT_IN_DP) + + val locationUrl = runningState.lastGeoUri.toLocationData()?.let { + urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, width, height) + } + + return MessageLiveLocationItem_() + .attributes(attributes) + .locationUrl(locationUrl) + .mapWidth(width) + .mapHeight(height) + .locationUserId(attributes.informationData.senderId) + .locationPinProvider(locationPinProvider) + .highlighted(highlight) + .leftGuideline(avatarSizeProvider.leftGuideline) + .currentUserId(session.myUserId) + .endOfLiveDateTime(runningState.endOfLiveDateTime) + .vectorDateFormatter(vectorDateFormatter) + } + + private fun getViewState(liveLocationShareSummaryData: LiveLocationShareSummaryData?): LiveLocationShareViewState { + return when { + liveLocationShareSummaryData?.isActive == null -> LiveLocationShareViewState.Unkwown + liveLocationShareSummaryData.isActive.not() || isLiveTimedOut(liveLocationShareSummaryData) -> LiveLocationShareViewState.Inactive + liveLocationShareSummaryData.isActive && liveLocationShareSummaryData.lastGeoUri.isNullOrEmpty() -> LiveLocationShareViewState.Loading + else -> + LiveLocationShareViewState.Running( + liveLocationShareSummaryData.lastGeoUri.orEmpty(), + getEndOfLiveDateTime(liveLocationShareSummaryData) + ) + }.also { viewState -> Timber.d("computed viewState: $viewState") } + } + + private fun isLiveTimedOut(liveLocationShareSummaryData: LiveLocationShareSummaryData): Boolean { + return getEndOfLiveDateTime(liveLocationShareSummaryData) + ?.let { endOfLive -> + // this will only cover users with different timezones but not users with manually time set + val now = LocalDateTime.now() + now.isAfter(endOfLive) + } + .orFalse() + } + + private fun getEndOfLiveDateTime(liveLocationShareSummaryData: LiveLocationShareSummaryData): LocalDateTime? { + return liveLocationShareSummaryData.endOfLiveTimestampMillis?.let { DateProvider.toLocalDateTime(timestamp = it) } + } + + private fun getLiveLocationShareSummaryData(event: TimelineEvent): LiveLocationShareSummaryData? { + return event.annotations?.liveLocationShareAggregatedSummary?.let { summary -> + LiveLocationShareSummaryData( + isActive = summary.isActive, + endOfLiveTimestampMillis = summary.endOfLiveTimestampMillis, + lastGeoUri = summary.lastLocationDataContent?.getBestLocationInfo()?.geoUri + ) + } + } + + private data class LiveLocationShareSummaryData( + val isActive: Boolean?, + val endOfLiveTimestampMillis: Long?, + val lastGeoUri: String?, + ) + + private sealed class LiveLocationShareViewState { + object Loading : LiveLocationShareViewState() + data class Running(val lastGeoUri: String, val endOfLiveDateTime: LocalDateTime?) : LiveLocationShareViewState() + object Inactive : LiveLocationShareViewState() + object Unkwown : LiveLocationShareViewState() + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index aca2aab174..224c1cdbea 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -55,8 +55,15 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde private val mergeItemCollapseStates = HashMap() /** + * @param event the main timeline event * @param nextEvent is an older event than event * @param items all known items, sorted from newer event to oldest event + * @param partialState partial state data + * @param addDaySeparator true to add a day separator + * @param currentPosition the current position + * @param eventIdToHighlight if not null the event which has to be highlighted + * @param callback callback for user event + * @param requestModelBuild lambda to let the built Item request a model build when the collapse state is changed */ fun create(event: TimelineEvent, nextEvent: TimelineEvent?, 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 b960e2c6a9..13f783cded 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 @@ -148,7 +148,7 @@ class MessageItemFactory @Inject constructor( private val locationPinProvider: LocationPinProvider, private val vectorPreferences: VectorPreferences, private val urlMapProvider: UrlMapProvider, - private val liveLocationMessageItemFactory: LiveLocationMessageItemFactory, + private val liveLocationShareMessageItemFactory: LiveLocationShareMessageItemFactory, ) { // TODO inject this properly? @@ -216,7 +216,7 @@ class MessageItemFactory @Inject constructor( buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes) } } - is MessageBeaconInfoContent -> liveLocationMessageItemFactory.create(messageContent, highlight, attributes) + is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(params.event, highlight, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } return messageItem?.apply { @@ -237,14 +237,14 @@ class MessageItemFactory @Inject constructor( urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, width, height) } - val userId = if (locationContent.isSelfLocation()) informationData.senderId else null + val locationUserId = if (locationContent.isSelfLocation()) informationData.senderId else null return MessageLocationItem_() .attributes(attributes) .locationUrl(locationUrl) .mapWidth(width) .mapHeight(height) - .userId(userId) + .locationUserId(locationUserId) .locationPinProvider(locationPinProvider) .highlighted(highlight) .leftGuideline(avatarSizeProvider.leftGuideline) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index f4bcc1ba65..07ae9d66c3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -100,7 +100,7 @@ class TimelineItemFactory @Inject constructor( // Message itemsX EventType.STICKER, in EventType.POLL_START, - EventType.MESSAGE -> messageItemFactory.create(params) + EventType.MESSAGE -> messageItemFactory.create(params) EventType.REDACTION, EventType.KEY_VERIFICATION_ACCEPT, EventType.KEY_VERIFICATION_START, @@ -113,14 +113,15 @@ class TimelineItemFactory @Inject constructor( EventType.CALL_NEGOTIATE, EventType.REACTION, in EventType.POLL_RESPONSE, - in EventType.POLL_END -> noticeItemFactory.create(params) + in EventType.POLL_END, + in EventType.BEACON_LOCATION_DATA -> noticeItemFactory.create(params) // Calls EventType.CALL_INVITE, EventType.CALL_HANGUP, EventType.CALL_REJECT, - EventType.CALL_ANSWER -> callItemFactory.create(params) + EventType.CALL_ANSWER -> callItemFactory.create(params) // Crypto - EventType.ENCRYPTED -> { + EventType.ENCRYPTED -> { if (event.root.isRedacted()) { // Redacted event, let the MessageItemFactory handle it messageItemFactory.create(params) @@ -129,11 +130,11 @@ class TimelineItemFactory @Inject constructor( } } EventType.KEY_VERIFICATION_CANCEL, - EventType.KEY_VERIFICATION_DONE -> { + EventType.KEY_VERIFICATION_DONE -> { verificationConclusionItemFactory.create(params) } // Unhandled event types - else -> { + else -> { // Should only happen when shouldShowHiddenEvents() settings is ON Timber.v("Type ${event.root.getClearType()} not handled") defaultItemFactory.create(params) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 7ad0cb27c6..8e06b3ee5d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -107,7 +107,8 @@ class NoticeEventFormatter @Inject constructor( EventType.REDACTION, EventType.STICKER, in EventType.POLL_RESPONSE, - in EventType.POLL_END -> formatDebug(timelineEvent.root) + in EventType.POLL_END, + in EventType.BEACON_LOCATION_DATA -> formatDebug(timelineEvent.root) else -> { Timber.v("Type $type not handled by this formatter") null diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index 1e95f067d2..7874f843e1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -44,8 +44,7 @@ import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited import javax.inject.Inject /** - * This class compute if data of an event (such has avatar, display name, ...) should be displayed, depending on the previous event in the timeline. - * TODO Update this comment + * This class is responsible of building extra information data associated to a given event. */ class MessageInformationDataFactory @Inject constructor(private val session: Session, private val dateFormatter: VectorDateFormatter, @@ -119,7 +118,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses isFirstFromThisSender = isFirstFromThisSender, isLastFromThisSender = isLastFromThisSender, e2eDecoration = e2eDecoration, - sendStateDecoration = sendStateDecoration + sendStateDecoration = sendStateDecoration, ) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt index 45c711ff93..737b0dc85d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt @@ -57,6 +57,7 @@ class MessageItemAttributesFactory @Inject constructor( memberClickListener = { callback?.onMemberNameClicked(informationData) }, + callback = callback, reactionPillCallback = callback, avatarCallback = callback, threadCallback = callback, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt index f317eb4f9a..8ca999309a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt @@ -36,6 +36,8 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen * @param index the index to start computing (inclusive) * @param minSize the minimum number of same type events to have sequentially, otherwise will return an empty list * @param eventIdToHighlight used to compute visibility + * @param rootThreadEventId the root thread event id if in a thread timeline + * @param isFromThreadTimeline true if the timeline is a thread * * @return a list of timeline events which have sequentially the same type following the next direction. */ @@ -86,6 +88,8 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen * @param index the index to start computing (inclusive) * @param minSize the minimum number of same type events to have sequentially, otherwise will return an empty list * @param eventIdToHighlight used to compute visibility + * @param rootThreadEventId the root thread eventId + * @param isFromThreadTimeline true if the timeline is a thread * * @return a list of timeline events which have sequentially the same type following the prev direction. */ @@ -107,6 +111,7 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen /** * @param timelineEvent the event to check for visibility * @param highlightedEventId can be checked to force visibility to true + * @param isFromThreadTimeline true if the timeline is a thread * @param rootThreadEventId if this param is null it means we are in the original timeline * @return true if the event should be shown in the timeline. */ diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt index 263f105b6b..b9d79d5818 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -178,6 +178,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem override val itemLongClickListener: View.OnLongClickListener? = null, override val itemClickListener: ClickListener? = null, val memberClickListener: ClickListener? = null, + val callback: TimelineEventController.Callback? = null, override val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null, val avatarCallback: TimelineEventController.AvatarCallback? = null, val threadCallback: TimelineEventController.ThreadCallback? = null, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt new file mode 100644 index 0000000000..f7146c24e9 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.timeline.item + +import android.graphics.drawable.Drawable +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.IdRes +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams +import com.airbnb.epoxy.EpoxyAttribute +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.Target +import im.vector.app.R +import im.vector.app.core.glide.GlideApp +import im.vector.app.core.utils.DimensionConverter +import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider +import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout +import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners + +abstract class AbsMessageLocationItem : AbsMessageItem() { + + @EpoxyAttribute + var locationUrl: String? = null + + @EpoxyAttribute + var locationUserId: String? = null + + @EpoxyAttribute + var mapWidth: Int = 0 + + @EpoxyAttribute + var mapHeight: Int = 0 + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var locationPinProvider: LocationPinProvider? = null + + override fun bind(holder: H) { + super.bind(holder) + renderSendState(holder.view, null) + bindMap(holder) + } + + private fun bindMap(holder: Holder) { + val location = locationUrl ?: return + val messageLayout = attributes.informationData.messageLayout + val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) { + messageLayout.cornersRadius.granularRoundedCorners() + } else { + val dimensionConverter = DimensionConverter(holder.view.resources) + RoundedCorners(dimensionConverter.dpToPx(8)) + } + holder.staticMapImageView.updateLayoutParams { + width = mapWidth + height = mapHeight + } + GlideApp.with(holder.staticMapImageView) + .load(location) + .apply(RequestOptions.centerCropTransform()) + .listener(object : RequestListener { + override fun onLoadFailed(e: GlideException?, + model: Any?, + target: Target?, + isFirstResource: Boolean): Boolean { + holder.staticMapPinImageView.setImageResource(R.drawable.ic_location_pin_failed) + holder.staticMapErrorTextView.isVisible = true + return false + } + + override fun onResourceReady(resource: Drawable?, + model: Any?, + target: Target?, + dataSource: DataSource?, + isFirstResource: Boolean): Boolean { + locationPinProvider?.create(locationUserId) { pinDrawable -> + // we are not using Glide since it does not display it correctly when there is no user photo + holder.staticMapPinImageView.setImageDrawable(pinDrawable) + } + holder.staticMapErrorTextView.isVisible = false + return false + } + }) + .transform(imageCornerTransformation) + .into(holder.staticMapImageView) + } + + abstract class Holder(@IdRes stubId: Int) : AbsMessageItem.Holder(stubId) { + val staticMapImageView by bind(R.id.staticMapImageView) + val staticMapPinImageView by bind(R.id.staticMapPinImageView) + val staticMapErrorTextView by bind(R.id.staticMapErrorTextView) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultLiveLocationShareStatusItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultLiveLocationShareStatusItem.kt new file mode 100644 index 0000000000..c421efda12 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultLiveLocationShareStatusItem.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2022 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.timeline.item + +import android.content.res.Resources +import android.graphics.drawable.ColorDrawable +import android.widget.ImageView +import androidx.core.view.updateLayoutParams +import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import im.vector.app.R +import im.vector.app.core.glide.GlideApp +import im.vector.app.core.utils.DimensionConverter +import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout +import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners +import im.vector.app.features.themes.ThemeUtils + +/** + * Default implementation of common methods for item representing the status of a live location share. + */ +class DefaultLiveLocationShareStatusItem : LiveLocationShareStatusItem { + + override fun bindMap( + mapImageView: ImageView, + mapWidth: Int, + mapHeight: Int, + messageLayout: TimelineMessageLayout + ) { + val mapCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) { + messageLayout.cornersRadius.granularRoundedCorners() + } else { + RoundedCorners(getDefaultLayoutCornerRadiusInDp(mapImageView.resources)) + } + mapImageView.updateLayoutParams { + width = mapWidth + height = mapHeight + } + GlideApp.with(mapImageView) + .load(R.drawable.bg_no_location_map) + .transform(mapCornerTransformation) + .into(mapImageView) + } + + override fun bindBottomBanner(bannerImageView: ImageView, messageLayout: TimelineMessageLayout) { + val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) { + GranularRoundedCorners( + 0f, + 0f, + messageLayout.cornersRadius.bottomEndRadius, + messageLayout.cornersRadius.bottomStartRadius + ) + } else { + val bottomCornerRadius = getDefaultLayoutCornerRadiusInDp(bannerImageView.resources).toFloat() + GranularRoundedCorners(0f, 0f, bottomCornerRadius, bottomCornerRadius) + } + GlideApp.with(bannerImageView) + .load(ColorDrawable(ThemeUtils.getColor(bannerImageView.context, android.R.attr.colorBackground))) + .transform(imageCornerTransformation) + .into(bannerImageView) + } + + private fun getDefaultLayoutCornerRadiusInDp(resources: Resources): Int { + val dimensionConverter = DimensionConverter(resources) + return dimensionConverter.dpToPx(8) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/LiveLocationShareStatusItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/LiveLocationShareStatusItem.kt new file mode 100644 index 0000000000..2f79f2fc9e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/LiveLocationShareStatusItem.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 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.timeline.item + +import android.widget.ImageView +import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout + +interface LiveLocationShareStatusItem { + fun bindMap( + mapImageView: ImageView, + mapWidth: Int, + mapHeight: Int, + messageLayout: TimelineMessageLayout + ) + + fun bindBottomBanner(bannerImageView: ImageView, messageLayout: TimelineMessageLayout) +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt index f3ca525136..f41c17d9e7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt @@ -135,7 +135,7 @@ abstract class MergedRoomCreationItem : BasedMergedItem(), + LiveLocationShareStatusItem by DefaultLiveLocationShareStatusItem() { + + @EpoxyAttribute + var mapWidth: Int = 0 + + @EpoxyAttribute + var mapHeight: Int = 0 + + override fun bind(holder: Holder) { + super.bind(holder) + renderSendState(holder.view, null) + bindMap(holder.noLocationMapImageView, mapWidth, mapHeight, attributes.informationData.messageLayout) + bindBottomBanner(holder.bannerImageView, attributes.informationData.messageLayout) + } + + override fun getViewStubId() = STUB_ID + + class Holder : AbsMessageItem.Holder(STUB_ID) { + val bannerImageView by bind(R.id.locationLiveInactiveBanner) + val noLocationMapImageView by bind(R.id.locationLiveInactiveMap) + } + + companion object { + private const val STUB_ID = R.id.messageContentLiveLocationInactiveStub + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt new file mode 100644 index 0000000000..838fbd46de --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationItem.kt @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2022 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.timeline.item + +import androidx.core.view.isVisible +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.date.DateFormatKind +import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.core.resources.toTimestamp +import im.vector.app.core.utils.DimensionConverter +import im.vector.app.features.home.room.detail.RoomDetailAction +import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout +import im.vector.app.features.location.live.LocationLiveMessageBannerView +import im.vector.app.features.location.live.LocationLiveMessageBannerViewState +import org.threeten.bp.LocalDateTime + +@EpoxyModelClass(layout = R.layout.item_timeline_event_base) +abstract class MessageLiveLocationItem : AbsMessageLocationItem() { + + @EpoxyAttribute + var currentUserId: String? = null + + @EpoxyAttribute + var endOfLiveDateTime: LocalDateTime? = null + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + lateinit var vectorDateFormatter: VectorDateFormatter + + override fun bind(holder: Holder) { + super.bind(holder) + bindLocationLiveBanner(holder) + } + + private fun bindLocationLiveBanner(holder: Holder) { + // TODO in a future PR add check on device id to confirm that is the one that sent the beacon + val isEmitter = currentUserId != null && currentUserId == locationUserId + val messageLayout = attributes.informationData.messageLayout + val viewState = buildViewState(holder, messageLayout, isEmitter) + holder.locationLiveMessageBanner.isVisible = true + holder.locationLiveMessageBanner.render(viewState) + holder.locationLiveMessageBanner.stopButton.setOnClickListener { + attributes.callback?.onTimelineItemAction(RoomDetailAction.StopLiveLocationSharing) + } + } + + private fun buildViewState( + holder: Holder, + messageLayout: TimelineMessageLayout, + isEmitter: Boolean + ): LocationLiveMessageBannerViewState { + return when { + messageLayout is TimelineMessageLayout.Bubble && isEmitter -> + LocationLiveMessageBannerViewState.Emitter( + remainingTimeInMillis = getRemainingTimeOfLiveInMillis(), + bottomStartCornerRadiusInDp = messageLayout.cornersRadius.bottomStartRadius, + bottomEndCornerRadiusInDp = messageLayout.cornersRadius.bottomEndRadius, + isStopButtonCenteredVertically = false + ) + messageLayout is TimelineMessageLayout.Bubble -> + LocationLiveMessageBannerViewState.Watcher( + bottomStartCornerRadiusInDp = messageLayout.cornersRadius.bottomStartRadius, + bottomEndCornerRadiusInDp = messageLayout.cornersRadius.bottomEndRadius, + formattedLocalTimeOfEndOfLive = getFormattedLocalTimeEndOfLive(), + ) + isEmitter -> { + val cornerRadius = getBannerCornerRadiusForDefaultLayout(holder) + LocationLiveMessageBannerViewState.Emitter( + remainingTimeInMillis = getRemainingTimeOfLiveInMillis(), + bottomStartCornerRadiusInDp = cornerRadius, + bottomEndCornerRadiusInDp = cornerRadius, + isStopButtonCenteredVertically = true + ) + } + else -> { + val cornerRadius = getBannerCornerRadiusForDefaultLayout(holder) + LocationLiveMessageBannerViewState.Watcher( + bottomStartCornerRadiusInDp = cornerRadius, + bottomEndCornerRadiusInDp = cornerRadius, + formattedLocalTimeOfEndOfLive = getFormattedLocalTimeEndOfLive(), + ) + } + } + } + + private fun getBannerCornerRadiusForDefaultLayout(holder: Holder): Float { + val dimensionConverter = DimensionConverter(holder.view.resources) + return dimensionConverter.dpToPx(8).toFloat() + } + + private fun getFormattedLocalTimeEndOfLive() = + endOfLiveDateTime?.toTimestamp()?.let { vectorDateFormatter.format(it, DateFormatKind.MESSAGE_SIMPLE) }.orEmpty() + + private fun getRemainingTimeOfLiveInMillis() = + (endOfLiveDateTime?.toTimestamp() ?: 0) - LocalDateTime.now().toTimestamp() + + override fun getViewStubId() = STUB_ID + + class Holder : AbsMessageLocationItem.Holder(STUB_ID) { + val locationLiveMessageBanner by bind(R.id.locationLiveMessageBanner) + } + + companion object { + private const val STUB_ID = R.id.messageContentLiveLocationStub + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationStartItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationStartItem.kt index 390db0ef50..001774b579 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationStartItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationStartItem.kt @@ -16,22 +16,15 @@ package im.vector.app.features.home.room.detail.timeline.item -import android.graphics.drawable.ColorDrawable import android.widget.ImageView -import androidx.core.view.updateLayoutParams import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass -import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners -import com.bumptech.glide.load.resource.bitmap.RoundedCorners import im.vector.app.R -import im.vector.app.core.glide.GlideApp -import im.vector.app.core.utils.DimensionConverter -import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout -import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners -import im.vector.app.features.themes.ThemeUtils @EpoxyModelClass(layout = R.layout.item_timeline_event_base) -abstract class MessageLiveLocationStartItem : AbsMessageItem() { +abstract class MessageLiveLocationStartItem : + AbsMessageItem(), + LiveLocationShareStatusItem by DefaultLiveLocationShareStatusItem() { @EpoxyAttribute var mapWidth: Int = 0 @@ -42,44 +35,8 @@ abstract class MessageLiveLocationStartItem : AbsMessageItem() { - - @EpoxyAttribute - var locationUrl: String? = null - - @EpoxyAttribute - var userId: String? = null - - @EpoxyAttribute - var mapWidth: Int = 0 - - @EpoxyAttribute - var mapHeight: Int = 0 - - @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) - var locationPinProvider: LocationPinProvider? = null - - override fun bind(holder: Holder) { - super.bind(holder) - renderSendState(holder.view, null) - val location = locationUrl ?: return - val messageLayout = attributes.informationData.messageLayout - val dimensionConverter = DimensionConverter(holder.view.resources) - val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) { - messageLayout.cornersRadius.granularRoundedCorners() - } else { - RoundedCorners(dimensionConverter.dpToPx(8)) - } - holder.staticMapImageView.updateLayoutParams { - width = mapWidth - height = mapHeight - } - GlideApp.with(holder.staticMapImageView) - .load(location) - .apply(RequestOptions.centerCropTransform()) - .listener(object : RequestListener { - override fun onLoadFailed(e: GlideException?, - model: Any?, - target: Target?, - isFirstResource: Boolean): Boolean { - holder.staticMapPinImageView.setImageResource(R.drawable.ic_location_pin_failed) - holder.staticMapErrorTextView.isVisible = true - return false - } - - override fun onResourceReady(resource: Drawable?, - model: Any?, - target: Target?, - dataSource: DataSource?, - isFirstResource: Boolean): Boolean { - locationPinProvider?.create(userId) { pinDrawable -> - GlideApp.with(holder.staticMapPinImageView) - .load(pinDrawable) - .into(holder.staticMapPinImageView) - } - holder.staticMapErrorTextView.isVisible = false - return false - } - }) - .transform(imageCornerTransformation) - .into(holder.staticMapImageView) - } +abstract class MessageLocationItem : AbsMessageLocationItem() { override fun getViewStubId() = STUB_ID - class Holder : AbsMessageItem.Holder(STUB_ID) { - val staticMapImageView by bind(R.id.staticMapImageView) - val staticMapPinImageView by bind(R.id.staticMapPinImageView) - val staticMapErrorTextView by bind(R.id.staticMapErrorTextView) - } + class Holder : AbsMessageLocationItem.Holder(STUB_ID) companion object { private const val STUB_ID = R.id.messageContentLocationStub diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt index cb0b2384ec..a0d10a8a75 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt @@ -66,6 +66,11 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess MessageType.MSGTYPE_VIDEO, MessageType.MSGTYPE_BEACON_INFO, ) + + private val MSG_TYPES_WITH_LOCATION_DATA = setOf( + MessageType.MSGTYPE_LOCATION, + MessageType.MSGTYPE_BEACON_LOCATION_DATA + ) } private val cornerRadius: Float by lazy { @@ -145,9 +150,11 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess } private fun MessageContent?.timestampInsideMessage(): Boolean { - if (this == null) return false - if (msgType == MessageType.MSGTYPE_LOCATION) return vectorPreferences.labsRenderLocationsInTimeline() - return this.msgType in MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE + return when { + this == null -> false + msgType in MSG_TYPES_WITH_LOCATION_DATA -> vectorPreferences.labsRenderLocationsInTimeline() + else -> msgType in MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE + } } private fun MessageContent?.shouldAddMessageOverlay(): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt index 3ae6a1fea5..69455c767e 100755 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt @@ -59,6 +59,8 @@ class PreviewUrlView @JvmOverloads constructor( * This methods is responsible for rendering the view according to the newState. * * @param newState the newState representing the view + * @param imageContentRenderer the tool to render the image + * @param force true to force refresh */ fun render(newState: PreviewUrlUiState, imageContentRenderer: ImageContentRenderer, diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceDirectoryFilterNoResults.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceDirectoryFilterNoResults.kt new file mode 100644 index 0000000000..1ae017c98c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceDirectoryFilterNoResults.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2019 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.list + +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel + +@EpoxyModelClass(layout = R.layout.item_space_directory_filter_no_results) +abstract class SpaceDirectoryFilterNoResults : VectorEpoxyModel() { + class Holder : VectorEpoxyHolder() +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt index 19419e52de..7756c3c5a5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt @@ -27,5 +27,6 @@ data class ThreadTimelineArgs( val avatarUrl: String?, val roomEncryptionTrustLevel: RoomEncryptionTrustLevel?, val rootThreadEventId: String? = null, - val startsThread: Boolean = false + val startsThread: Boolean = false, + val showKeyboard: Boolean = false ) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt index 04889f375f..8e762fda96 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.threads.list.views import android.os.Bundle import android.view.LayoutInflater import android.view.Menu +import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup @@ -70,6 +71,16 @@ class ThreadListFragment @Inject constructor( analyticsScreenName = MobileScreen.ScreenName.ThreadList } + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + + menu.findItem(R.id.menu_thread_list_filter)?.let { menuItem -> + menuItem.actionView.setOnClickListener { + onOptionsItemSelected(menuItem) + } + } + } + override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.menu_thread_list_filter -> { @@ -82,6 +93,9 @@ class ThreadListFragment @Inject constructor( override fun onPrepareOptionsMenu(menu: Menu) { withState(threadListViewModel) { state -> + val filterIcon = menu.findItem(R.id.menu_thread_list_filter).actionView + val filterBadge = filterIcon.findViewById(R.id.threadListFilterBadge) + filterBadge.isVisible = state.shouldFilterThreads when (threadListViewModel.canHomeserverUseThreading()) { true -> menu.findItem(R.id.menu_thread_list_filter).isVisible = !state.threadSummaryList.invoke().isNullOrEmpty() false -> menu.findItem(R.id.menu_thread_list_filter).isVisible = !state.rootThreadEventList.invoke().isNullOrEmpty() @@ -146,9 +160,9 @@ class ThreadListFragment @Inject constructor( override fun onThreadSummaryClicked(threadSummary: ThreadSummary) { val roomThreadDetailArgs = ThreadTimelineArgs( - roomId = threadSummary.roomId, - displayName = threadSummary.rootThreadSenderInfo.displayName, - avatarUrl = threadSummary.rootThreadSenderInfo.avatarUrl, + roomId = threadListArgs.roomId, + displayName = threadListArgs.displayName, + avatarUrl = threadListArgs.avatarUrl, roomEncryptionTrustLevel = null, rootThreadEventId = threadSummary.rootEventId ) @@ -157,9 +171,9 @@ class ThreadListFragment @Inject constructor( override fun onThreadListClicked(timelineEvent: TimelineEvent) { val threadTimelineArgs = ThreadTimelineArgs( - roomId = timelineEvent.roomId, - displayName = timelineEvent.senderInfo.displayName, - avatarUrl = timelineEvent.senderInfo.avatarUrl, + roomId = threadListArgs.roomId, + displayName = threadListArgs.displayName, + avatarUrl = threadListArgs.avatarUrl, roomEncryptionTrustLevel = null, rootThreadEventId = timelineEvent.eventId ) diff --git a/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt b/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt index 386e60359d..e453a347f5 100644 --- a/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt +++ b/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt @@ -136,7 +136,7 @@ class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager * Detect potential malicious activity. * Check if the activity running in app task is declared in app manifest. * - * @param activity the activity of the task + * @param activity the activity of the task * @return true if the activity is potentially malicious */ private fun isPotentialMaliciousActivity(activity: ComponentName): Boolean = activitiesInfo.none { it.name == activity.className } diff --git a/vector/src/main/java/im/vector/app/features/location/Config.kt b/vector/src/main/java/im/vector/app/features/location/Config.kt index 6f947290e2..c29e2e911a 100644 --- a/vector/src/main/java/im/vector/app/features/location/Config.kt +++ b/vector/src/main/java/im/vector/app/features/location/Config.kt @@ -22,5 +22,5 @@ const val DEFAULT_PIN_ID = "DEFAULT_PIN_ID" const val INITIAL_MAP_ZOOM_IN_PREVIEW = 15.0 const val INITIAL_MAP_ZOOM_IN_TIMELINE = 17.0 -const val MIN_TIME_TO_UPDATE_LOCATION_MILLIS = 5 * 1_000L // every 5 seconds +const val MIN_TIME_TO_UPDATE_LOCATION_MILLIS = 2 * 1_000L // every 2 seconds const val MIN_DISTANCE_TO_UPDATE_LOCATION_METERS = 10f diff --git a/vector/src/main/java/im/vector/app/features/location/LocationData.kt b/vector/src/main/java/im/vector/app/features/location/LocationData.kt index 061f338e72..b3466ff871 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationData.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationData.kt @@ -29,7 +29,7 @@ data class LocationData( ) : Parcelable /** - * Creates location data from a LocationContent. + * Creates location data from a MessageLocationContent. * "geo:40.05,29.24;30" -> LocationData(40.05, 29.24, 30) * @return location data or null if geo uri is not valid */ @@ -37,6 +37,15 @@ fun MessageLocationContent.toLocationData(): LocationData? { return parseGeo(getBestGeoUri()) } +/** + * Creates location data from a geoUri String. + * "geo:40.05,29.24;30" -> LocationData(40.05, 29.24, 30) + * @return location data or null if geo uri is null or not valid + */ +fun String?.toLocationData(): LocationData? { + return this?.let { parseGeo(it) } +} + @VisibleForTesting fun parseGeo(geo: String): LocationData? { val geoParts = geo diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt index e472c568b6..cc5586e7f5 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -37,11 +37,11 @@ import im.vector.app.core.utils.PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.databinding.FragmentLocationSharingBinding -import im.vector.app.features.VectorFeatures import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import im.vector.app.features.location.live.duration.ChooseLiveDurationBottomSheet import im.vector.app.features.location.option.LocationSharingOption +import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.util.MatrixItem import java.lang.ref.WeakReference import javax.inject.Inject @@ -53,7 +53,7 @@ class LocationSharingFragment @Inject constructor( private val urlMapProvider: UrlMapProvider, private val avatarRenderer: AvatarRenderer, private val matrixItemColorProvider: MatrixItemColorProvider, - private val vectorFeatures: VectorFeatures, + private val vectorPreferences: VectorPreferences, ) : VectorBaseFragment(), LocationTargetChangeListener, VectorBaseBottomSheetDialogFragment.ResultListener { @@ -255,7 +255,7 @@ class LocationSharingFragment @Inject constructor( // first, update the options view val options: Set = when (state.areTargetAndUserLocationEqual) { true -> { - if (vectorFeatures.isLiveLocationEnabled()) { + if (vectorPreferences.labsEnableLiveLocation()) { setOf(LocationSharingOption.USER_CURRENT, LocationSharingOption.USER_LIVE) } else { setOf(LocationSharingOption.USER_CURRENT) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt index 362b82ccf5..8b9a1c75ae 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt @@ -55,7 +55,10 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { private val binder = LocalBinder() - private var roomArgsList = mutableListOf() + /** + * Keep track of a map between beacon event Id starting the live and RoomArgs. + */ + private var roomArgsMap = mutableMapOf() private var timers = mutableListOf() override fun onCreate() { @@ -73,8 +76,6 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { Timber.i("### LocationSharingService.onStartCommand. sessionId - roomId ${roomArgs?.sessionId} - ${roomArgs?.roomId}") if (roomArgs != null) { - roomArgsList.add(roomArgs) - // Show a sticky notification val notification = notificationUtils.buildLiveLocationSharingNotification() startForeground(roomArgs.roomId.hashCode(), notification) @@ -87,7 +88,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { .getSafeActiveSession() ?.let { session -> session.coroutineScope.launch(session.coroutineDispatchers.io) { - sendLiveBeaconInfo(session, roomArgs) + sendStartingLiveBeaconInfo(session, roomArgs) } } } @@ -95,7 +96,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { return START_STICKY } - private suspend fun sendLiveBeaconInfo(session: Session, roomArgs: RoomArgs) { + private suspend fun sendStartingLiveBeaconInfo(session: Session, roomArgs: RoomArgs) { val beaconContent = MessageBeaconInfoContent( timeout = roomArgs.durationMillis, isLive = true, @@ -103,7 +104,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { ).toContent() val stateKey = session.myUserId - session + val beaconEventId = session .getRoom(roomArgs.roomId) ?.stateService() ?.sendStateEvent( @@ -111,6 +112,16 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { stateKey = stateKey, body = beaconContent ) + + beaconEventId + ?.takeUnless { it.isEmpty() } + ?.let { + roomArgsMap[it] = roomArgs + locationTracker.requestLastKnownLocation() + } + ?: run { + Timber.w("### LocationSharingService.sendStartingLiveBeaconInfo error, no received beacon info id") + } } private fun scheduleTimer(roomId: String, durationMillis: Long) { @@ -134,9 +145,13 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { // Send a new beacon info state by setting live field as false sendStoppedBeaconInfo(roomId) - synchronized(roomArgsList) { - roomArgsList.removeAll { it.roomId == roomId } - if (roomArgsList.isEmpty()) { + synchronized(roomArgsMap) { + val beaconIds = roomArgsMap + .filter { it.value.roomId == roomId } + .map { it.key } + beaconIds.forEach { roomArgsMap.remove(it) } + + if (roomArgsMap.isEmpty()) { Timber.i("### LocationSharingService. Destroying self, time is up for all rooms") destroyMe() } @@ -156,16 +171,17 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { override fun onLocationUpdate(locationData: LocationData) { Timber.i("### LocationSharingService.onLocationUpdate. Uncertainty: ${locationData.uncertainty}") - val session = activeSessionHolder.getSafeActiveSession() // Emit location update to all rooms in which live location sharing is active - session?.coroutineScope?.launch(session.coroutineDispatchers.io) { - roomArgsList.toList().forEach { roomArg -> - sendLiveLocation(roomArg.roomId, locationData) - } + roomArgsMap.toMap().forEach { item -> + sendLiveLocation(item.value.roomId, item.key, locationData) } } - private suspend fun sendLiveLocation(roomId: String, locationData: LocationData) { + private fun sendLiveLocation( + roomId: String, + beaconInfoEventId: String, + locationData: LocationData + ) { val session = activeSessionHolder.getSafeActiveSession() val room = session?.getRoom(roomId) val userId = session?.myUserId @@ -174,18 +190,12 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { return } - room - .stateService() - .getLiveLocationBeaconInfo(userId, true) - ?.eventId - ?.let { - room.sendService().sendLiveLocation( - beaconInfoEventId = it, - latitude = locationData.latitude, - longitude = locationData.longitude, - uncertainty = locationData.uncertainty - ) - } + room.sendService().sendLiveLocation( + beaconInfoEventId = beaconInfoEventId, + latitude = locationData.latitude, + longitude = locationData.longitude, + uncertainty = locationData.uncertainty + ) } override fun onLocationProviderIsNotAvailable() { diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt index b7006370a6..4e56e7954c 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt @@ -40,10 +40,12 @@ class LocationTracker @Inject constructor( fun onLocationProviderIsNotAvailable() } - private var callbacks = mutableListOf() + private val callbacks = mutableListOf() private var hasGpsProviderLiveLocation = false + private var lastLocation: LocationData? = null + @RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION]) fun start() { Timber.d("## LocationTracker. start()") @@ -92,6 +94,14 @@ class LocationTracker @Inject constructor( callbacks.clear() } + /** + * Request the last known location. It will be given async through Callback. + * Please ensure adding a callback to receive the value. + */ + fun requestLastKnownLocation() { + lastLocation?.let { location -> callbacks.forEach { it.onLocationUpdate(location) } } + } + fun addCallback(callback: Callback) { if (!callbacks.contains(callback)) { callbacks.add(callback) @@ -127,7 +137,9 @@ class LocationTracker @Inject constructor( } } } - callbacks.forEach { it.onLocationUpdate(location.toLocationData()) } + val locationData = location.toLocationData() + lastLocation = locationData + callbacks.forEach { it.onLocationUpdate(locationData) } } override fun onProviderDisabled(provider: String) { diff --git a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt new file mode 100644 index 0000000000..8cb552e3c4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerView.kt @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2022 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.location.live + +import android.content.Context +import android.graphics.drawable.ColorDrawable +import android.os.CountDownTimer +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.Button +import android.widget.ImageView +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.core.view.isVisible +import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners +import im.vector.app.R +import im.vector.app.core.glide.GlideApp +import im.vector.app.core.utils.TextUtils +import im.vector.app.databinding.ViewLocationLiveMessageBannerBinding +import im.vector.app.features.themes.ThemeUtils +import org.threeten.bp.Duration + +private const val REMAINING_TIME_COUNTER_INTERVAL_IN_MS = 1000L + +class LocationLiveMessageBannerView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr) { + + private val binding = ViewLocationLiveMessageBannerBinding.inflate( + LayoutInflater.from(context), + this + ) + + val stopButton: Button + get() = binding.locationLiveMessageBannerStop + + private val background: ImageView + get() = binding.locationLiveMessageBannerBackground + + private val title: TextView + get() = binding.locationLiveMessageBannerTitle + + private val subTitle: TextView + get() = binding.locationLiveMessageBannerSubTitle + + private var countDownTimer: CountDownTimer? = null + + fun render(viewState: LocationLiveMessageBannerViewState) { + when (viewState) { + is LocationLiveMessageBannerViewState.Emitter -> renderEmitter(viewState) + is LocationLiveMessageBannerViewState.Watcher -> renderWatcher(viewState) + } + + GlideApp.with(context) + .load(ColorDrawable(ThemeUtils.getColor(context, android.R.attr.colorBackground))) + .transform(GranularRoundedCorners(0f, 0f, viewState.bottomEndCornerRadiusInDp, viewState.bottomStartCornerRadiusInDp)) + .into(background) + } + + private fun renderEmitter(viewState: LocationLiveMessageBannerViewState.Emitter) { + stopButton.isVisible = true + title.text = context.getString(R.string.location_share_live_enabled) + + countDownTimer?.cancel() + viewState.remainingTimeInMillis + .takeIf { it >= 0 } + ?.let { + countDownTimer = object : CountDownTimer(it, REMAINING_TIME_COUNTER_INTERVAL_IN_MS) { + override fun onTick(millisUntilFinished: Long) { + val duration = Duration.ofMillis(millisUntilFinished.coerceAtLeast(0L)) + subTitle.text = context.getString( + R.string.location_share_live_remaining_time, + TextUtils.formatDurationWithUnits(context, duration) + ) + } + + override fun onFinish() { + subTitle.text = context.getString( + R.string.location_share_live_remaining_time, + TextUtils.formatDurationWithUnits(context, Duration.ofMillis(0L)) + ) + } + } + countDownTimer?.start() + } + + val rootLayout: ConstraintLayout? = (binding.root as? ConstraintLayout) + rootLayout?.let { parentLayout -> + val constraintSet = ConstraintSet() + constraintSet.clone(rootLayout) + + if (viewState.isStopButtonCenteredVertically) { + constraintSet.connect( + R.id.locationLiveMessageBannerStop, + ConstraintSet.BOTTOM, + R.id.locationLiveMessageBannerBackground, + ConstraintSet.BOTTOM, + 0 + ) + } else { + constraintSet.clear(R.id.locationLiveMessageBannerStop, ConstraintSet.BOTTOM) + } + + constraintSet.applyTo(parentLayout) + } + } + + private fun renderWatcher(viewState: LocationLiveMessageBannerViewState.Watcher) { + stopButton.isVisible = false + title.text = context.getString(R.string.location_share_live_view) + subTitle.text = context.getString(R.string.location_share_live_until, viewState.formattedLocalTimeOfEndOfLive) + } +} diff --git a/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerViewState.kt b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerViewState.kt new file mode 100644 index 0000000000..976085386b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/live/LocationLiveMessageBannerViewState.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 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.location.live + +sealed class LocationLiveMessageBannerViewState( + open val bottomStartCornerRadiusInDp: Float, + open val bottomEndCornerRadiusInDp: Float, +) { + + data class Emitter( + override val bottomStartCornerRadiusInDp: Float, + override val bottomEndCornerRadiusInDp: Float, + val remainingTimeInMillis: Long, + val isStopButtonCenteredVertically: Boolean + ) : LocationLiveMessageBannerViewState(bottomStartCornerRadiusInDp, bottomEndCornerRadiusInDp) + + data class Watcher( + override val bottomStartCornerRadiusInDp: Float, + override val bottomEndCornerRadiusInDp: Float, + val formattedLocalTimeOfEndOfLive: String, + ) : LocationLiveMessageBannerViewState(bottomStartCornerRadiusInDp, bottomEndCornerRadiusInDp) +} diff --git a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt index c9cb4612a8..1b08d2a86f 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt @@ -36,7 +36,7 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.ensureTrailingSlash import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixPatterns.getDomain +import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig @@ -607,7 +607,7 @@ class LoginViewModel @AssistedInject constructor( identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } ) ?: HomeServerConnectionConfig( - homeServerUri = Uri.parse("https://${action.username.getDomain()}"), + homeServerUri = Uri.parse("https://${action.username.getServerName()}"), homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl), identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } ) diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt index e72e3a1790..6b9d255937 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt @@ -38,7 +38,7 @@ import im.vector.app.features.login.LoginMode import im.vector.app.features.login.ReAuthHelper import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixPatterns.getDomain +import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig @@ -640,7 +640,7 @@ class LoginViewModel2 @AssistedInject constructor( } viewEvent?.let { _viewEvents.post(it) } - val urlFromUser = action.username.getDomain() + val urlFromUser = action.username.getServerName() setState { copy( isLoading = false, 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 7cc42ec57f..0f921ab80a 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 @@ -317,6 +317,7 @@ class DefaultNavigator @Inject constructor( if (context is AppCompatActivity) { if (context !is MatrixToBottomSheet.InteractionListener) { fatalError("Caller context should implement MatrixToBottomSheet.InteractionListener", vectorPreferences.failFast()) + return } // TODO check if there is already one?? MatrixToBottomSheet.withLink(link, origin) diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index d03fcadcfa..abfca1a64c 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -233,6 +233,7 @@ class NotificationUtils @Inject constructor( * Build a polling thread listener notification. * * @param subTitleResId subtitle string resource Id of the notification + * @param withProgress true to show indeterminate progress on the notification * @return the polling thread listener notification */ @SuppressLint("NewApi") @@ -298,10 +299,8 @@ class NotificationUtils @Inject constructor( * Build an incoming call notification. * This notification starts the VectorHomeActivity which is in charge of centralizing the incoming call flow. * - * @param isVideo true if this is a video call, false for voice call - * @param roomName the room name in which the call is pending. - * @param matrixId the matrix id - * @param callId the call id. + * @param call information about the call + * @param title title of the notification * @param fromBg true if the app is in background when posting the notification * @return the call notification. */ @@ -430,11 +429,8 @@ class NotificationUtils @Inject constructor( /** * Build a pending call notification. * - * @param isVideo true if this is a video call, false for voice call - * @param roomName the room name in which the call is pending. - * @param roomId the room Id - * @param matrixId the matrix id - * @param callId the call id. + * @param call information about the call + * @param title title of the notification * @return the call notification. */ @SuppressLint("NewApi") diff --git a/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt b/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt index 171d8f7bb5..3014b199b4 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt @@ -20,7 +20,7 @@ import im.vector.app.R import im.vector.app.core.extensions.andThen import im.vector.app.core.resources.StringProvider import im.vector.app.features.onboarding.OnboardingAction.LoginOrRegister -import org.matrix.android.sdk.api.MatrixPatterns.getDomain +import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.wellknown.WellknownResult @@ -75,7 +75,7 @@ class DirectLoginUseCase @Inject constructor( ) private fun fallbackConfig(action: LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt) = HomeServerConnectionConfig( - homeServerUri = uriFactory.parse("https://${action.username.getDomain()}"), + homeServerUri = uriFactory.parse("https://${action.username.getServerName()}"), homeServerUriBase = uriFactory.parse(wellKnownPrompt.homeServerUrl), identityServerUri = wellKnownPrompt.identityServerUrl?.let { uriFactory.parse(it) } ) diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt index b7ce7ffdb4..e2c1aaa2a4 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt @@ -156,13 +156,16 @@ class BugReporter @Inject constructor( /** * Send a bug report. * - * @param reportType The report type (bug, suggestion, feedback) - * @param withDevicesLogs true to include the device log - * @param withCrashLogs true to include the crash logs + * @param reportType The report type (bug, suggestion, feedback) + * @param withDevicesLogs true to include the device log + * @param withCrashLogs true to include the crash logs * @param withKeyRequestHistory true to include the crash logs - * @param withScreenshot true to include the screenshot + * @param withScreenshot true to include the screenshot * @param theBugDescription the bug description - * @param listener the listener + * @param serverVersion version of the server + * @param canContact true if the user opt in to be contacted directly + * @param customFields fields which will be sent with the report + * @param listener the listener */ @SuppressLint("StaticFieldLeak") fun sendBugReport(reportType: ReportType, @@ -287,7 +290,8 @@ class BugReporter @Inject constructor( .addFormDataPart("app_language", VectorLocale.applicationLocale.toString()) .addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString()) .addFormDataPart("theme", ThemeUtils.getApplicationTheme(context)) - .addFormDataPart("server_version", serverVersion).apply { + .addFormDataPart("server_version", serverVersion) + .apply { customFields?.forEach { (name, value) -> addFormDataPart(name, value) } @@ -678,7 +682,7 @@ class BugReporter @Inject constructor( /** * Retrieves the logs. * - * @param streamWriter the stream writer + * @param streamWriter the stream writer * @param isErrorLogCat true to save the error logs */ private fun getLogCatError(streamWriter: OutputStreamWriter, isErrorLogCat: Boolean) { diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporterMultipartBody.java b/vector/src/main/java/im/vector/app/features/rageshake/BugReporterMultipartBody.java index a530b6e667..72cc63e5c7 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporterMultipartBody.java +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporterMultipartBody.java @@ -39,7 +39,7 @@ public class BugReporterMultipartBody extends RequestBody { /** * Upload listener * - * @param totalWritten total written bytes + * @param totalWritten total written bytes * @param contentLength content length */ void onWrite(long totalWritten, long contentLength); @@ -296,4 +296,4 @@ public class BugReporterMultipartBody extends RequestBody { return new BugReporterMultipartBody(boundary, parts); } } -} \ No newline at end of file +} diff --git a/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt b/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt index bc78b84088..5496ff4a94 100644 --- a/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt @@ -57,7 +57,7 @@ class VectorUncaughtExceptionHandler @Inject constructor( /** * An uncaught exception has been triggered. * - * @param thread the thread + * @param thread the thread * @param throwable the throwable * @return the exception description */ diff --git a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt index 4dd5a68673..91269cb114 100644 --- a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt +++ b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt @@ -16,14 +16,14 @@ package im.vector.app.features.raw.wellknown -import org.matrix.android.sdk.api.MatrixPatterns.getDomain +import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.raw.RawService suspend fun RawService.getElementWellknown(sessionParams: SessionParams): ElementWellKnown? { // By default we use the domain of the userId to retrieve the .well-known data - val domain = sessionParams.userId.getDomain() + val domain = sessionParams.userId.getServerName() return tryOrNull { getWellknown(domain) } ?.let { ElementWellKnownMapper.from(it) } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt index 1994de396f..f1306d9851 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -35,7 +35,7 @@ import im.vector.app.features.raw.wellknown.getElementWellknown import im.vector.app.features.raw.wellknown.isE2EByDefault import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixPatterns.getDomain +import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.raw.RawService @@ -98,7 +98,7 @@ class CreateRoomViewModel @AssistedInject constructor( private fun initHomeServerName() { setState { copy( - homeServerName = session.myUserId.getDomain() + homeServerName = session.myUserId.getServerName() ) } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt index 90283de77c..a168ea749c 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt @@ -20,7 +20,7 @@ import im.vector.app.R import im.vector.app.core.resources.StringArrayProvider import im.vector.app.features.roomdirectory.RoomDirectoryData import im.vector.app.features.roomdirectory.RoomDirectoryServer -import org.matrix.android.sdk.api.MatrixPatterns.getDomain +import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol import javax.inject.Inject @@ -37,7 +37,7 @@ class RoomDirectoryListCreator @Inject constructor( val protocols = ArrayList() // Add user homeserver name - val userHsName = session.myUserId.getDomain() + val userHsName = session.myUserId.getServerName() // Add default protocol protocols.add( diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index 2641eb4184..a66ef9a659 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -31,7 +31,7 @@ import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixPatterns.getDomain +import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType @@ -96,7 +96,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo private fun initHomeServerName() { setState { copy( - homeServerName = session.myUserId.getDomain() + homeServerName = session.myUserId.getServerName() ) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/FontScale.kt b/vector/src/main/java/im/vector/app/features/settings/FontScale.kt index c4ea730afd..a1acef7d35 100644 --- a/vector/src/main/java/im/vector/app/features/settings/FontScale.kt +++ b/vector/src/main/java/im/vector/app/features/settings/FontScale.kt @@ -78,6 +78,7 @@ object FontScale { /** * Store the font scale value. * + * @param context the Android context * @param fontScaleValue the font scale value to store */ private fun saveFontScaleValue(context: Context, fontScaleValue: FontScaleValue) { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt b/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt index 3fb3d3f7c8..326f20845f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt @@ -120,8 +120,8 @@ object VectorLocale { /** * Get String from a locale. * - * @param context the context - * @param locale the locale + * @param context the context + * @param locale the locale * @param resourceId the string resource id * @return the localized string */ diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index c841c6a0af..72f6080417 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -203,6 +203,7 @@ class VectorPreferences @Inject constructor( private const val TAKE_PHOTO_VIDEO_MODE = "TAKE_PHOTO_VIDEO_MODE" private const val SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE = "SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE" + private const val SETTINGS_LABS_ENABLE_LIVE_LOCATION = "SETTINGS_LABS_ENABLE_LIVE_LOCATION" // This key will be used to identify clients with the old thread support enabled io.element.thread const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES_OLD_CLIENTS = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES" @@ -492,7 +493,7 @@ class VectorPreferences @Inject constructor( /** * Update the notification ringtone. * - * @param uri the new notification ringtone, or null for no RingTone + * @param uri the new notification ringtone, or null for no RingTone */ fun setNotificationRingTone(uri: Uri?) { defaultPrefs.edit { @@ -635,7 +636,7 @@ class VectorPreferences @Inject constructor( /** * Tells if the application is started on boot. * - * @param value true to start the application on boot + * @param value true to start the application on boot */ fun setAutoStartOnBoot(value: Boolean) { defaultPrefs.edit { @@ -655,7 +656,7 @@ class VectorPreferences @Inject constructor( /** * Updates the selected saving period. * - * @param index the selected period index + * @param index the selected period index */ fun setSelectedMediasSavingPeriod(index: Int) { defaultPrefs.edit { @@ -1041,6 +1042,10 @@ class VectorPreferences @Inject constructor( return defaultPrefs.getBoolean(SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE, true) } + fun labsEnableLiveLocation(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_LIVE_LOCATION, false) + } + /** * Indicates whether or not thread messages are enabled. */ diff --git a/vector/src/main/java/im/vector/app/features/spaces/LeaveSpaceBottomSheet.kt b/vector/src/main/java/im/vector/app/features/spaces/LeaveSpaceBottomSheet.kt deleted file mode 100644 index a292b64ddd..0000000000 --- a/vector/src/main/java/im/vector/app/features/spaces/LeaveSpaceBottomSheet.kt +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (c) 2021 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.spaces - -import android.app.Activity -import android.graphics.Typeface -import android.os.Bundle -import android.os.Parcelable -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.text.toSpannable -import androidx.core.view.isInvisible -import androidx.core.view.isVisible -import androidx.lifecycle.lifecycleScope -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.args -import com.airbnb.mvrx.parentFragmentViewModel -import com.airbnb.mvrx.withState -import dagger.hilt.android.AndroidEntryPoint -import im.vector.app.R -import im.vector.app.core.error.ErrorFormatter -import im.vector.app.core.extensions.registerStartForActivityResult -import im.vector.app.core.extensions.setTextOrHide -import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment -import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.utils.styleMatchingText -import im.vector.app.databinding.BottomSheetLeaveSpaceBinding -import im.vector.app.features.displayname.getBestName -import im.vector.app.features.spaces.leave.SpaceLeaveAdvancedActivity -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.parcelize.Parcelize -import me.gujun.android.span.span -import org.matrix.android.sdk.api.util.toMatrixItem -import reactivecircus.flowbinding.android.widget.checkedChanges -import javax.inject.Inject - -@AndroidEntryPoint -class LeaveSpaceBottomSheet : VectorBaseBottomSheetDialogFragment() { - - val settingsViewModel: SpaceMenuViewModel by parentFragmentViewModel() - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetLeaveSpaceBinding { - return BottomSheetLeaveSpaceBinding.inflate(inflater, container, false) - } - - @Inject lateinit var colorProvider: ColorProvider - @Inject lateinit var errorFormatter: ErrorFormatter - - @Parcelize - data class Args( - val spaceId: String - ) : Parcelable - - override val showExpanded = true - - private val spaceArgs: SpaceBottomSheetSettingsArgs by args() - - private val cherryPickLeaveActivityResult = registerStartForActivityResult { activityResult -> - if (activityResult.resultCode == Activity.RESULT_OK) { - // nothing actually? - } else { - // move back to default - settingsViewModel.handle(SpaceLeaveViewAction.SetAutoLeaveAll) - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - views.autoLeaveRadioGroup.checkedChanges() - .onEach { - when (it) { - views.leaveAll.id -> { - settingsViewModel.handle(SpaceLeaveViewAction.SetAutoLeaveAll) - } - views.leaveNone.id -> { - settingsViewModel.handle(SpaceLeaveViewAction.SetAutoLeaveNone) - } - views.leaveSelected.id -> { - settingsViewModel.handle(SpaceLeaveViewAction.SetAutoLeaveSelected) - // launch dedicated activity - cherryPickLeaveActivityResult.launch( - SpaceLeaveAdvancedActivity.newIntent(requireContext(), spaceArgs.spaceId) - ) - } - } - } - .launchIn(viewLifecycleOwner.lifecycleScope) - - views.leaveButton.debouncedClicks { - settingsViewModel.handle(SpaceLeaveViewAction.LeaveSpace) - } - - views.cancelButton.debouncedClicks { - dismiss() - } - } - - override fun invalidate() = withState(settingsViewModel) { state -> - super.invalidate() - - val spaceSummary = state.spaceSummary ?: return@withState - val bestName = spaceSummary.toMatrixItem().getBestName() - val commonText = getString(R.string.space_leave_prompt_msg_with_name, bestName) - .toSpannable().styleMatchingText(bestName, Typeface.BOLD) - - val warningMessage: CharSequence = if (spaceSummary.otherMemberIds.isEmpty()) { - span { - +commonText - +"\n\n" - span(getString(R.string.space_leave_prompt_msg_only_you)) { - textColor = colorProvider.getColorFromAttribute(R.attr.colorError) - } - } - } else if (state.isLastAdmin) { - span { - +commonText - +"\n\n" - span(getString(R.string.space_leave_prompt_msg_as_admin)) { - textColor = colorProvider.getColorFromAttribute(R.attr.colorError) - } - } - } else if (!spaceSummary.isPublic) { - span { - +commonText - +"\n\n" - span(getString(R.string.space_leave_prompt_msg_private)) { - textColor = colorProvider.getColorFromAttribute(R.attr.colorError) - } - } - } else { - commonText - } - - views.bottomLeaveSpaceWarningText.setTextOrHide(warningMessage) - - views.inlineErrorText.setTextOrHide(null) - if (state.leavingState is Loading) { - views.leaveButton.isInvisible = true - views.cancelButton.isInvisible = true - views.leaveProgress.isVisible = true - } else { - views.leaveButton.isInvisible = false - views.cancelButton.isInvisible = false - views.leaveProgress.isVisible = false - if (state.leavingState is Fail) { - views.inlineErrorText.setTextOrHide(errorFormatter.toHumanReadable(state.leavingState.error)) - } - } - - val hasChildren = (spaceSummary.spaceChildren?.size ?: 0) > 0 - if (hasChildren) { - views.autoLeaveRadioGroup.isVisible = true - when (state.leaveMode) { - SpaceMenuState.LeaveMode.LEAVE_ALL -> { - views.autoLeaveRadioGroup.check(views.leaveAll.id) - } - SpaceMenuState.LeaveMode.LEAVE_NONE -> { - views.autoLeaveRadioGroup.check(views.leaveNone.id) - } - SpaceMenuState.LeaveMode.LEAVE_SELECTED -> { - views.autoLeaveRadioGroup.check(views.leaveSelected.id) - } - } - } else { - views.autoLeaveRadioGroup.isVisible = false - } - } - - companion object { - - fun newInstance(spaceId: String): LeaveSpaceBottomSheet { - return LeaveSpaceBottomSheet().apply { - setArguments(SpaceBottomSheetSettingsArgs(spaceId)) - } - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceSettingsMenuBottomSheet.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceSettingsMenuBottomSheet.kt index 78eab5b97f..94aa7e19b8 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceSettingsMenuBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceSettingsMenuBottomSheet.kt @@ -35,6 +35,7 @@ import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.navigation.Navigator import im.vector.app.features.rageshake.BugReporter import im.vector.app.features.roomprofile.RoomProfileActivity +import im.vector.app.features.spaces.leave.SpaceLeaveAdvancedActivity import im.vector.app.features.spaces.manage.ManageType import im.vector.app.features.spaces.manage.SpaceManageActivity import kotlinx.parcelize.Parcelize @@ -109,7 +110,7 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment() { interface InteractionListener { + fun onFilterQueryChanged(query: String?) fun onButtonClick(spaceChildInfo: SpaceChildInfo) fun onSpaceChildClick(spaceChildInfo: SpaceChildInfo) fun onRoomClick(spaceChildInfo: SpaceChildInfo) @@ -62,6 +65,7 @@ class SpaceDirectoryController @Inject constructor( } var listener: InteractionListener? = null + private val matchFilter = SpaceChildInfoMatchFilter() override fun buildModels(data: SpaceDirectoryState?) { val host = this @@ -76,7 +80,7 @@ class SpaceDirectoryController @Inject constructor( val failure = results.error if (failure is Failure.ServerError && failure.error.code == M_UNRECOGNIZED) { genericPillItem { - id("HS no Support") + id("hs_no_support") imageRes(R.drawable.error) tintIcon(false) text( @@ -132,43 +136,52 @@ class SpaceDirectoryController @Inject constructor( } } } else { - flattenChildInfo.forEach { info -> - val isSpace = info.roomType == RoomType.SPACE - val isJoined = data?.joinedRoomsIds?.contains(info.childRoomId) == true - val isLoading = data?.changeMembershipStates?.get(info.childRoomId)?.isInProgress() ?: false - val error = (data?.changeMembershipStates?.get(info.childRoomId) as? ChangeMembershipState.FailedJoining)?.throwable - // if it's known use that matrixItem because it would have a better computed name - val matrixItem = data?.knownRoomSummaries?.find { it.roomId == info.childRoomId }?.toMatrixItem() - ?: info.toMatrixItem() + matchFilter.filter = data?.currentFilter ?: "" + val filteredChildInfo = flattenChildInfo.filter { matchFilter.test(it) } - spaceChildInfoItem { - id(info.childRoomId) - matrixItem(matrixItem) - avatarRenderer(host.avatarRenderer) - topic(info.topic) - suggested(info.suggested.orFalse()) - errorLabel( - error?.let { - host.stringProvider.getString(R.string.error_failed_to_join_room, host.errorFormatter.toHumanReadable(it)) + if (filteredChildInfo.isEmpty()) { + spaceDirectoryFilterNoResults { + id("no_results") + } + } else { + filteredChildInfo.forEach { info -> + val isSpace = info.roomType == RoomType.SPACE + val isJoined = data?.joinedRoomsIds?.contains(info.childRoomId) == true + val isLoading = data?.changeMembershipStates?.get(info.childRoomId)?.isInProgress() ?: false + val error = (data?.changeMembershipStates?.get(info.childRoomId) as? ChangeMembershipState.FailedJoining)?.throwable + // if it's known use that matrixItem because it would have a better computed name + val matrixItem = data?.knownRoomSummaries?.find { it.roomId == info.childRoomId }?.toMatrixItem() + ?: info.toMatrixItem() + + spaceChildInfoItem { + id(info.childRoomId) + matrixItem(matrixItem) + avatarRenderer(host.avatarRenderer) + topic(info.topic) + suggested(info.suggested.orFalse()) + errorLabel( + error?.let { + host.stringProvider.getString(R.string.error_failed_to_join_room, host.errorFormatter.toHumanReadable(it)) + } + ) + memberCount(info.activeMemberCount ?: 0) + loading(isLoading) + buttonLabel( + when { + error != null -> host.stringProvider.getString(R.string.global_retry) + isJoined -> host.stringProvider.getString(R.string.action_open) + else -> host.stringProvider.getString(R.string.action_join) + } + ) + apply { + if (isSpace) { + itemClickListener { host.listener?.onSpaceChildClick(info) } + } else { + itemClickListener { host.listener?.onRoomClick(info) } } - ) - memberCount(info.activeMemberCount ?: 0) - loading(isLoading) - buttonLabel( - when { - error != null -> host.stringProvider.getString(R.string.global_retry) - isJoined -> host.stringProvider.getString(R.string.action_open) - else -> host.stringProvider.getString(R.string.action_join) - } - ) - apply { - if (isSpace) { - itemClickListener { host.listener?.onSpaceChildClick(info) } - } else { - itemClickListener { host.listener?.onRoomClick(info) } } + buttonClickListener { host.listener?.onButtonClick(info) } } - buttonClickListener { host.listener?.onButtonClick(info) } } } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt index e59087778f..ed0bbdd911 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt @@ -23,6 +23,7 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.appcompat.widget.SearchView import androidx.core.text.toSpannable import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope @@ -44,7 +45,6 @@ import im.vector.app.core.utils.openUrlInExternalBrowser import im.vector.app.databinding.FragmentSpaceDirectoryBinding import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.room.detail.timeline.TimelineEventController -import im.vector.app.features.matrixto.SpaceCardRenderer import im.vector.app.features.permalink.PermalinkHandler import im.vector.app.features.spaces.manage.ManageType import im.vector.app.features.spaces.manage.SpaceAddRoomSpaceChooserBottomSheet @@ -63,7 +63,6 @@ data class SpaceDirectoryArgs( class SpaceDirectoryFragment @Inject constructor( private val epoxyController: SpaceDirectoryController, private val permalinkHandler: PermalinkHandler, - private val spaceCardRenderer: SpaceCardRenderer, private val colorProvider: ColorProvider ) : VectorBaseFragment(), SpaceDirectoryController.InteractionListener, @@ -123,9 +122,6 @@ class SpaceDirectoryFragment @Inject constructor( } } - views.spaceCard.matrixToCardMainButton.isVisible = false - views.spaceCard.matrixToCardSecondaryButton.isVisible = false - // Hide FAB when list is scrolling views.spaceDirectoryList.addOnScrollListener( object : RecyclerView.OnScrollListener() { @@ -167,18 +163,37 @@ class SpaceDirectoryFragment @Inject constructor( // it's the root toolbar?.setTitle(R.string.space_explore_activity_title) } else { - toolbar?.title = state.currentRootSummary?.name + val spaceName = state.currentRootSummary?.name ?: state.currentRootSummary?.canonicalAlias - ?: getString(R.string.space_explore_activity_title) + + if (spaceName != null) { + toolbar?.title = spaceName + toolbar?.subtitle = getString(R.string.space_explore_activity_title) + } else { + toolbar?.title = getString(R.string.space_explore_activity_title) + } } - spaceCardRenderer.render(state.currentRootSummary, emptyList(), this, views.spaceCard, showDescription = false) views.addOrCreateChatRoomButton.isVisible = state.canAddRooms } override fun onPrepareOptionsMenu(menu: Menu) = withState(viewModel) { state -> menu.findItem(R.id.spaceAddRoom)?.isVisible = state.canAddRooms menu.findItem(R.id.spaceCreateRoom)?.isVisible = false // Not yet implemented + + menu.findItem(R.id.spaceSearch)?.let { searchItem -> + val searchView = searchItem.actionView as SearchView + searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String?): Boolean { + return true + } + + override fun onQueryTextChange(newText: String?): Boolean { + onFilterQueryChanged(newText) + return true + } + }) + } super.onPrepareOptionsMenu(menu) } @@ -198,6 +213,10 @@ class SpaceDirectoryFragment @Inject constructor( return super.onOptionsItemSelected(item) } + override fun onFilterQueryChanged(query: String?) { + viewModel.handle(SpaceDirectoryViewAction.FilterRooms(query)) + } + override fun onButtonClick(spaceChildInfo: SpaceChildInfo) { viewModel.handle(SpaceDirectoryViewAction.JoinOrOpen(spaceChildInfo)) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt index 2166a7e306..1d180eea4f 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt @@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo sealed class SpaceDirectoryViewAction : VectorViewModelAction { data class ExploreSubSpace(val spaceChildInfo: SpaceChildInfo) : SpaceDirectoryViewAction() data class JoinOrOpen(val spaceChildInfo: SpaceChildInfo) : SpaceDirectoryViewAction() + data class FilterRooms(val query: String?) : SpaceDirectoryViewAction() data class ShowDetails(val spaceChildInfo: SpaceChildInfo) : SpaceDirectoryViewAction() data class NavigateToRoom(val roomId: String) : SpaceDirectoryViewAction() object CreateNewRoom : SpaceDirectoryViewAction() diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index 2ddcb42e2a..7ae2feebcf 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -225,9 +225,16 @@ class SpaceDirectoryViewModel @AssistedInject constructor( _viewEvents.post(SpaceDirectoryViewEvents.NavigateToCreateNewRoom(state.currentRootSummary?.roomId ?: initialState.spaceId)) } } + is SpaceDirectoryViewAction.FilterRooms -> { + filter(action.query) + } } } + private fun filter(query: String?) { + setState { copy(currentFilter = query.orEmpty()) } + } + private fun handleBack() = withState { state -> if (state.hierarchyStack.isEmpty()) { _viewEvents.post(SpaceDirectoryViewEvents.Dismiss) diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewAction.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewAction.kt index 68b313ec7f..a25476bff9 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewAction.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewAction.kt @@ -21,6 +21,9 @@ import im.vector.app.core.platform.VectorViewModelAction sealed class SpaceLeaveAdvanceViewAction : VectorViewModelAction { data class ToggleSelection(val roomId: String) : SpaceLeaveAdvanceViewAction() data class UpdateFilter(val filter: String) : SpaceLeaveAdvanceViewAction() + data class SetFilteringEnabled(val isEnabled: Boolean) : SpaceLeaveAdvanceViewAction() object DoLeave : SpaceLeaveAdvanceViewAction() object ClearError : SpaceLeaveAdvanceViewAction() + object SelectAll : SpaceLeaveAdvanceViewAction() + object SelectNone : SpaceLeaveAdvanceViewAction() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewState.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewState.kt index b8dcd3f7a2..fce5f4efa1 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewState.kt @@ -28,8 +28,11 @@ data class SpaceLeaveAdvanceViewState( val allChildren: Async> = Uninitialized, val selectedRooms: List = emptyList(), val currentFilter: String = "", - val leaveState: Async = Uninitialized + val leaveState: Async = Uninitialized, + val isFilteringEnabled: Boolean = false, + val isLastAdmin: Boolean = false ) : MavericksState { + constructor(args: SpaceBottomSheetSettingsArgs) : this( spaceId = args.spaceId ) diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt index 53c7481acb..308572a30f 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt @@ -18,20 +18,23 @@ package im.vector.app.features.spaces.leave import android.os.Bundle import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuItem import android.view.View import android.view.ViewGroup -import androidx.lifecycle.lifecycleScope +import androidx.appcompat.widget.SearchView +import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.view.isVisible +import com.airbnb.mvrx.Success import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState +import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.utils.ToggleableAppBarLayoutBehavior import im.vector.app.databinding.FragmentSpaceLeaveAdvancedBinding -import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.model.RoomSummary -import reactivecircus.flowbinding.appcompat.queryTextChanges import javax.inject.Inject class SpaceLeaveAdvancedFragment @Inject constructor( @@ -44,11 +47,33 @@ class SpaceLeaveAdvancedFragment @Inject constructor( val viewModel: SpaceLeaveAdvancedViewModel by activityViewModel() + override fun getMenuRes() = R.menu.menu_space_leave + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setupToolbar(views.toolbar) - .allowBack() + controller.listener = this + + withState(viewModel) { state -> + setupToolbar(views.toolbar) + .setSubtitle(state.spaceSummary?.name) + .allowBack() + + state.spaceSummary?.let { summary -> + val warningMessage: CharSequence? = when { + summary.otherMemberIds.isEmpty() -> getString(R.string.space_leave_prompt_msg_only_you) + state.isLastAdmin -> getString(R.string.space_leave_prompt_msg_as_admin) + !summary.isPublic -> getString(R.string.space_leave_prompt_msg_private) + else -> null + } + + views.spaceLeavePromptDescription.isVisible = warningMessage != null + views.spaceLeavePromptDescription.text = warningMessage + } + + views.spaceLeavePromptTitle.text = getString(R.string.space_leave_prompt_msg_with_name, state.spaceSummary?.name ?: "") + } + views.roomList.configureWith(controller) views.spaceLeaveCancel.debouncedClicks { requireActivity().finish() } @@ -56,12 +81,23 @@ class SpaceLeaveAdvancedFragment @Inject constructor( viewModel.handle(SpaceLeaveAdvanceViewAction.DoLeave) } - views.publicRoomsFilter.queryTextChanges() - .debounce(100) - .onEach { - viewModel.handle(SpaceLeaveAdvanceViewAction.UpdateFilter(it.toString())) - } - .launchIn(viewLifecycleOwner.lifecycleScope) + views.spaceLeaveSelectGroup.setOnCheckedChangeListener { _, optionId -> + when (optionId) { + R.id.spaceLeaveSelectAll -> viewModel.handle(SpaceLeaveAdvanceViewAction.SelectAll) + R.id.spaceLeaveSelectNone -> viewModel.handle(SpaceLeaveAdvanceViewAction.SelectNone) + } + } + } + + override fun onPrepareOptionsMenu(menu: Menu) { + menu.findItem(R.id.menu_space_leave_search)?.let { searchItem -> + searchItem.bind( + onExpanded = { viewModel.handle(SpaceLeaveAdvanceViewAction.SetFilteringEnabled(isEnabled = true)) }, + onCollapsed = { viewModel.handle(SpaceLeaveAdvanceViewAction.SetFilteringEnabled(isEnabled = false)) }, + onTextChanged = { viewModel.handle(SpaceLeaveAdvanceViewAction.UpdateFilter(it)) } + ) + } + super.onPrepareOptionsMenu(menu) } override fun onDestroyView() { @@ -72,10 +108,63 @@ class SpaceLeaveAdvancedFragment @Inject constructor( override fun invalidate() = withState(viewModel) { state -> super.invalidate() + + if (state.isFilteringEnabled) { + views.appBarLayout.setExpanded(false) + } + + updateAppBarBehaviorState(state) + updateRadioButtonsState(state) + controller.setData(state) } override fun onItemSelected(roomSummary: RoomSummary) { viewModel.handle(SpaceLeaveAdvanceViewAction.ToggleSelection(roomSummary.roomId)) } + + private fun updateAppBarBehaviorState(state: SpaceLeaveAdvanceViewState) { + val behavior = (views.appBarLayout.layoutParams as CoordinatorLayout.LayoutParams).behavior as ToggleableAppBarLayoutBehavior + behavior.isEnabled = !state.isFilteringEnabled + } + + private fun updateRadioButtonsState(state: SpaceLeaveAdvanceViewState) { + (state.allChildren as? Success)?.invoke()?.size?.let { allChildrenCount -> + when (state.selectedRooms.size) { + 0 -> views.spaceLeaveSelectNone.isChecked = true + allChildrenCount -> views.spaceLeaveSelectAll.isChecked = true + else -> views.spaceLeaveSelectSemi.isChecked = true + } + } + } + + private fun MenuItem.bind( + onExpanded: () -> Unit, + onCollapsed: () -> Unit, + onTextChanged: (String) -> Unit) { + setOnActionExpandListener(object : MenuItem.OnActionExpandListener { + override fun onMenuItemActionExpand(item: MenuItem?): Boolean { + onExpanded() + return true + } + + override fun onMenuItemActionCollapse(item: MenuItem?): Boolean { + onCollapsed() + return true + } + }) + + val searchView = actionView as SearchView + + searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String?): Boolean { + return false + } + + override fun onQueryTextChange(newText: String?): Boolean { + onTextChanged(newText ?: "") + return true + } + }) + } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt index 3f5a27f696..2ab417ac55 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt @@ -36,9 +36,14 @@ import okhttp3.internal.toImmutableList import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom -import org.matrix.android.sdk.api.session.getRoomSummary +import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap @@ -50,52 +55,24 @@ class SpaceLeaveAdvancedViewModel @AssistedInject constructor( private val appStateHandler: AppStateHandler ) : VectorViewModel(initialState) { - override fun handle(action: SpaceLeaveAdvanceViewAction) = withState { state -> - when (action) { - is SpaceLeaveAdvanceViewAction.ToggleSelection -> { - val existing = state.selectedRooms.toMutableList() - if (existing.contains(action.roomId)) { - existing.remove(action.roomId) - } else { - existing.add(action.roomId) - } - setState { - copy( - selectedRooms = existing.toImmutableList() - ) - } - } - is SpaceLeaveAdvanceViewAction.UpdateFilter -> { - setState { copy(currentFilter = action.filter) } - } - SpaceLeaveAdvanceViewAction.DoLeave -> { - setState { copy(leaveState = Loading()) } - viewModelScope.launch { - try { - state.selectedRooms.forEach { - try { - session.roomService().leaveRoom(it) - } catch (failure: Throwable) { - // silently ignore? - Timber.e(failure, "Fail to leave sub rooms/spaces") - } - } + init { + val space = session.getRoom(initialState.spaceId) + val spaceSummary = space?.roomSummary() - session.spaceService().leaveSpace(initialState.spaceId) - // We observe the membership and to dismiss when we have remote echo of leaving - } catch (failure: Throwable) { - setState { copy(leaveState = Fail(failure)) } - } - } - } - SpaceLeaveAdvanceViewAction.ClearError -> { - setState { copy(leaveState = Uninitialized) } + val powerLevelsEvent = space?.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS) + powerLevelsEvent?.content?.toModel()?.let { powerLevelsContent -> + val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) + val isAdmin = powerLevelsHelper.getUserRole(session.myUserId) is Role.Admin + val otherAdminCount = spaceSummary?.otherMemberIds + ?.map { powerLevelsHelper.getUserRole(it) } + ?.count { it is Role.Admin } + ?: 0 + val isLastAdmin = isAdmin && otherAdminCount == 0 + setState { + copy(isLastAdmin = isLastAdmin) } } - } - init { - val spaceSummary = session.getRoomSummary(initialState.spaceId) setState { copy(spaceSummary = spaceSummary) } session.getRoom(initialState.spaceId)?.let { room -> room.flow().liveRoomSummary() @@ -127,6 +104,62 @@ class SpaceLeaveAdvancedViewModel @AssistedInject constructor( } } + override fun handle(action: SpaceLeaveAdvanceViewAction) { + when (action) { + is SpaceLeaveAdvanceViewAction.UpdateFilter -> setState { copy(currentFilter = action.filter) } + SpaceLeaveAdvanceViewAction.ClearError -> setState { copy(leaveState = Uninitialized) } + SpaceLeaveAdvanceViewAction.SelectNone -> setState { copy(selectedRooms = emptyList()) } + is SpaceLeaveAdvanceViewAction.SetFilteringEnabled -> setState { copy(isFilteringEnabled = action.isEnabled) } + is SpaceLeaveAdvanceViewAction.ToggleSelection -> handleSelectionToggle(action) + SpaceLeaveAdvanceViewAction.DoLeave -> handleLeave() + SpaceLeaveAdvanceViewAction.SelectAll -> handleSelectAll() + } + } + + private fun handleSelectAll() = withState { state -> + val filteredRooms = (state.allChildren as? Success)?.invoke()?.filter { + it.name.contains(state.currentFilter, true) + } + filteredRooms?.let { + setState { copy(selectedRooms = it.map { it.roomId }) } + } + } + + private fun handleLeave() = withState { state -> + setState { copy(leaveState = Loading()) } + viewModelScope.launch { + try { + state.selectedRooms.forEach { + try { + session.roomService().leaveRoom(it) + } catch (failure: Throwable) { + // silently ignore? + Timber.e(failure, "Fail to leave sub rooms/spaces") + } + } + + session.spaceService().leaveSpace(initialState.spaceId) + // We observe the membership and to dismiss when we have remote echo of leaving + } catch (failure: Throwable) { + setState { copy(leaveState = Fail(failure)) } + } + } + } + + private fun handleSelectionToggle(action: SpaceLeaveAdvanceViewAction.ToggleSelection) = withState { state -> + val existing = state.selectedRooms.toMutableList() + if (existing.contains(action.roomId)) { + existing.remove(action.roomId) + } else { + existing.add(action.roomId) + } + setState { + copy( + selectedRooms = existing.toImmutableList(), + ) + } + } + @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { override fun create(initialState: SpaceLeaveAdvanceViewState): SpaceLeaveAdvancedViewModel diff --git a/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt b/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt index 3b1e8240fa..3d1a224d0c 100644 --- a/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt +++ b/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt @@ -108,6 +108,7 @@ object ThemeUtils { /** * Update the application theme. * + * @param context the Android context * @param aTheme the new theme */ fun setApplicationTheme(context: Context, aTheme: String) { @@ -126,9 +127,11 @@ object ThemeUtils { } /** - * Set the activity theme according to the selected one. + * Set the activity theme according to the selected one. Default is Light, so if this is the current + * theme, the theme is not changed. * * @param activity the activity + * @param otherThemes themes to apply for dark and black theme */ fun setActivityTheme(activity: Activity, otherThemes: ActivityOtherThemes) { when (getApplicationTheme(activity)) { @@ -143,7 +146,7 @@ object ThemeUtils { /** * Translates color attributes to colors. * - * @param c Context + * @param c Context * @param colorAttribute Color Attribute * @return Requested Color */ @@ -175,8 +178,8 @@ object ThemeUtils { /** * Tint the drawable with a theme attribute. * - * @param context the context - * @param drawable the drawable to tint + * @param context the context + * @param drawable the drawable to tint * @param attribute the theme color * @return the tinted drawable */ @@ -188,7 +191,7 @@ object ThemeUtils { * Tint the drawable with a color integer. * * @param drawable the drawable to tint - * @param color the color + * @param color the color * @return the tinted drawable */ fun tintDrawableWithColor(drawable: Drawable, @ColorInt color: Int): Drawable { diff --git a/vector/src/main/java/im/vector/app/features/webview/WebViewEventListener.kt b/vector/src/main/java/im/vector/app/features/webview/WebViewEventListener.kt index bd77283029..2f00ad07b9 100644 --- a/vector/src/main/java/im/vector/app/features/webview/WebViewEventListener.kt +++ b/vector/src/main/java/im/vector/app/features/webview/WebViewEventListener.kt @@ -48,8 +48,8 @@ interface WebViewEventListener { /** * Triggered when an error occurred while loading a page. * - * @param url The url that failed. - * @param errorCode The error code. + * @param url The url that failed. + * @param errorCode The error code. * @param description The error description. */ fun onPageError(url: String, errorCode: Int, description: String) { @@ -59,8 +59,8 @@ interface WebViewEventListener { /** * Triggered when an error occurred while loading a page. * - * @param url The url that failed. - * @param errorCode The error code. + * @param url The url that failed. + * @param errorCode The error code. * @param description The error description. */ fun onHttpError(url: String, errorCode: Int, description: String) { diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt index 3c88ea65a3..fc73e71b51 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt @@ -110,6 +110,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo /** * Retrieve the latest botOptions event. * + * @param widgetPostAPIMediator the post api mediator * @param eventData the modular data */ private fun getBotOptions(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { @@ -171,6 +172,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo /** * Provides the membership state. * + * @param widgetPostAPIMediator the post api mediator * @param eventData the modular data */ private fun getMembershipState(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { @@ -190,6 +192,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo /** * Request the latest joined room event. * + * @param widgetPostAPIMediator the post api mediator * @param eventData the modular data */ private fun getJoinRules(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { @@ -208,6 +211,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo /** * Provide the widgets list. * + * @param widgetPostAPIMediator the post api mediator * @param eventData the modular data */ private fun getWidgets(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { @@ -228,6 +232,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo /** * Set a new widget. * + * @param widgetPostAPIMediator the post api mediator * @param eventData the modular data */ private fun setWidget(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { @@ -303,6 +308,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo /** * Update the 'plumbing state". * + * @param widgetPostAPIMediator the post api mediator * @param eventData the modular data */ private fun setPlumbingState(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { @@ -328,6 +334,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo /** * Update the bot options. * + * @param widgetPostAPIMediator the post api mediator * @param eventData the modular data */ @Suppress("UNCHECKED_CAST") @@ -353,6 +360,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo /** * Update the bot power levels. * + * @param widgetPostAPIMediator the post api mediator * @param eventData the modular data */ private fun setBotPower(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { @@ -375,6 +383,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo /** * Invite an user to this room. * + * @param widgetPostAPIMediator the post api mediator * @param eventData the modular data */ private fun inviteUser(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { @@ -397,6 +406,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo /** * Provides the number of members in the rooms. * + * @param widgetPostAPIMediator the post api mediator * @param eventData the modular data */ private fun getMembershipCount(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { diff --git a/vector/src/main/res/drawable-hdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-hdpi/bg_no_location_map.webp index 23a45700f0..3241b5dc82 100644 Binary files a/vector/src/main/res/drawable-hdpi/bg_no_location_map.webp and b/vector/src/main/res/drawable-hdpi/bg_no_location_map.webp differ diff --git a/vector/src/main/res/drawable-mdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-mdpi/bg_no_location_map.webp index a6130fba78..03f9ba5062 100644 Binary files a/vector/src/main/res/drawable-mdpi/bg_no_location_map.webp and b/vector/src/main/res/drawable-mdpi/bg_no_location_map.webp differ diff --git a/vector/src/main/res/drawable-night-hdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-night-hdpi/bg_no_location_map.webp new file mode 100644 index 0000000000..76e0a75dd6 Binary files /dev/null and b/vector/src/main/res/drawable-night-hdpi/bg_no_location_map.webp differ diff --git a/vector/src/main/res/drawable-night-mdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-night-mdpi/bg_no_location_map.webp new file mode 100644 index 0000000000..79900cec1b Binary files /dev/null and b/vector/src/main/res/drawable-night-mdpi/bg_no_location_map.webp differ diff --git a/vector/src/main/res/drawable-night-xhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-night-xhdpi/bg_no_location_map.webp new file mode 100644 index 0000000000..14f7e0e44c Binary files /dev/null and b/vector/src/main/res/drawable-night-xhdpi/bg_no_location_map.webp differ diff --git a/vector/src/main/res/drawable-night-xxhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-night-xxhdpi/bg_no_location_map.webp new file mode 100644 index 0000000000..91cb7c8eb6 Binary files /dev/null and b/vector/src/main/res/drawable-night-xxhdpi/bg_no_location_map.webp differ diff --git a/vector/src/main/res/drawable-night-xxxhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-night-xxxhdpi/bg_no_location_map.webp new file mode 100644 index 0000000000..e4864a9eb2 Binary files /dev/null and b/vector/src/main/res/drawable-night-xxxhdpi/bg_no_location_map.webp differ diff --git a/vector/src/main/res/drawable-xhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-xhdpi/bg_no_location_map.webp index e908191371..513089b55b 100644 Binary files a/vector/src/main/res/drawable-xhdpi/bg_no_location_map.webp and b/vector/src/main/res/drawable-xhdpi/bg_no_location_map.webp differ diff --git a/vector/src/main/res/drawable-xxhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-xxhdpi/bg_no_location_map.webp index e062178367..50284965a7 100644 Binary files a/vector/src/main/res/drawable-xxhdpi/bg_no_location_map.webp and b/vector/src/main/res/drawable-xxhdpi/bg_no_location_map.webp differ diff --git a/vector/src/main/res/drawable-xxxhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-xxxhdpi/bg_no_location_map.webp index 8b110d33fe..881af0055a 100644 Binary files a/vector/src/main/res/drawable-xxxhdpi/bg_no_location_map.webp and b/vector/src/main/res/drawable-xxxhdpi/bg_no_location_map.webp differ diff --git a/vector/src/main/res/drawable/thread_filter_badge.xml b/vector/src/main/res/drawable/thread_filter_badge.xml new file mode 100644 index 0000000000..c9a01197c8 --- /dev/null +++ b/vector/src/main/res/drawable/thread_filter_badge.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/bottom_sheet_leave_space.xml b/vector/src/main/res/layout/bottom_sheet_leave_space.xml deleted file mode 100644 index 9e5a7c7ebf..0000000000 --- a/vector/src/main/res/layout/bottom_sheet_leave_space.xml +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - -