mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-24 02:15:46 +03:00
Merge branch 'develop' into feature/bca/rust_flavor
This commit is contained in:
commit
14cee226c5
161 changed files with 3588 additions and 811 deletions
|
@ -1,3 +1,11 @@
|
|||
Changes in Element 1.5.11 (2022-12-07)
|
||||
======================================
|
||||
|
||||
Bugfixes 🐛
|
||||
----------
|
||||
- Fix crash when the network is not available. ([#7725](https://github.com/vector-im/element-android/issues/7725))
|
||||
|
||||
|
||||
Changes in Element v1.5.10 (2022-11-30)
|
||||
=======================================
|
||||
|
||||
|
|
|
@ -45,10 +45,10 @@ plugins {
|
|||
// Detekt
|
||||
id "io.gitlab.arturbosch.detekt" version "1.22.0"
|
||||
// Ksp
|
||||
id "com.google.devtools.ksp" version "1.7.21-1.0.8"
|
||||
id "com.google.devtools.ksp" version "1.7.22-1.0.8"
|
||||
|
||||
// Dependency Analysis
|
||||
id 'com.autonomousapps.dependency-analysis' version "1.16.0"
|
||||
id 'com.autonomousapps.dependency-analysis' version "1.17.0"
|
||||
// Gradle doctor
|
||||
id "com.osacky.doctor" version "0.8.1"
|
||||
}
|
||||
|
|
1
changelog.d/7274.bugfix
Normal file
1
changelog.d/7274.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fix bad pills color background. For light and dark theme the color is now 61708B (iso EleWeb)
|
1
changelog.d/7596.feature
Normal file
1
changelog.d/7596.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Save m.local_notification_settings.<device-id> event in account_data
|
1
changelog.d/7632.feature
Normal file
1
changelog.d/7632.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Update notifications setting when m.local_notification_settings.<device-id> event changes for current device
|
1
changelog.d/7653.bugfix
Normal file
1
changelog.d/7653.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
ANR when asking to select the notification method
|
1
changelog.d/7693.feature
Normal file
1
changelog.d/7693.feature
Normal file
|
@ -0,0 +1 @@
|
|||
[Session manager] Add action to signout all the other session
|
1
changelog.d/7695.bugfix
Normal file
1
changelog.d/7695.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
[Rich text editor] Add error tracking for rich text editor
|
1
changelog.d/7697.feature
Normal file
1
changelog.d/7697.feature
Normal file
|
@ -0,0 +1 @@
|
|||
[Session manager] Add actions to rename and signout current session
|
1
changelog.d/7708.misc
Normal file
1
changelog.d/7708.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Add tracing Id for to device messages
|
1
changelog.d/7710.bugfix
Normal file
1
changelog.d/7710.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fix usage of unknown shield in room summary
|
1
changelog.d/7723.misc
Normal file
1
changelog.d/7723.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Disable nightly popup and add an entry point in the advanced settings instead.
|
1
changelog.d/7725.bugfix
Normal file
1
changelog.d/7725.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fix crash when the network is not available.
|
|
@ -8,7 +8,7 @@ ext.versions = [
|
|||
|
||||
def gradle = "7.3.1"
|
||||
// Ref: https://kotlinlang.org/releases.html
|
||||
def kotlin = "1.7.21"
|
||||
def kotlin = "1.7.22"
|
||||
def kotlinCoroutines = "1.6.4"
|
||||
def dagger = "2.44.2"
|
||||
def appDistribution = "16.0.0-beta05"
|
||||
|
@ -98,7 +98,7 @@ ext.libs = [
|
|||
],
|
||||
element : [
|
||||
'opusencoder' : "io.element.android:opusencoder:1.1.0",
|
||||
'wysiwyg' : "io.element.android:wysiwyg:0.7.0.1"
|
||||
'wysiwyg' : "io.element.android:wysiwyg:0.9.0"
|
||||
],
|
||||
squareup : [
|
||||
'moshi' : "com.squareup.moshi:moshi:$moshi",
|
||||
|
|
2
fastlane/metadata/android/cs-CZ/changelogs/40105100.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40105100.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Hlavní změny v této verzi: Nová implementace celoobrazovkového režimu pro editor formátovaného textu a opravy chyb.
|
||||
Úplný seznam změn: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/de-DE/changelogs/40105100.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/40105100.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Die wichtigsten Änderungen in dieser Version: Der Vollbildmodus des Textverarbeitungseditors wurde neu umgesetzt und es wurden diverse Fehler behoben.
|
||||
Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/en-US/changelogs/40105110.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40105110.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Main changes in this version: New implementation of the full screen mode for the Rich Text Editor and bugfixes.
|
||||
Full changelog: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/et/changelogs/40105100.txt
Normal file
2
fastlane/metadata/android/et/changelogs/40105100.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Põhilised muutused selles versioonis: tekstitoimeti täisekraanivaade ja erinevate vigade parandused.
|
||||
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/id/changelogs/40105100.txt
Normal file
2
fastlane/metadata/android/id/changelogs/40105100.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Perubahan utama dalam versi ini: Penerapan baru mode layar penuh untuk Penyunting Teks Kaya dan perbaikan kutu.
|
||||
Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/sk/changelogs/40105100.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40105100.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Hlavné zmeny v tejto verzii: Nová implementácia celo-obrazovkového režimu pre Rozšírený textový editor a opravy chýb.
|
||||
Úplný zoznam zmien: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/sq/changelogs/40105080.txt
Normal file
2
fastlane/metadata/android/sq/changelogs/40105080.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Ndryshimet kryesore në këtë version: ndreqje të metash dhe përmirësime.
|
||||
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/sv-SE/changelogs/40105080.txt
Normal file
2
fastlane/metadata/android/sv-SE/changelogs/40105080.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Huvudsakliga ändringar i den här versionen: buggfixar och förbättringar.
|
||||
Full ändringslogg: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/uk/changelogs/40105100.txt
Normal file
2
fastlane/metadata/android/uk/changelogs/40105100.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Основні зміни в цій версії: Нова реалізація повноекранного режиму для редактора розширеного тексту та виправлення помилок.
|
||||
Перелік усіх змін: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/zh-TW/changelogs/40105100.txt
Normal file
2
fastlane/metadata/android/zh-TW/changelogs/40105100.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
此版本中的主要變動:格式化文字編輯器的全螢幕模式新實作與臭蟲修復。
|
||||
完整的變更紀錄:https://github.com/vector-im/element-android/releases
|
|
@ -2789,7 +2789,7 @@
|
|||
<string name="key_authenticity_not_guaranteed">Pravost této šifrované zprávy nelze v tomto zařízení zaručit.</string>
|
||||
<string name="settings_security_incognito_keyboard_summary">Požadujte, aby klávesnice neaktualizovala žádné personalizované údaje, jako je historie psaní a slovník, na základě toho, co jste napsali v konverzacích. Upozorňujeme, že některé klávesnice nemusí toto nastavení respektovat.</string>
|
||||
<string name="settings_security_incognito_keyboard_title">Inkognito klávesnice</string>
|
||||
<string name="command_description_table_flip">Přidá znaky (╯°□°)╯︵ ┻━┻ před zprávy ve formátu obyčejného textu</string>
|
||||
<string name="command_description_table_flip">Přidá znaky (╯°□°)╯︵ ┻━┻ před zprávy ve formátu prostého textu</string>
|
||||
<string name="attachment_type_voice_broadcast">Hlasové vysílání</string>
|
||||
<string name="command_description_devtools">Otevřít nástroje pro vývojáře</string>
|
||||
<string name="room_settings_global_block_unverified_info_text">🔒 V nastavení zabezpečení jste povolili šifrování pouze do ověřených relací pro všechny místnosti.</string>
|
||||
|
@ -2824,8 +2824,8 @@
|
|||
<string name="permissions_rationale_msg_notification">${app_name} potřebuje oprávnění k zobrazování oznámení. Oznámení mohou zobrazovat vaše zprávy, pozvánky atd.
|
||||
\n
|
||||
\nPro zobrazování oznámení povolte přístup na dalších vyskakovacích oknech.</string>
|
||||
<string name="labs_enable_rich_text_editor_summary">Vyzkoušejte rozšířený textový editor (textový režim již brzy)</string>
|
||||
<string name="labs_enable_rich_text_editor_title">Povolit rozšířený textový editor</string>
|
||||
<string name="labs_enable_rich_text_editor_summary">Vyzkoušejte editor formátovaného textu (režim prostého textu již brzy)</string>
|
||||
<string name="labs_enable_rich_text_editor_title">Povolit editor formátovaného textu</string>
|
||||
<string name="qr_code_login_confirm_security_code_description">Ujistěte se, že znáte původ tohoto kódu. Propojením zařízení poskytnete někomu plný přístup ke svému účtu.</string>
|
||||
<string name="qr_code_login_confirm_security_code">Potvrdit</string>
|
||||
<string name="qr_code_login_try_again">Zkuste to znovu</string>
|
||||
|
@ -2868,7 +2868,7 @@
|
|||
<string name="qr_code_login_header_failed_other_device_already_signed_in_description">Druhé zařízení je již přihlášeno.</string>
|
||||
<string name="qr_code_login_header_failed_e2ee_security_issue_description">Při nastavování zabezpečeného zasílání zpráv se vyskytl problém se zabezpečením. Může být napadena jedna z následujících věcí: váš domovský server; vaše internetové připojení; vaše zařízení;</string>
|
||||
<string name="qr_code_login_header_failed_other_description">Žádost se nezdařila.</string>
|
||||
<string name="a11y_voice_broadcast_buffering">Ukládání do vyrovnávací paměti</string>
|
||||
<string name="a11y_voice_broadcast_buffering">Ukládání do vyrovnávací paměti…</string>
|
||||
<string name="a11y_pause_voice_broadcast">Pozastavit hlasové vysílání</string>
|
||||
<string name="a11y_play_voice_broadcast">Přehrát nebo obnovit hlasové vysílání</string>
|
||||
<string name="a11y_stop_voice_broadcast_record">Ukončit záznam hlasového vysílání</string>
|
||||
|
@ -2922,4 +2922,6 @@
|
|||
<string name="quoting">Citace</string>
|
||||
<string name="replying_to">Odpovídám na %s</string>
|
||||
<string name="editing">Úpravy</string>
|
||||
<string name="settings_enable_direct_share_summary">Zobrazit poslední chaty v nabídce sdílení systému</string>
|
||||
<string name="settings_enable_direct_share_title">Povolit přímé sdílení</string>
|
||||
</resources>
|
|
@ -2815,7 +2815,7 @@
|
|||
<string name="qr_code_login_header_failed_other_description">Die Anfrage ist fehlgeschlagen.</string>
|
||||
<string name="a11y_play_voice_broadcast">Abspielen oder fortsetzen der Sprachübertragung</string>
|
||||
<string name="a11y_resume_voice_broadcast_record">Fortsetzen der Sprachübertragung</string>
|
||||
<string name="a11y_voice_broadcast_buffering">Puffere</string>
|
||||
<string name="a11y_voice_broadcast_buffering">Puffere …</string>
|
||||
<string name="a11y_pause_voice_broadcast">Pausiere Sprachübertragung</string>
|
||||
<string name="a11y_stop_voice_broadcast_record">Stoppe Aufzeichnung der Sprachübertragung</string>
|
||||
<string name="a11y_pause_voice_broadcast_record">Pausiere Aufzeichnung der Sprachübertragung</string>
|
||||
|
@ -2865,4 +2865,6 @@
|
|||
<string name="replying_to">%s antworten</string>
|
||||
<string name="device_manager_other_sessions_hide_ip_address">IP-Adresse ausblenden</string>
|
||||
<string name="device_manager_other_sessions_show_ip_address">IP-Adresse anzeigen</string>
|
||||
<string name="settings_enable_direct_share_summary">Kürzliche Unterhaltungen im Teilen-Menü des Systems anzeigen</string>
|
||||
<string name="settings_enable_direct_share_title">Direktes Teilen aktivieren</string>
|
||||
</resources>
|
|
@ -2805,7 +2805,7 @@
|
|||
<string name="qr_code_login_header_failed_other_device_already_signed_in_description">Teine seade on juba võrku loginud.</string>
|
||||
<string name="qr_code_login_header_failed_e2ee_security_issue_description">Turvalise sõnumivahetuse ülesseadmisel tekkis turvaviga. Üks kolmest võib olla sattunud vale osapoole kontrolli alla: sinu koduserver, sinu internetiühendus või sinu seade;</string>
|
||||
<string name="qr_code_login_header_failed_other_description">Päring ei õnnestunud.</string>
|
||||
<string name="a11y_voice_broadcast_buffering">Andmed on puhverdamisel</string>
|
||||
<string name="a11y_voice_broadcast_buffering">Andmed on puhverdamisel…</string>
|
||||
<string name="a11y_play_voice_broadcast">Alusta või jätka ringhäälingukõne esitamist</string>
|
||||
<string name="a11y_stop_voice_broadcast_record">Lõpeta ringhäälingukõne salvestamine</string>
|
||||
<string name="a11y_pause_voice_broadcast_record">Peata ringhäälingukõne salvestamine</string>
|
||||
|
@ -2857,4 +2857,6 @@
|
|||
<string name="message_reply_to_sender_sent_video">saatis video.</string>
|
||||
<string name="message_reply_to_sender_sent_sticker">saatis kleepsu.</string>
|
||||
<string name="message_reply_to_sender_created_poll">koostas küsitluse.</string>
|
||||
<string name="settings_enable_direct_share_title">Kasuta otsejagamist</string>
|
||||
<string name="settings_enable_direct_share_summary">Näita viimaseid vestlusi süsteemses jagamisvaates</string>
|
||||
</resources>
|
|
@ -943,7 +943,7 @@
|
|||
\n
|
||||
\nپیامهایتان با قفلهایی امن شدهاند و فقط شما و گیرندگان دیگر، کلیدهای یکتا را برای قفلگشاییشان دارید.</string>
|
||||
<string name="room_profile_section_security">امنیت</string>
|
||||
<string name="room_profile_section_security_learn_more">بثیشتر بدانید</string>
|
||||
<string name="room_profile_section_security_learn_more">بیشتر بدانید</string>
|
||||
<string name="room_profile_section_more">بیشتر</string>
|
||||
<string name="room_profile_section_admin">کنشهای مدیر</string>
|
||||
<string name="room_profile_section_more_settings">تنظمیات اتاق</string>
|
||||
|
@ -2783,7 +2783,7 @@
|
|||
<string name="attachment_type_selector_poll">نظرسنجیها</string>
|
||||
<string name="attachment_type_selector_file">پیوستها</string>
|
||||
<string name="attachment_type_selector_sticker">برچسبها</string>
|
||||
<string name="a11y_voice_broadcast_buffering">میانگیری</string>
|
||||
<string name="a11y_voice_broadcast_buffering">میانگیری…</string>
|
||||
<string name="voice_broadcast_live">زنده</string>
|
||||
<string name="qr_code_login_confirm_security_code">تأیید</string>
|
||||
<string name="three">۳</string>
|
||||
|
@ -2844,4 +2844,9 @@
|
|||
<string name="quoting">نقل کردن</string>
|
||||
<string name="replying_to">پاسخ دادن به %s</string>
|
||||
<string name="editing">ویرایش کردن</string>
|
||||
<string name="device_manager_sessions_sign_in_with_qr_code_description">میتوانید با یک رمز QR از این افزاره برای ورود به افزارهای همراه یا روی وب استفاده کنید. دو راه برای این کار وجود دارد:</string>
|
||||
<string name="qr_code_login_header_failed_e2ee_security_issue_description">مشکلی امنیتی در برپایی پیامرسانی امن وجود داشت. ممکن است یکی از موارد زیر دستکاری شده باشند: کارساز خانیگیتان؛ اتّصال اینترنتیتان؛ افزاره(های)تان؛</string>
|
||||
<string name="qr_code_login_confirm_security_code_description">لطفاً مطمئن شوید که مبدأ این کد را میدانید. با پیوند دادن افزارهها، دسترسی کامل را به حسابتان میدهید.</string>
|
||||
<string name="settings_enable_direct_share_summary">نمایش گپهای اخیر در فهرست هم رسانی سامانه</string>
|
||||
<string name="settings_enable_direct_share_title">به کار انداختن همرسانی مستقیم</string>
|
||||
</resources>
|
|
@ -2814,7 +2814,7 @@
|
|||
<string name="device_manager_sessions_sign_in_with_qr_code_description">Vous pouvez utiliser cet appareil pour connecter un appareil mobile ou un client web avec un QR code. Il y a deux façons de le faire :</string>
|
||||
<string name="device_manager_sessions_sign_in_with_qr_code_title">Se connecter avec un QR code</string>
|
||||
<string name="login_scan_qr_code">Scanner le QR code</string>
|
||||
<string name="a11y_voice_broadcast_buffering">Mise en mémoire tampon</string>
|
||||
<string name="a11y_voice_broadcast_buffering">Mise en mémoire tampon…</string>
|
||||
<string name="a11y_pause_voice_broadcast">Mettre en pause la diffusion audio</string>
|
||||
<string name="a11y_play_voice_broadcast">Lire ou continuer la diffusion audio</string>
|
||||
<string name="a11y_stop_voice_broadcast_record">Arrêter l’enregistrement de la diffusion audio</string>
|
||||
|
@ -2866,4 +2866,6 @@
|
|||
<string name="quoting">Citation de</string>
|
||||
<string name="replying_to">Réponse à %s</string>
|
||||
<string name="editing">Modification</string>
|
||||
<string name="settings_enable_direct_share_summary">Affiche les conversations récentes dans le menu de partage du système</string>
|
||||
<string name="settings_enable_direct_share_title">Activer le partage direct</string>
|
||||
</resources>
|
|
@ -2762,7 +2762,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string>
|
|||
<string name="qr_code_login_header_failed_other_description">Permintaan gagal.</string>
|
||||
<string name="labs_enable_voice_broadcast_summary">Memungkinkan untuk merekam dan mengirim siaran suara dalam linimasa ruangan.</string>
|
||||
<string name="labs_enable_voice_broadcast_title">Aktifkan siaran suara (dalam pengembangan aktif)</string>
|
||||
<string name="a11y_voice_broadcast_buffering">Memuat</string>
|
||||
<string name="a11y_voice_broadcast_buffering">Memuat…</string>
|
||||
<string name="a11y_pause_voice_broadcast">Jeda siaran suara</string>
|
||||
<string name="a11y_play_voice_broadcast">Mainkan atau lanjutkan siaran suara</string>
|
||||
<string name="a11y_stop_voice_broadcast_record">Hentikan rekaman siaran suara</string>
|
||||
|
@ -2812,4 +2812,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string>
|
|||
<string name="editing">Mengedit</string>
|
||||
<string name="device_manager_other_sessions_show_ip_address">Tampilkan alamat IP</string>
|
||||
<string name="replying_to">Membalas ke %s</string>
|
||||
<string name="settings_enable_direct_share_summary">Tampilkan obrolan terkini dalam menu pembagian sistem</string>
|
||||
<string name="settings_enable_direct_share_title">Aktifkan pembagian langsung</string>
|
||||
</resources>
|
|
@ -2805,7 +2805,7 @@
|
|||
<string name="qr_code_login_header_failed_other_device_already_signed_in_description">L\'altro dispositivo ha già fatto l\'accesso.</string>
|
||||
<string name="qr_code_login_header_failed_e2ee_security_issue_description">Si è verificato un problema di sicurezza configurando i messaggi sicuri. Una delle seguenti cose potrebbe essere compromessa: il tuo homeserver; la/e connessione/i internet; il/i dispositivo/i;</string>
|
||||
<string name="qr_code_login_header_failed_other_description">La richiesta è fallita.</string>
|
||||
<string name="a11y_voice_broadcast_buffering">Buffering</string>
|
||||
<string name="a11y_voice_broadcast_buffering">Buffer…</string>
|
||||
<string name="a11y_pause_voice_broadcast">Sospendi trasmissione vocale</string>
|
||||
<string name="a11y_play_voice_broadcast">Avvia o riprendi trasmissione vocale</string>
|
||||
<string name="a11y_stop_voice_broadcast_record">Ferma registrazione trasmissione vocale</string>
|
||||
|
@ -2857,4 +2857,6 @@
|
|||
<string name="quoting">Citazione</string>
|
||||
<string name="replying_to">Risposta a %s</string>
|
||||
<string name="editing">Modifica</string>
|
||||
<string name="settings_enable_direct_share_summary">Mostra chat recenti nel menu di condivisione di sistema</string>
|
||||
<string name="settings_enable_direct_share_title">Attiva condivisione diretta</string>
|
||||
</resources>
|
|
@ -2723,7 +2723,7 @@
|
|||
<string name="device_manager_learn_more_sessions_unverified_title">Sessões não-verificadas</string>
|
||||
<string name="device_manager_learn_more_sessions_inactive">Sessões inativas são sessões que você não tem usado em algum tempo, mas elas continuam a receber chaves de encriptação.
|
||||
\n
|
||||
\nRemover sessões inativas melhora segurança e performance, e torna-o mais fácil para você identificar se uma nova sessão é suspeita.</string>
|
||||
\nRemover sessões inativas melhora segurança e performance, e torna mais fácil para você identificar se uma nova sessão é suspeita.</string>
|
||||
<string name="device_manager_learn_more_sessions_inactive_title">Sessões inativas</string>
|
||||
<string name="device_manager_session_rename_warning">Por favor esteja ciente que nomes de sessões também são visíveis a pessoas com quem você se comunica.</string>
|
||||
<string name="device_manager_session_rename_description">Nomes de sessões personalizadas podem ajudar você a reconhecer seus dispositivos mais facilmente.</string>
|
||||
|
@ -2844,9 +2844,9 @@
|
|||
<string name="error_voice_broadcast_unauthorized_title">Não dá pra começar um novo broadcast de voz</string>
|
||||
<string name="a11y_voice_broadcast_fast_forward">Avançar rápido 30 segundos</string>
|
||||
<string name="a11y_voice_broadcast_fast_backward">Retroceder 30 segundos</string>
|
||||
<string name="device_manager_learn_more_sessions_verified_description">Sessões verificadas são onde quer que você está usando esta conta depois de entrar sua frasepasse ou confirmar sua identidade com uma outra sessão verificada.
|
||||
<string name="device_manager_learn_more_sessions_verified_description">Sessões verificadas são onde quer que você esteja usando esta conta depois de entrar sua frasepasse ou confirmar sua identidade com uma outra sessão verificada.
|
||||
\n
|
||||
\nIsto significa que você tem todas as chaves necessitadas para destrancar suas mensagens encriptadas e confirmar a outras(os) usuárias(os) que você confia nesta sessão.</string>
|
||||
\nIsto significa que você tem todas as chaves necessárias para destrancar suas mensagens encriptadas e confirmar a outras(os) usuárias(os) que você confia nesta sessão.</string>
|
||||
<plurals name="device_manager_other_sessions_multi_signout_all">
|
||||
<item quantity="one">Fazer signout de %1$d sessão</item>
|
||||
<item quantity="other">Fazer signout de %1$d sessões</item>
|
||||
|
|
|
@ -2868,7 +2868,7 @@
|
|||
<string name="qr_code_login_header_failed_other_description">Žiadosť zlyhala.</string>
|
||||
<string name="labs_enable_voice_broadcast_summary">Možnosť nahrávania a odosielania hlasového vysielania v časovej osi miestnosti.</string>
|
||||
<string name="labs_enable_voice_broadcast_title">Zapnúť hlasové vysielanie (v štádiu aktívneho vývoja)</string>
|
||||
<string name="a11y_voice_broadcast_buffering">Načítavanie do vyrovnávacej pamäte</string>
|
||||
<string name="a11y_voice_broadcast_buffering">Načítavanie do vyrovnávacej pamäte…</string>
|
||||
<string name="a11y_pause_voice_broadcast">Pozastaviť hlasové vysielanie</string>
|
||||
<string name="a11y_play_voice_broadcast">Prehrať alebo pokračovať v nahrávaní hlasového vysielania</string>
|
||||
<string name="a11y_stop_voice_broadcast_record">Zastaviť nahrávanie hlasového vysielania</string>
|
||||
|
@ -2922,4 +2922,6 @@
|
|||
<string name="device_manager_other_sessions_show_ip_address">Zobraziť IP adresu</string>
|
||||
<string name="replying_to">Odpoveď na %s</string>
|
||||
<string name="editing">Úprava</string>
|
||||
<string name="settings_enable_direct_share_summary">Zobraziť posledné konverzácie v systémovej ponuke zdieľania</string>
|
||||
<string name="settings_enable_direct_share_title">Povoliť priame zdieľanie</string>
|
||||
</resources>
|
|
@ -2659,7 +2659,7 @@
|
|||
\nKy shërbyes Home mund të mos jetë formësuar të shfaqë harta.</string>
|
||||
<string name="poll_undisclosed_not_ended">Përfundimet do të jenë të dukshme pasi të ketë përfunduar pyetësori</string>
|
||||
<string name="labs_enable_msc3061_share_history_desc">Kur bëhet ftesë në një dhomë të fshehtëzuar që ka historik ndarjesh me të tjerët, historiku i fshehtëzuar do të jetë i dukshëm.</string>
|
||||
<string name="a11y_voice_broadcast_buffering">Përdo</string>
|
||||
<string name="a11y_voice_broadcast_buffering"></string>
|
||||
<string name="a11y_pause_voice_broadcast">Ndal transmetim zanor</string>
|
||||
<string name="a11y_play_voice_broadcast">Luani ose vazhdoni luajtje transmetimi zanor</string>
|
||||
<string name="a11y_stop_voice_broadcast_record">Ndal incizim transmetimi zanor</string>
|
||||
|
@ -2851,4 +2851,6 @@
|
|||
<string name="a11y_voice_broadcast_fast_backward">Kthim prapa 30 sekonda</string>
|
||||
<string name="replying_to">Si përgjigje për %s</string>
|
||||
<string name="labs_enable_deferred_dm_title">Aktivizo MD të lënë për më vonë</string>
|
||||
<string name="a11y_collapse_space_children">Tkurr pjella të %s</string>
|
||||
<string name="a11y_expand_space_children">Zgjero pjella të %s</string>
|
||||
</resources>
|
|
@ -2852,4 +2852,18 @@
|
|||
<string name="error_voice_broadcast_unauthorized_title">Kan inte starta en ny röstsändning</string>
|
||||
<string name="a11y_voice_broadcast_fast_forward">Spola framåt 30 sekunder</string>
|
||||
<string name="a11y_voice_broadcast_fast_backward">Spola tillbaka 30 sekunder</string>
|
||||
<string name="message_reply_to_sender_created_poll">skickade en omröstning.</string>
|
||||
<string name="message_reply_to_sender_sent_sticker">skickade en dekal.</string>
|
||||
<string name="message_reply_to_sender_sent_video">skickade en video.</string>
|
||||
<string name="message_reply_to_sender_sent_image">skickade en bild.</string>
|
||||
<string name="message_reply_to_sender_sent_voice_message">skickade ett röstmeddelande.</string>
|
||||
<string name="message_reply_to_sender_sent_audio_file">skickade en ljudfil.</string>
|
||||
<string name="message_reply_to_sender_sent_file">skickade en fil.</string>
|
||||
<string name="message_reply_to_prefix">Svar på</string>
|
||||
<string name="device_manager_other_sessions_hide_ip_address">Dölj IP-adress</string>
|
||||
<string name="device_manager_other_sessions_show_ip_address">Visa IP-adress</string>
|
||||
<string name="voice_broadcast_recording_time_left">%1$s kvar</string>
|
||||
<string name="quoting">Citerar</string>
|
||||
<string name="replying_to">Besvarar %s</string>
|
||||
<string name="editing">Redigerar</string>
|
||||
</resources>
|
|
@ -2922,7 +2922,7 @@
|
|||
<string name="qr_code_login_header_failed_other_description">Запит не виконаний.</string>
|
||||
<string name="labs_enable_voice_broadcast_summary">Можливість записувати та надсилати голосові трансляції до стрічки кімнати.</string>
|
||||
<string name="labs_enable_voice_broadcast_title">Увімкнути голосові трансляції (в активній розробці)</string>
|
||||
<string name="a11y_voice_broadcast_buffering">Буферизація</string>
|
||||
<string name="a11y_voice_broadcast_buffering">Буферизація…</string>
|
||||
<string name="a11y_pause_voice_broadcast">Призупинити голосову трансляцію</string>
|
||||
<string name="a11y_play_voice_broadcast">Відтворити або поновити відтворення голосової трансляції</string>
|
||||
<string name="a11y_stop_voice_broadcast_record">Припинити запис голосової трансляції</string>
|
||||
|
@ -2966,16 +2966,18 @@
|
|||
<string name="device_manager_other_sessions_multi_signout_selection">Вийти</string>
|
||||
<string name="voice_broadcast_recording_time_left">Залишилося %1$s</string>
|
||||
<string name="message_reply_to_sender_sent_audio_file">надсилає аудіофайл.</string>
|
||||
<string name="message_reply_to_sender_sent_file">відправив файл.</string>
|
||||
<string name="message_reply_to_sender_sent_file">надсилає файл.</string>
|
||||
<string name="message_reply_to_prefix">У відповідь на</string>
|
||||
<string name="device_manager_other_sessions_hide_ip_address">Сховати IP-адресу</string>
|
||||
<string name="message_reply_to_sender_created_poll">створив голосування.</string>
|
||||
<string name="message_reply_to_sender_sent_sticker">відправив наліпку.</string>
|
||||
<string name="message_reply_to_sender_sent_video">відправив відео.</string>
|
||||
<string name="message_reply_to_sender_sent_image">відправив зображення.</string>
|
||||
<string name="message_reply_to_sender_sent_voice_message">відправив голосове повідомлення.</string>
|
||||
<string name="message_reply_to_sender_created_poll">створює опитування.</string>
|
||||
<string name="message_reply_to_sender_sent_sticker">надсилає наліпку.</string>
|
||||
<string name="message_reply_to_sender_sent_video">надсилає відео.</string>
|
||||
<string name="message_reply_to_sender_sent_image">надсилає зображення.</string>
|
||||
<string name="message_reply_to_sender_sent_voice_message">надсилає голосове повідомлення.</string>
|
||||
<string name="device_manager_other_sessions_show_ip_address">Показати IP-адресу</string>
|
||||
<string name="quoting">Цитуючи</string>
|
||||
<string name="replying_to">У відповідь на %s</string>
|
||||
<string name="replying_to">У відповідь %s</string>
|
||||
<string name="editing">Редагування</string>
|
||||
<string name="settings_enable_direct_share_summary">Показувати останні бесіди в системному меню загального доступу</string>
|
||||
<string name="settings_enable_direct_share_title">Увімкнути пряме поширення</string>
|
||||
</resources>
|
|
@ -1007,7 +1007,7 @@
|
|||
<string name="settings_discovery_disconnect_with_bound_pid">您当前在身份服务器 %1$s 上共享电子邮件地址或电话号码。您需要重新连接到 %2$s 才能停止共享它们。</string>
|
||||
<string name="settings_agree_to_terms">同意身份服务器 (%s) 服务条款使你可以通过电子邮件地址或电话号码被发现。</string>
|
||||
<string name="labs_allow_extended_logging">启用详细日志。</string>
|
||||
<string name="labs_allow_extended_logging_summary">详细日志将通过在您发送 RageShake 时提供更多日志来帮助开发人员。即使启用,应用程序也不会记录消息内容或任何其他私人数据。</string>
|
||||
<string name="labs_allow_extended_logging_summary">详细日志将通过在您发送愤怒摇动(RageShake)时提供更多日志来帮助开发人员。即使启用,应用程序也不会记录消息内容或任何其他私人数据。</string>
|
||||
<string name="error_terms_not_accepted">接收你的主服务器条款和条件后请重试。</string>
|
||||
<string name="error_network_timeout">服务器似乎响应时间太长,这可能是由于连接不良或服务器错误引起的。请稍后再试。</string>
|
||||
<string name="send_attachment">发送附件</string>
|
||||
|
@ -1205,7 +1205,7 @@
|
|||
<string name="settings_advanced_settings">高级设置</string>
|
||||
<string name="settings_developer_mode">开发者模式</string>
|
||||
<string name="settings_developer_mode_summary">开发者模式激活隐藏的功能,也可能使应用不稳定。仅供开发者使用!</string>
|
||||
<string name="settings_rageshake">摇一摇</string>
|
||||
<string name="settings_rageshake">愤怒摇动(Rageshake)</string>
|
||||
<string name="settings_rageshake_detection_threshold">检测阈值</string>
|
||||
<string name="settings_rageshake_detection_threshold_summary">摇动手机以测试检测阈值</string>
|
||||
<string name="rageshake_detected">检测到摇动!</string>
|
||||
|
@ -1213,7 +1213,7 @@
|
|||
<string name="devices_current_device">当前会话</string>
|
||||
<string name="devices_other_devices">其它会话</string>
|
||||
<string name="autocomplete_limited_results">仅显示第一个结果,请输入更多字符…</string>
|
||||
<string name="settings_developer_mode_fail_fast_title">快速失败</string>
|
||||
<string name="settings_developer_mode_fail_fast_title">快速失败(Fail-fast)</string>
|
||||
<string name="settings_developer_mode_fail_fast_summary">发生意外错误时,${app_name} 可能更经常崩溃</string>
|
||||
<string name="command_description_shrug">在明文消息前添加 ¯\\_(ツ)_/¯</string>
|
||||
<string name="create_room_encryption_title">启用加密</string>
|
||||
|
@ -2694,7 +2694,7 @@
|
|||
<string name="device_manager_verification_status_detail_other_session_unknown">验证您当前的会话以显示此会话的验证状态。</string>
|
||||
<string name="device_manager_verification_status_unknown">未知的验证状态</string>
|
||||
<string name="tooltip_attachment_voice_broadcast">开始语音广播</string>
|
||||
<string name="a11y_voice_broadcast_buffering">缓冲</string>
|
||||
<string name="a11y_voice_broadcast_buffering">正在缓冲……</string>
|
||||
<string name="a11y_pause_voice_broadcast">暂停语音广播</string>
|
||||
<string name="voice_broadcast_live">实时</string>
|
||||
<string name="action_got_it">知道了</string>
|
||||
|
@ -2789,4 +2789,19 @@
|
|||
<plurals name="x_selected">
|
||||
<item quantity="other">已选择 %1$d</item>
|
||||
</plurals>
|
||||
<string name="message_reply_to_sender_created_poll">已创建投票。</string>
|
||||
<string name="message_reply_to_sender_sent_sticker">已发送贴纸。</string>
|
||||
<string name="message_reply_to_sender_sent_video">已发送视频。</string>
|
||||
<string name="message_reply_to_sender_sent_image">已发送图片。</string>
|
||||
<string name="message_reply_to_sender_sent_voice_message">已发送语音消息。</string>
|
||||
<string name="message_reply_to_sender_sent_audio_file">已发送音频文件。</string>
|
||||
<string name="message_reply_to_sender_sent_file">已发送文件。</string>
|
||||
<string name="device_manager_learn_more_sessions_verified_description">已验证的会话是在输入你的口令词组或用另一个已验证的会话确认你的身份之后你使用此账户的任何地方。
|
||||
\n
|
||||
\n这意味着你拥有解锁你的已加密消息和向其他用户证明你信任此会话所需的全部密钥。</string>
|
||||
<plurals name="device_manager_other_sessions_multi_signout_all">
|
||||
<item quantity="other">登出%1$d个会话</item>
|
||||
</plurals>
|
||||
<string name="device_manager_other_sessions_multi_signout_selection">登出</string>
|
||||
<string name="voice_broadcast_recording_time_left">剩余%1$s</string>
|
||||
</resources>
|
|
@ -2760,7 +2760,7 @@
|
|||
<string name="qr_code_login_header_failed_other_description">請求失敗。</string>
|
||||
<string name="labs_enable_voice_broadcast_summary">可以在聊天室時間軸中錄製並傳送語音廣播。</string>
|
||||
<string name="labs_enable_voice_broadcast_title">啟用語音廣播(正在積極開發中)</string>
|
||||
<string name="a11y_voice_broadcast_buffering">正在緩衝</string>
|
||||
<string name="a11y_voice_broadcast_buffering">正在緩衝……</string>
|
||||
<string name="a11y_pause_voice_broadcast">暫停語音廣播</string>
|
||||
<string name="a11y_play_voice_broadcast">播放或繼續語音廣播</string>
|
||||
<string name="a11y_stop_voice_broadcast_record">停止語音廣播錄製</string>
|
||||
|
@ -2810,4 +2810,6 @@
|
|||
<string name="quoting">引用</string>
|
||||
<string name="replying_to">回覆給 %s</string>
|
||||
<string name="editing">正在編輯</string>
|
||||
<string name="settings_enable_direct_share_summary">在系統分享選單中顯示最近聊天</string>
|
||||
<string name="settings_enable_direct_share_title">啟用直接分享</string>
|
||||
</resources>
|
|
@ -2491,6 +2491,9 @@
|
|||
<string name="settings_key_requests">Key Requests</string>
|
||||
<string name="settings_export_trail">Export Audit</string>
|
||||
|
||||
<string name="settings_nightly_build">Nightly build</string>
|
||||
<string name="settings_nightly_build_update">Get the latest build (note: you may have trouble to sign in)</string>
|
||||
|
||||
<string name="e2e_use_keybackup">Unlock encrypted messages history</string>
|
||||
|
||||
<string name="refresh">Refresh</string>
|
||||
|
@ -3370,6 +3373,7 @@
|
|||
<item quantity="one">Sign out of %1$d session</item>
|
||||
<item quantity="other">Sign out of %1$d sessions</item>
|
||||
</plurals>
|
||||
<string name="device_manager_signout_all_other_sessions">Sign out of all other sessions</string>
|
||||
<string name="device_manager_other_sessions_show_ip_address">Show IP address</string>
|
||||
<string name="device_manager_other_sessions_hide_ip_address">Hide IP address</string>
|
||||
<string name="device_manager_session_overview_signout">Sign out of this session</string>
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
<item name="vctr_list_separator">?vctr_content_quinary</item>
|
||||
<item name="vctr_list_separator_system">?vctr_system</item>
|
||||
<item name="vctr_list_separator_on_surface">?vctr_system</item>
|
||||
<item name="vctr_unread_background">?vctr_content_tertiary</item>
|
||||
<item name="vctr_unread_background">?vctr_notice_secondary</item>
|
||||
|
||||
<!-- Material color -->
|
||||
<item name="colorPrimary">@color/element_accent_dark</item>
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
<item name="vctr_list_separator">?vctr_content_quinary</item>
|
||||
<item name="vctr_list_separator_system">?vctr_system</item>
|
||||
<item name="vctr_list_separator_on_surface">?vctr_system</item>
|
||||
<item name="vctr_unread_background">?vctr_content_tertiary</item>
|
||||
<item name="vctr_unread_background">?vctr_notice_secondary</item>
|
||||
|
||||
<!-- Material color -->
|
||||
<item name="colorPrimary">@color/element_accent_light</item>
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent
|
|||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.toDeviceTracingId
|
||||
import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService
|
||||
import org.matrix.android.sdk.internal.session.sync.ProgressReporter
|
||||
import timber.log.Timber
|
||||
|
@ -46,12 +47,14 @@ internal class CryptoSyncHandler @Inject constructor(
|
|||
.forEachIndexed { index, event ->
|
||||
progressReporter?.reportProgress(index * 100F / total)
|
||||
// Decrypt event if necessary
|
||||
Timber.tag(loggerTag.value).i("To device event from ${event.senderId} of type:${event.type}")
|
||||
Timber.tag(loggerTag.value).d("To device event msgid:${event.toDeviceTracingId()}")
|
||||
decryptToDeviceEvent(event, null)
|
||||
|
||||
if (event.getClearType() == EventType.MESSAGE &&
|
||||
event.getClearContent()?.toModel<MessageContent>()?.msgType == "m.bad.encrypted") {
|
||||
Timber.tag(loggerTag.value).e("handleToDeviceEvent() : Warning: Unable to decrypt to-device event : ${event.content}")
|
||||
} else {
|
||||
Timber.tag(loggerTag.value).d("received to-device ${event.getClearType()} from:${event.senderId} msgid:${event.toDeviceTracingId()}")
|
||||
verificationService.onToDeviceEvent(event)
|
||||
cryptoService.get().onToDeviceEvent(event)
|
||||
}
|
||||
|
|
|
@ -21,5 +21,6 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class LocalNotificationSettingsContent(
|
||||
@Json(name = "is_silenced") val isSilenced: Boolean = false
|
||||
@Json(name = "is_silenced")
|
||||
val isSilenced: Boolean?
|
||||
)
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.matrix.android.sdk.api.session.events.model
|
||||
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType.MSGTYPE_VERIFICATION_REQUEST
|
||||
|
||||
/**
|
||||
* Constants defining known event types from Matrix specifications.
|
||||
*/
|
||||
|
@ -127,6 +129,7 @@ object EventType {
|
|||
|
||||
fun isVerificationEvent(type: String): Boolean {
|
||||
return when (type) {
|
||||
MSGTYPE_VERIFICATION_REQUEST,
|
||||
KEY_VERIFICATION_START,
|
||||
KEY_VERIFICATION_ACCEPT,
|
||||
KEY_VERIFICATION_KEY,
|
||||
|
|
|
@ -17,15 +17,23 @@
|
|||
package org.matrix.android.sdk.internal.crypto.tasks
|
||||
|
||||
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
||||
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.toContent
|
||||
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.SendToDeviceBody
|
||||
import org.matrix.android.sdk.internal.network.DEFAULT_REQUEST_RETRY_COUNT
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
const val TO_DEVICE_TRACING_ID_KEY = "org.matrix.msgid"
|
||||
|
||||
fun Event.toDeviceTracingId(): String? = content?.get(TO_DEVICE_TRACING_ID_KEY) as? String
|
||||
|
||||
internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> {
|
||||
data class Params(
|
||||
// the type of event to send
|
||||
|
@ -35,7 +43,9 @@ internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> {
|
|||
// the transactionId. If not provided, a transactionId will be created by the task
|
||||
val transactionId: String? = null,
|
||||
// Number of retry before failing
|
||||
val retryCount: Int = DEFAULT_REQUEST_RETRY_COUNT
|
||||
val retryCount: Int = DEFAULT_REQUEST_RETRY_COUNT,
|
||||
// add tracing id, notice that to device events that do signature on content might be broken by it
|
||||
val addTracingIds: Boolean = !EventType.isVerificationEvent(eventType),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -45,15 +55,22 @@ internal class DefaultSendToDeviceTask @Inject constructor(
|
|||
) : SendToDeviceTask {
|
||||
|
||||
override suspend fun execute(params: SendToDeviceTask.Params) {
|
||||
val sendToDeviceBody = SendToDeviceBody(
|
||||
messages = params.contentMap.map
|
||||
)
|
||||
|
||||
// If params.transactionId is not provided, we create a unique txnId.
|
||||
// It's important to do that outside the requestBlock parameter of executeRequest()
|
||||
// to use the same value if the request is retried
|
||||
val txnId = params.transactionId ?: createUniqueTxnId()
|
||||
|
||||
// add id tracing to debug
|
||||
val decorated = if (params.addTracingIds) {
|
||||
decorateWithToDeviceTracingIds(params)
|
||||
} else {
|
||||
params.contentMap.map to emptyList()
|
||||
}
|
||||
|
||||
val sendToDeviceBody = SendToDeviceBody(
|
||||
messages = decorated.first
|
||||
)
|
||||
|
||||
return executeRequest(
|
||||
globalErrorReceiver,
|
||||
canRetry = true,
|
||||
|
@ -64,8 +81,35 @@ internal class DefaultSendToDeviceTask @Inject constructor(
|
|||
transactionId = txnId,
|
||||
body = sendToDeviceBody
|
||||
)
|
||||
Timber.i("Sent to device type=${params.eventType} txnid=$txnId [${decorated.second.joinToString(",")}]")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To make it easier to track down where to-device messages are getting lost,
|
||||
* add a custom property to each one, and that will be logged after sent and on reception. Synapse will also log
|
||||
* this property.
|
||||
* @return A pair, first is the decorated content, and second info to log out after sending
|
||||
*/
|
||||
private fun decorateWithToDeviceTracingIds(params: SendToDeviceTask.Params): Pair<Map<String, Map<String, Any>>, List<String>> {
|
||||
val tracingInfo = mutableListOf<String>()
|
||||
val decoratedContent = params.contentMap.map.map { userToDeviceMap ->
|
||||
val userId = userToDeviceMap.key
|
||||
userId to userToDeviceMap.value.map {
|
||||
val deviceId = it.key
|
||||
deviceId to it.value.toContent().toMutableMap().apply {
|
||||
put(
|
||||
TO_DEVICE_TRACING_ID_KEY,
|
||||
UUID.randomUUID().toString().also {
|
||||
tracingInfo.add("$userId/$deviceId (msgid $it)")
|
||||
}
|
||||
)
|
||||
}
|
||||
}.toMap()
|
||||
}.toMap()
|
||||
|
||||
return decoratedContent to tracingInfo
|
||||
}
|
||||
}
|
||||
|
||||
internal fun createUniqueTxnId() = UUID.randomUUID().toString()
|
||||
|
|
|
@ -42,7 +42,7 @@ internal class DefaultGetCurrentFilterTask @Inject constructor(
|
|||
|
||||
return when (storedFilterBody) {
|
||||
currentFilterBody -> storedFilterId ?: storedFilterBody
|
||||
else -> saveFilter(currentFilter)
|
||||
else -> saveFilter(currentFilter) ?: currentFilterBody
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.session.filter
|
||||
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
|
@ -24,8 +25,9 @@ import javax.inject.Inject
|
|||
|
||||
/**
|
||||
* Save a filter, in db and if any changes, upload to the server.
|
||||
* Return the filterId if uploading to the server is successful, else return null.
|
||||
*/
|
||||
internal interface SaveFilterTask : Task<SaveFilterTask.Params, String> {
|
||||
internal interface SaveFilterTask : Task<SaveFilterTask.Params, String?> {
|
||||
|
||||
data class Params(
|
||||
val filter: Filter
|
||||
|
@ -39,18 +41,20 @@ internal class DefaultSaveFilterTask @Inject constructor(
|
|||
private val globalErrorReceiver: GlobalErrorReceiver,
|
||||
) : SaveFilterTask {
|
||||
|
||||
override suspend fun execute(params: SaveFilterTask.Params): String {
|
||||
override suspend fun execute(params: SaveFilterTask.Params): String? {
|
||||
val filter = params.filter
|
||||
val filterResponse = executeRequest(globalErrorReceiver) {
|
||||
// TODO auto retry
|
||||
val filterResponse = tryOrNull {
|
||||
executeRequest(globalErrorReceiver) {
|
||||
filterAPI.uploadFilter(userId, filter)
|
||||
}
|
||||
}
|
||||
|
||||
val filterId = filterResponse?.filterId
|
||||
filterRepository.storeSyncFilter(
|
||||
filter = filter,
|
||||
filterId = filterResponse.filterId,
|
||||
filterId = filterId.orEmpty(),
|
||||
roomEventFilter = FilterFactory.createDefaultRoomFilter()
|
||||
)
|
||||
return filterResponse.filterId
|
||||
return filterId
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,255 @@
|
|||
/*
|
||||
* 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.crypto
|
||||
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.amshove.kluent.internal.assertEquals
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.model.DevicesListResponse
|
||||
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDevicesParams
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeyChangesResponse
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimBody
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimResponse
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryBody
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadBody
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.SendToDeviceBody
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.UpdateDeviceInfoBody
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.UploadSigningKeysBody
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendToDeviceTask
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||
|
||||
class DefaultSendToDeviceTaskTest {
|
||||
|
||||
private val users = listOf(
|
||||
"@alice:example.com" to listOf("D0", "D1"),
|
||||
"bob@example.com" to listOf("D2", "D3")
|
||||
)
|
||||
|
||||
private val fakeEncryptedContent = mapOf(
|
||||
"algorithm" to "m.olm.v1.curve25519-aes-sha2",
|
||||
"sender_key" to "gMObR+/4dqL5T4DisRRRYBJpn+OjzFnkyCFOktP6Eyw",
|
||||
"ciphertext" to mapOf(
|
||||
"tdwXf7006FDgzmufMCVI4rDdVPO51ecRTTT6HkRxUwE" to mapOf(
|
||||
"type" to 0,
|
||||
"body" to "AwogCA1ULEc0abGIFxMDIC9iv7ul3jqJSnapTHQ+8JJx"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
private val fakeStartVerificationContent = mapOf(
|
||||
"method" to "m.sas.v1",
|
||||
"from_device" to "MNQHVEISFQ",
|
||||
"key_agreement_protocols" to listOf(
|
||||
"curve25519-hkdf-sha256",
|
||||
"curve25519"
|
||||
),
|
||||
"hashes" to listOf("sha256"),
|
||||
"message_authentication_codes" to listOf(
|
||||
"org.matrix.msc3783.hkdf-hmac-sha256",
|
||||
"hkdf-hmac-sha256",
|
||||
"hmac-sha256"
|
||||
),
|
||||
"short_authentication_string" to listOf(
|
||||
"decimal",
|
||||
"emoji"
|
||||
),
|
||||
"transaction_id" to "4wNOpkHGwGZPXjkZToooCDWfb8hsf7vW"
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `tracing id should be added to to_device contents`() {
|
||||
val fakeCryptoAPi = FakeCryptoApi()
|
||||
|
||||
val sendToDeviceTask = DefaultSendToDeviceTask(
|
||||
cryptoApi = fakeCryptoAPi,
|
||||
globalErrorReceiver = mockk(relaxed = true)
|
||||
)
|
||||
|
||||
val contentMap = MXUsersDevicesMap<Any>()
|
||||
|
||||
users.forEach { pairOfUserDevices ->
|
||||
val userId = pairOfUserDevices.first
|
||||
pairOfUserDevices.second.forEach {
|
||||
contentMap.setObject(userId, it, fakeEncryptedContent)
|
||||
}
|
||||
}
|
||||
|
||||
val params = SendToDeviceTask.Params(
|
||||
eventType = EventType.ENCRYPTED,
|
||||
contentMap = contentMap
|
||||
)
|
||||
|
||||
runBlocking {
|
||||
sendToDeviceTask.execute(params)
|
||||
}
|
||||
|
||||
val generatedIds = mutableListOf<String>()
|
||||
users.forEach { pairOfUserDevices ->
|
||||
val userId = pairOfUserDevices.first
|
||||
pairOfUserDevices.second.forEach {
|
||||
val modifiedContent = fakeCryptoAPi.body!!.messages!![userId]!![it] as Map<*, *>
|
||||
Assert.assertNotNull("Tracing id should have been added", modifiedContent["org.matrix.msgid"])
|
||||
generatedIds.add(modifiedContent["org.matrix.msgid"] as String)
|
||||
|
||||
assertEquals(
|
||||
"The rest of the content should be the same",
|
||||
fakeEncryptedContent.keys,
|
||||
modifiedContent.toMutableMap().apply { remove("org.matrix.msgid") }.keys
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals("Id should be unique per content", generatedIds.size, generatedIds.toSet().size)
|
||||
println("modified content ${fakeCryptoAPi.body}")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `tracing id should not be added to verification start to_device contents`() {
|
||||
val fakeCryptoAPi = FakeCryptoApi()
|
||||
|
||||
val sendToDeviceTask = DefaultSendToDeviceTask(
|
||||
cryptoApi = fakeCryptoAPi,
|
||||
globalErrorReceiver = mockk(relaxed = true)
|
||||
)
|
||||
val contentMap = MXUsersDevicesMap<Any>()
|
||||
contentMap.setObject("@alice:example.com", "MNQHVEISFQ", fakeStartVerificationContent)
|
||||
|
||||
val params = SendToDeviceTask.Params(
|
||||
eventType = EventType.KEY_VERIFICATION_START,
|
||||
contentMap = contentMap
|
||||
)
|
||||
|
||||
runBlocking {
|
||||
sendToDeviceTask.execute(params)
|
||||
}
|
||||
|
||||
val modifiedContent = fakeCryptoAPi.body!!.messages!!["@alice:example.com"]!!["MNQHVEISFQ"] as Map<*, *>
|
||||
Assert.assertNull("Tracing id should not have been added", modifiedContent["org.matrix.msgid"])
|
||||
|
||||
// try to force
|
||||
runBlocking {
|
||||
sendToDeviceTask.execute(
|
||||
SendToDeviceTask.Params(
|
||||
eventType = EventType.KEY_VERIFICATION_START,
|
||||
contentMap = contentMap,
|
||||
addTracingIds = true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val modifiedContentForced = fakeCryptoAPi.body!!.messages!!["@alice:example.com"]!!["MNQHVEISFQ"] as Map<*, *>
|
||||
Assert.assertNotNull("Tracing id should have been added", modifiedContentForced["org.matrix.msgid"])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `tracing id should not be added to all verification to_device contents`() {
|
||||
val fakeCryptoAPi = FakeCryptoApi()
|
||||
|
||||
val sendToDeviceTask = DefaultSendToDeviceTask(
|
||||
cryptoApi = fakeCryptoAPi,
|
||||
globalErrorReceiver = mockk(relaxed = true)
|
||||
)
|
||||
val contentMap = MXUsersDevicesMap<Any>()
|
||||
contentMap.setObject("@alice:example.com", "MNQHVEISFQ", emptyMap<String, Any>())
|
||||
|
||||
val verificationEvents = listOf(
|
||||
MessageType.MSGTYPE_VERIFICATION_REQUEST,
|
||||
EventType.KEY_VERIFICATION_START,
|
||||
EventType.KEY_VERIFICATION_ACCEPT,
|
||||
EventType.KEY_VERIFICATION_KEY,
|
||||
EventType.KEY_VERIFICATION_MAC,
|
||||
EventType.KEY_VERIFICATION_CANCEL,
|
||||
EventType.KEY_VERIFICATION_DONE,
|
||||
EventType.KEY_VERIFICATION_READY
|
||||
)
|
||||
|
||||
for (type in verificationEvents) {
|
||||
val params = SendToDeviceTask.Params(
|
||||
eventType = type,
|
||||
contentMap = contentMap
|
||||
)
|
||||
runBlocking {
|
||||
sendToDeviceTask.execute(params)
|
||||
}
|
||||
|
||||
val modifiedContent = fakeCryptoAPi.body!!.messages!!["@alice:example.com"]!!["MNQHVEISFQ"] as Map<*, *>
|
||||
Assert.assertNull("Tracing id should not have been added", modifiedContent["org.matrix.msgid"])
|
||||
}
|
||||
}
|
||||
|
||||
internal class FakeCryptoApi : CryptoApi {
|
||||
override suspend fun getDevices(): DevicesListResponse {
|
||||
throw java.lang.AssertionError("Should not be called")
|
||||
}
|
||||
|
||||
override suspend fun getDeviceInfo(deviceId: String): DeviceInfo {
|
||||
throw java.lang.AssertionError("Should not be called")
|
||||
}
|
||||
|
||||
override suspend fun uploadKeys(body: KeysUploadBody): KeysUploadResponse {
|
||||
throw java.lang.AssertionError("Should not be called")
|
||||
}
|
||||
|
||||
override suspend fun downloadKeysForUsers(params: KeysQueryBody): KeysQueryResponse {
|
||||
throw java.lang.AssertionError("Should not be called")
|
||||
}
|
||||
|
||||
override suspend fun uploadSigningKeys(params: UploadSigningKeysBody): KeysQueryResponse {
|
||||
throw java.lang.AssertionError("Should not be called")
|
||||
}
|
||||
|
||||
override suspend fun uploadSignatures(params: Map<String, Any>?): SignatureUploadResponse {
|
||||
throw java.lang.AssertionError("Should not be called")
|
||||
}
|
||||
|
||||
override suspend fun claimOneTimeKeysForUsersDevices(body: KeysClaimBody): KeysClaimResponse {
|
||||
throw java.lang.AssertionError("Should not be called")
|
||||
}
|
||||
|
||||
var body: SendToDeviceBody? = null
|
||||
override suspend fun sendToDevice(eventType: String, transactionId: String, body: SendToDeviceBody) {
|
||||
this.body = body
|
||||
}
|
||||
|
||||
override suspend fun deleteDevice(deviceId: String, params: DeleteDeviceParams) {
|
||||
throw java.lang.AssertionError("Should not be called")
|
||||
}
|
||||
|
||||
override suspend fun deleteDevices(params: DeleteDevicesParams) {
|
||||
throw java.lang.AssertionError("Should not be called")
|
||||
}
|
||||
|
||||
override suspend fun updateDeviceInfo(deviceId: String, params: UpdateDeviceInfoBody) {
|
||||
throw java.lang.AssertionError("Should not be called")
|
||||
}
|
||||
|
||||
override suspend fun getKeyChanges(oldToken: String, newToken: String): KeyChangesResponse {
|
||||
throw java.lang.AssertionError("Should not be called")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -345,7 +345,8 @@ ${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-x86-release-signe
|
|||
printf "File vector-gplay-x86_64-release-signed.apk:\n"
|
||||
${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-x86_64-release-signed.apk | grep package
|
||||
|
||||
read -p "\nDoes it look correct? Press enter when it's done."
|
||||
printf "\n"
|
||||
read -p "Does it look correct? Press enter when it's done."
|
||||
|
||||
printf "\n================================================================================\n"
|
||||
read -p "Installing apk on a real device, press enter when a real device is connected. "
|
||||
|
@ -356,7 +357,7 @@ read -p "Please run the APK on your phone to check that the upgrade went well (n
|
|||
# TODO Get the block to copy from towncrier earlier (be may be edited by the release manager)?
|
||||
read -p "Create the release on gitHub from the tag https://github.com/vector-im/element-android/tags, copy paste the block from the file CHANGES.md. Press enter when it's done."
|
||||
|
||||
read -p "Add the 4 signed APKs to the GitHub release. Press enter when it's done."
|
||||
read -p "Add the 4 signed APKs to the GitHub release. They are located at ${targetPath}. Press enter when it's done."
|
||||
|
||||
printf "\n================================================================================\n"
|
||||
printf "Message for the Android internal room:\n\n"
|
||||
|
|
|
@ -403,7 +403,7 @@ dependencies {
|
|||
debugImplementation(libs.flipper.flipperNetworkPlugin) {
|
||||
exclude group: 'com.facebook.fbjni', module: 'fbjni'
|
||||
}
|
||||
debugImplementation 'com.facebook.soloader:soloader:0.10.4'
|
||||
debugImplementation 'com.facebook.soloader:soloader:0.10.5'
|
||||
debugImplementation "com.kgurgul.flipper:flipper-realm-android:2.2.0"
|
||||
|
||||
gplayImplementation "com.google.android.gms:play-services-location:21.0.1"
|
||||
|
@ -418,7 +418,7 @@ dependencies {
|
|||
// API-only library
|
||||
gplayImplementation libs.google.appdistributionApi
|
||||
// Full SDK implementation
|
||||
gplayImplementation libs.google.appdistribution
|
||||
nightlyImplementation libs.google.appdistribution
|
||||
|
||||
// OSS License, gplay flavor only
|
||||
gplayImplementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
|
||||
|
@ -448,5 +448,5 @@ dependencies {
|
|||
androidTestImplementation libs.androidx.fragmentTesting
|
||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.22"
|
||||
debugImplementation libs.androidx.fragmentTesting
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
|
||||
}
|
||||
|
|
|
@ -46,9 +46,9 @@ abstract class FlavorModule {
|
|||
|
||||
@Provides
|
||||
fun provideNightlyProxy() = object : NightlyProxy {
|
||||
override fun onHomeResumed() {
|
||||
// no op
|
||||
}
|
||||
override fun canDisplayPopup() = false
|
||||
override fun isNightlyBuild() = false
|
||||
override fun updateApplication() = Unit
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package im.vector.app.push.fcm
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.pushers.FcmHelper
|
||||
|
@ -44,7 +43,7 @@ class FdroidFcmHelper @Inject constructor(
|
|||
// No op
|
||||
}
|
||||
|
||||
override fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) {
|
||||
override fun ensureFcmTokenIsRetrieved(pushersManager: PushersManager, registerPusher: Boolean) {
|
||||
// No op
|
||||
}
|
||||
|
||||
|
|
|
@ -34,8 +34,11 @@ class FirebaseNightlyProxy @Inject constructor(
|
|||
private val buildMeta: BuildMeta,
|
||||
) : NightlyProxy {
|
||||
|
||||
override fun onHomeResumed() {
|
||||
if (!canDisplayPopup()) return
|
||||
override fun isNightlyBuild(): Boolean {
|
||||
return buildMeta.applicationId in nightlyPackages
|
||||
}
|
||||
|
||||
override fun updateApplication() {
|
||||
val firebaseAppDistribution = FirebaseAppDistribution.getInstance()
|
||||
firebaseAppDistribution.updateIfNewReleaseAvailable()
|
||||
.addOnProgressListener { up ->
|
||||
|
@ -46,6 +49,7 @@ class FirebaseNightlyProxy @Inject constructor(
|
|||
when (e.errorCode) {
|
||||
FirebaseAppDistributionException.Status.NOT_IMPLEMENTED -> {
|
||||
// SDK did nothing. This is expected when building for Play.
|
||||
Timber.d("FirebaseAppDistribution NOT_IMPLEMENTED error")
|
||||
}
|
||||
else -> {
|
||||
// Handle other errors.
|
||||
|
@ -56,10 +60,14 @@ class FirebaseNightlyProxy @Inject constructor(
|
|||
Timber.e(e, "FirebaseAppDistribution - other error")
|
||||
}
|
||||
}
|
||||
.addOnSuccessListener {
|
||||
Timber.d("FirebaseAppDistribution Success!")
|
||||
}
|
||||
}
|
||||
|
||||
private fun canDisplayPopup(): Boolean {
|
||||
if (buildMeta.applicationId != "im.vector.app.nightly") return false
|
||||
override fun canDisplayPopup(): Boolean {
|
||||
if (!POPUP_IS_ENABLED) return false
|
||||
if (!isNightlyBuild()) return false
|
||||
val today = clock.epochMillis() / A_DAY_IN_MILLIS
|
||||
val lastDisplayPopupDay = sharedPreferences.getLong(SHARED_PREF_KEY, 0)
|
||||
return (today > lastDisplayPopupDay)
|
||||
|
@ -73,7 +81,12 @@ class FirebaseNightlyProxy @Inject constructor(
|
|||
}
|
||||
|
||||
companion object {
|
||||
private const val POPUP_IS_ENABLED = false
|
||||
private const val A_DAY_IN_MILLIS = 8_600_000L
|
||||
private const val SHARED_PREF_KEY = "LAST_NIGHTLY_POPUP_DAY"
|
||||
|
||||
private val nightlyPackages = listOf(
|
||||
"im.vector.app.nightly"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
package im.vector.app.push.fcm
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.widget.Toast
|
||||
|
@ -23,6 +22,7 @@ import androidx.core.content.edit
|
|||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.di.DefaultPreferences
|
||||
|
@ -36,8 +36,8 @@ import javax.inject.Inject
|
|||
* It has an alter ego in the fdroid variant.
|
||||
*/
|
||||
class GoogleFcmHelper @Inject constructor(
|
||||
@DefaultPreferences
|
||||
private val sharedPrefs: SharedPreferences,
|
||||
@ApplicationContext private val context: Context,
|
||||
@DefaultPreferences private val sharedPrefs: SharedPreferences,
|
||||
) : FcmHelper {
|
||||
companion object {
|
||||
private const val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN"
|
||||
|
@ -56,10 +56,9 @@ class GoogleFcmHelper @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) {
|
||||
// if (TextUtils.isEmpty(getFcmToken(activity))) {
|
||||
override fun ensureFcmTokenIsRetrieved(pushersManager: PushersManager, registerPusher: Boolean) {
|
||||
// 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features'
|
||||
if (checkPlayServices(activity)) {
|
||||
if (checkPlayServices(context)) {
|
||||
try {
|
||||
FirebaseMessaging.getInstance().token
|
||||
.addOnSuccessListener { token ->
|
||||
|
@ -75,7 +74,7 @@ class GoogleFcmHelper @Inject constructor(
|
|||
Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed")
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(activity, R.string.no_valid_google_play_services_apk, Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, R.string.no_valid_google_play_services_apk, Toast.LENGTH_SHORT).show()
|
||||
Timber.e("No valid Google Play Services found. Cannot use FCM.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ import im.vector.app.core.utils.AndroidSystemSettingsProvider
|
|||
import im.vector.app.core.utils.SystemSettingsProvider
|
||||
import im.vector.app.features.analytics.AnalyticsTracker
|
||||
import im.vector.app.features.analytics.VectorAnalytics
|
||||
import im.vector.app.features.analytics.errors.ErrorTracker
|
||||
import im.vector.app.features.analytics.impl.DefaultVectorAnalytics
|
||||
import im.vector.app.features.analytics.metrics.VectorPlugins
|
||||
import im.vector.app.features.invite.AutoAcceptInvites
|
||||
|
@ -84,6 +85,9 @@ import javax.inject.Singleton
|
|||
@Binds
|
||||
abstract fun bindVectorAnalytics(analytics: DefaultVectorAnalytics): VectorAnalytics
|
||||
|
||||
@Binds
|
||||
abstract fun bindErrorTracker(analytics: DefaultVectorAnalytics): ErrorTracker
|
||||
|
||||
@Binds
|
||||
abstract fun bindAnalyticsTracker(analytics: DefaultVectorAnalytics): AnalyticsTracker
|
||||
|
||||
|
|
|
@ -72,7 +72,9 @@
|
|||
<application android:supportsRtl="true">
|
||||
|
||||
<!-- Sentry auto-initialization disable -->
|
||||
<meta-data android:name="io.sentry.auto-init" android:value="false" />
|
||||
<meta-data
|
||||
android:name="io.sentry.auto-init"
|
||||
android:value="false" />
|
||||
|
||||
<!-- No limit for screen ratio: avoid black strips -->
|
||||
<meta-data
|
||||
|
@ -330,6 +332,7 @@
|
|||
|
||||
<service
|
||||
android:name=".core.services.CallAndroidService"
|
||||
android:foregroundServiceType="phoneCall"
|
||||
android:exported="false">
|
||||
<!-- in order to get headset button events -->
|
||||
<intent-filter>
|
||||
|
@ -341,6 +344,7 @@
|
|||
<service
|
||||
android:name=".core.services.VectorSyncAndroidService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="dataSync"
|
||||
tools:ignore="Instantiatable" />
|
||||
|
||||
<service
|
||||
|
|
|
@ -19,7 +19,7 @@ package im.vector.app.core.di
|
|||
import android.content.Context
|
||||
import im.vector.app.ActiveSessionDataSource
|
||||
import im.vector.app.core.extensions.startSyncing
|
||||
import im.vector.app.core.pushers.UnifiedPushHelper
|
||||
import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase
|
||||
import im.vector.app.core.services.GuardServiceStarter
|
||||
import im.vector.app.core.session.ConfigureAndStartSessionUseCase
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
|
@ -46,12 +46,12 @@ class ActiveSessionHolder @Inject constructor(
|
|||
private val pushRuleTriggerListener: PushRuleTriggerListener,
|
||||
private val sessionListener: SessionListener,
|
||||
private val imageManager: ImageManager,
|
||||
private val unifiedPushHelper: UnifiedPushHelper,
|
||||
private val guardServiceStarter: GuardServiceStarter,
|
||||
private val sessionInitializer: SessionInitializer,
|
||||
private val applicationContext: Context,
|
||||
private val authenticationService: AuthenticationService,
|
||||
private val configureAndStartSessionUseCase: ConfigureAndStartSessionUseCase,
|
||||
private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
|
||||
) {
|
||||
|
||||
private var activeSessionReference: AtomicReference<Session?> = AtomicReference()
|
||||
|
@ -85,7 +85,7 @@ class ActiveSessionHolder @Inject constructor(
|
|||
incomingVerificationRequestHandler.stop()
|
||||
pushRuleTriggerListener.stop()
|
||||
// No need to unregister the pusher, the sign out will (should?) do it server side.
|
||||
unifiedPushHelper.unregister(pushersManager = null)
|
||||
unregisterUnifiedPushUseCase.execute(pushersManager = null)
|
||||
guardServiceStarter.stop()
|
||||
}
|
||||
|
||||
|
|
|
@ -104,6 +104,7 @@ import im.vector.app.features.settings.ignored.IgnoredUsersViewModel
|
|||
import im.vector.app.features.settings.labs.VectorSettingsLabsViewModel
|
||||
import im.vector.app.features.settings.legals.LegalsViewModel
|
||||
import im.vector.app.features.settings.locale.LocalePickerViewModel
|
||||
import im.vector.app.features.settings.notifications.VectorSettingsNotificationPreferenceViewModel
|
||||
import im.vector.app.features.settings.push.PushGatewaysViewModel
|
||||
import im.vector.app.features.settings.threepids.ThreePidsSettingsViewModel
|
||||
import im.vector.app.features.share.IncomingShareViewModel
|
||||
|
@ -687,4 +688,11 @@ interface MavericksViewModelModule {
|
|||
@IntoMap
|
||||
@MavericksViewModelKey(AttachmentTypeSelectorViewModel::class)
|
||||
fun attachmentTypeSelectorViewModelFactory(factory: AttachmentTypeSelectorViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@MavericksViewModelKey(VectorSettingsNotificationPreferenceViewModel::class)
|
||||
fun vectorSettingsNotificationPreferenceViewModelFactory(
|
||||
factory: VectorSettingsNotificationPreferenceViewModel.Factory
|
||||
): MavericksAssistedViewModelFactory<*, *>
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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.extensions
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.Service
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.Build
|
||||
|
||||
fun Service.startForegroundCompat(
|
||||
id: Int,
|
||||
notification: Notification,
|
||||
provideForegroundServiceType: (() -> Int)? = null
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(
|
||||
id,
|
||||
notification,
|
||||
provideForegroundServiceType?.invoke() ?: ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
|
||||
)
|
||||
} else {
|
||||
startForeground(id, notification)
|
||||
}
|
||||
}
|
|
@ -23,14 +23,21 @@ import org.matrix.android.sdk.api.session.Session
|
|||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Listen changes in Pusher or Account Data to update the local setting for notification toggle.
|
||||
*/
|
||||
@Singleton
|
||||
class EnableNotificationsSettingUpdater @Inject constructor(
|
||||
class NotificationsSettingUpdater @Inject constructor(
|
||||
private val updateEnableNotificationsSettingOnChangeUseCase: UpdateEnableNotificationsSettingOnChangeUseCase,
|
||||
) {
|
||||
|
||||
private var job: Job? = null
|
||||
|
||||
fun onSessionsStarted(session: Session) {
|
||||
fun onSessionStarted(session: Session) {
|
||||
updateEnableNotificationsSettingOnChange(session)
|
||||
}
|
||||
|
||||
private fun updateEnableNotificationsSettingOnChange(session: Session) {
|
||||
job?.cancel()
|
||||
job = session.coroutineScope.launch {
|
||||
updateEnableNotificationsSettingOnChangeUseCase.execute(session)
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.pushers
|
||||
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import javax.inject.Inject
|
||||
|
||||
class EnsureFcmTokenIsRetrievedUseCase @Inject constructor(
|
||||
private val unifiedPushHelper: UnifiedPushHelper,
|
||||
private val fcmHelper: FcmHelper,
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
) {
|
||||
|
||||
fun execute(pushersManager: PushersManager, registerPusher: Boolean) {
|
||||
if (unifiedPushHelper.isEmbeddedDistributor()) {
|
||||
fcmHelper.ensureFcmTokenIsRetrieved(pushersManager, shouldAddHttpPusher(registerPusher))
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldAddHttpPusher(registerPusher: Boolean) = if (registerPusher) {
|
||||
val currentSession = activeSessionHolder.getActiveSession()
|
||||
val currentPushers = currentSession.pushersService().getPushers()
|
||||
currentPushers.none { it.deviceId == currentSession.sessionParams.deviceId }
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package im.vector.app.core.pushers
|
||||
|
||||
import android.app.Activity
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
|
||||
interface FcmHelper {
|
||||
|
@ -39,11 +38,10 @@ interface FcmHelper {
|
|||
/**
|
||||
* onNewToken may not be called on application upgrade, so ensure my shared pref is set.
|
||||
*
|
||||
* @param activity the first launch Activity.
|
||||
* @param pushersManager the instance to register the pusher on.
|
||||
* @param registerPusher whether the pusher should be registered.
|
||||
*/
|
||||
fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean)
|
||||
fun ensureFcmTokenIsRetrieved(pushersManager: PushersManager, registerPusher: Boolean)
|
||||
|
||||
fun onEnterForeground(activeSessionHolder: ActiveSessionHolder)
|
||||
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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.pushers
|
||||
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import im.vector.app.features.VectorFeatures
|
||||
import org.unifiedpush.android.connector.UnifiedPush
|
||||
import javax.inject.Inject
|
||||
|
||||
class RegisterUnifiedPushUseCase @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val vectorFeatures: VectorFeatures,
|
||||
) {
|
||||
|
||||
sealed interface RegisterUnifiedPushResult {
|
||||
object Success : RegisterUnifiedPushResult
|
||||
object NeedToAskUserForDistributor : RegisterUnifiedPushResult
|
||||
}
|
||||
|
||||
fun execute(distributor: String = ""): RegisterUnifiedPushResult {
|
||||
if (distributor.isNotEmpty()) {
|
||||
saveAndRegisterApp(distributor)
|
||||
return RegisterUnifiedPushResult.Success
|
||||
}
|
||||
|
||||
if (!vectorFeatures.allowExternalUnifiedPushDistributors()) {
|
||||
saveAndRegisterApp(context.packageName)
|
||||
return RegisterUnifiedPushResult.Success
|
||||
}
|
||||
|
||||
if (UnifiedPush.getDistributor(context).isNotEmpty()) {
|
||||
registerApp()
|
||||
return RegisterUnifiedPushResult.Success
|
||||
}
|
||||
|
||||
val distributors = UnifiedPush.getDistributors(context)
|
||||
|
||||
return if (distributors.size == 1) {
|
||||
saveAndRegisterApp(distributors.first())
|
||||
RegisterUnifiedPushResult.Success
|
||||
} else {
|
||||
RegisterUnifiedPushResult.NeedToAskUserForDistributor
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveAndRegisterApp(distributor: String) {
|
||||
UnifiedPush.saveDistributor(context, distributor)
|
||||
registerApp()
|
||||
}
|
||||
|
||||
private fun registerApp() {
|
||||
UnifiedPush.registerApp(context)
|
||||
}
|
||||
}
|
|
@ -17,18 +17,13 @@
|
|||
package im.vector.app.core.pushers
|
||||
|
||||
import android.content.Context
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.annotation.MainThread
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.utils.getApplicationLabel
|
||||
import im.vector.app.features.VectorFeatures
|
||||
import im.vector.app.features.settings.BackgroundSyncMode
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
import org.matrix.android.sdk.api.cache.CacheStrategy
|
||||
import org.matrix.android.sdk.api.util.MatrixJsonParser
|
||||
|
@ -41,90 +36,14 @@ class UnifiedPushHelper @Inject constructor(
|
|||
private val context: Context,
|
||||
private val unifiedPushStore: UnifiedPushStore,
|
||||
private val stringProvider: StringProvider,
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val matrix: Matrix,
|
||||
private val vectorFeatures: VectorFeatures,
|
||||
private val fcmHelper: FcmHelper,
|
||||
) {
|
||||
|
||||
// Called when the home activity starts
|
||||
// or when notifications are enabled
|
||||
fun register(
|
||||
activity: FragmentActivity,
|
||||
onDoneRunnable: Runnable? = null,
|
||||
) {
|
||||
registerInternal(
|
||||
activity,
|
||||
onDoneRunnable = onDoneRunnable
|
||||
)
|
||||
}
|
||||
|
||||
// If registration is forced:
|
||||
// * the current distributor (if any) is removed
|
||||
// * The dialog is opened
|
||||
//
|
||||
// The registration is forced in 2 cases :
|
||||
// * in the settings
|
||||
// * in the troubleshoot list (doFix)
|
||||
fun forceRegister(
|
||||
activity: FragmentActivity,
|
||||
pushersManager: PushersManager,
|
||||
onDoneRunnable: Runnable? = null
|
||||
) {
|
||||
registerInternal(
|
||||
activity,
|
||||
force = true,
|
||||
pushersManager = pushersManager,
|
||||
onDoneRunnable = onDoneRunnable
|
||||
)
|
||||
}
|
||||
|
||||
private fun registerInternal(
|
||||
activity: FragmentActivity,
|
||||
force: Boolean = false,
|
||||
pushersManager: PushersManager? = null,
|
||||
onDoneRunnable: Runnable? = null
|
||||
) {
|
||||
activity.lifecycleScope.launch {
|
||||
if (!vectorFeatures.allowExternalUnifiedPushDistributors()) {
|
||||
UnifiedPush.saveDistributor(context, context.packageName)
|
||||
UnifiedPush.registerApp(context)
|
||||
onDoneRunnable?.run()
|
||||
return@launch
|
||||
}
|
||||
if (force) {
|
||||
// Un-register first
|
||||
unregister(pushersManager)
|
||||
}
|
||||
// the !force should not be needed
|
||||
if (!force && UnifiedPush.getDistributor(context).isNotEmpty()) {
|
||||
UnifiedPush.registerApp(context)
|
||||
onDoneRunnable?.run()
|
||||
return@launch
|
||||
}
|
||||
|
||||
val distributors = UnifiedPush.getDistributors(context)
|
||||
|
||||
if (!force && distributors.size == 1) {
|
||||
UnifiedPush.saveDistributor(context, distributors.first())
|
||||
UnifiedPush.registerApp(context)
|
||||
onDoneRunnable?.run()
|
||||
} else {
|
||||
openDistributorDialogInternal(
|
||||
activity = activity,
|
||||
onDoneRunnable = onDoneRunnable,
|
||||
distributors = distributors
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// There is no case where this function is called
|
||||
// with a saved distributor and/or a pusher
|
||||
private fun openDistributorDialogInternal(
|
||||
activity: FragmentActivity,
|
||||
onDoneRunnable: Runnable?,
|
||||
distributors: List<String>
|
||||
@MainThread
|
||||
fun showSelectDistributorDialog(
|
||||
context: Context,
|
||||
onDistributorSelected: (String) -> Unit,
|
||||
) {
|
||||
val internalDistributorName = stringProvider.getString(
|
||||
if (fcmHelper.isFirebaseAvailable()) {
|
||||
|
@ -134,6 +53,7 @@ class UnifiedPushHelper @Inject constructor(
|
|||
}
|
||||
)
|
||||
|
||||
val distributors = UnifiedPush.getDistributors(context)
|
||||
val distributorsName = distributors.map {
|
||||
if (it == context.packageName) {
|
||||
internalDistributorName
|
||||
|
@ -142,44 +62,23 @@ class UnifiedPushHelper @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(stringProvider.getString(R.string.unifiedpush_getdistributors_dialog_title))
|
||||
.setItems(distributorsName.toTypedArray()) { _, which ->
|
||||
val distributor = distributors[which]
|
||||
|
||||
activity.lifecycleScope.launch {
|
||||
UnifiedPush.saveDistributor(context, distributor)
|
||||
Timber.i("Saving distributor: $distributor")
|
||||
UnifiedPush.registerApp(context)
|
||||
onDoneRunnable?.run()
|
||||
}
|
||||
onDistributorSelected(distributor)
|
||||
}
|
||||
.setOnCancelListener {
|
||||
// we do not want to change the distributor on behalf of the user
|
||||
if (UnifiedPush.getDistributor(context).isEmpty()) {
|
||||
// By default, use internal solution (fcm/background sync)
|
||||
UnifiedPush.saveDistributor(context, context.packageName)
|
||||
UnifiedPush.registerApp(context)
|
||||
onDoneRunnable?.run()
|
||||
onDistributorSelected(context.packageName)
|
||||
}
|
||||
}
|
||||
.setCancelable(true)
|
||||
.show()
|
||||
}
|
||||
|
||||
suspend fun unregister(pushersManager: PushersManager? = null) {
|
||||
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
|
||||
vectorPreferences.setFdroidSyncBackgroundMode(mode)
|
||||
try {
|
||||
getEndpointOrToken()?.let {
|
||||
Timber.d("Removing $it")
|
||||
pushersManager?.unregisterPusher(it)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.d(e, "Probably unregistering a non existing pusher")
|
||||
}
|
||||
unifiedPushStore.storeUpEndpoint(null)
|
||||
unifiedPushStore.storePushGateway(null)
|
||||
UnifiedPush.unregisterApp(context)
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class DiscoveryResponse(
|
||||
@Json(name = "unifiedpush") val unifiedpush: DiscoveryUnifiedPush = DiscoveryUnifiedPush()
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.pushers
|
||||
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import im.vector.app.features.settings.BackgroundSyncMode
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import org.unifiedpush.android.connector.UnifiedPush
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class UnregisterUnifiedPushUseCase @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val unifiedPushStore: UnifiedPushStore,
|
||||
private val unifiedPushHelper: UnifiedPushHelper,
|
||||
) {
|
||||
|
||||
suspend fun execute(pushersManager: PushersManager?) {
|
||||
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
|
||||
vectorPreferences.setFdroidSyncBackgroundMode(mode)
|
||||
try {
|
||||
unifiedPushHelper.getEndpointOrToken()?.let {
|
||||
Timber.d("Removing $it")
|
||||
pushersManager?.unregisterPusher(it)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.d(e, "Probably unregistering a non existing pusher")
|
||||
}
|
||||
unifiedPushStore.storeUpEndpoint(null)
|
||||
unifiedPushStore.storePushGateway(null)
|
||||
UnifiedPush.unregisterApp(context)
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ import androidx.media.session.MediaButtonReceiver
|
|||
import com.airbnb.mvrx.Mavericks
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.core.extensions.singletonEntryPoint
|
||||
import im.vector.app.core.extensions.startForegroundCompat
|
||||
import im.vector.app.features.call.CallArgs
|
||||
import im.vector.app.features.call.VectorCallActivity
|
||||
import im.vector.app.features.call.telecom.CallConnection
|
||||
|
@ -181,7 +182,7 @@ class CallAndroidService : VectorAndroidService() {
|
|||
fromBg = fromBg
|
||||
)
|
||||
if (knownCalls.isEmpty()) {
|
||||
startForeground(callId.hashCode(), notification)
|
||||
startForegroundCompat(callId.hashCode(), notification)
|
||||
} else {
|
||||
notificationManager.notify(callId.hashCode(), notification)
|
||||
}
|
||||
|
@ -201,7 +202,7 @@ class CallAndroidService : VectorAndroidService() {
|
|||
}
|
||||
val notification = notificationUtils.buildCallEndedNotification(false)
|
||||
val notificationId = callId.hashCode()
|
||||
startForeground(notificationId, notification)
|
||||
startForegroundCompat(notificationId, notification)
|
||||
if (knownCalls.isEmpty()) {
|
||||
Timber.tag(loggerTag.value).v("No more call, stop the service")
|
||||
stopForegroundCompat()
|
||||
|
@ -236,7 +237,7 @@ class CallAndroidService : VectorAndroidService() {
|
|||
title = callInformation.opponentMatrixItem?.getBestName() ?: callInformation.opponentUserId
|
||||
)
|
||||
if (knownCalls.isEmpty()) {
|
||||
startForeground(callId.hashCode(), notification)
|
||||
startForegroundCompat(callId.hashCode(), notification)
|
||||
} else {
|
||||
notificationManager.notify(callId.hashCode(), notification)
|
||||
}
|
||||
|
@ -260,7 +261,7 @@ class CallAndroidService : VectorAndroidService() {
|
|||
title = callInformation.opponentMatrixItem?.getBestName() ?: callInformation.opponentUserId
|
||||
)
|
||||
if (knownCalls.isEmpty()) {
|
||||
startForeground(callId.hashCode(), notification)
|
||||
startForegroundCompat(callId.hashCode(), notification)
|
||||
} else {
|
||||
notificationManager.notify(callId.hashCode(), notification)
|
||||
}
|
||||
|
@ -273,9 +274,9 @@ class CallAndroidService : VectorAndroidService() {
|
|||
callRingPlayerOutgoing?.stop()
|
||||
val notification = notificationUtils.buildCallEndedNotification(false)
|
||||
if (callId != null) {
|
||||
startForeground(callId.hashCode(), notification)
|
||||
startForegroundCompat(callId.hashCode(), notification)
|
||||
} else {
|
||||
startForeground(DEFAULT_NOTIFICATION_ID, notification)
|
||||
startForegroundCompat(DEFAULT_NOTIFICATION_ID, notification)
|
||||
}
|
||||
if (knownCalls.isEmpty()) {
|
||||
mediaSession?.isActive = false
|
||||
|
|
|
@ -32,6 +32,7 @@ import androidx.work.Worker
|
|||
import androidx.work.WorkerParameters
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.startForegroundCompat
|
||||
import im.vector.app.core.platform.PendingIntentCompat
|
||||
import im.vector.app.core.time.Clock
|
||||
import im.vector.app.core.time.DefaultClock
|
||||
|
@ -98,7 +99,7 @@ class VectorSyncAndroidService : SyncAndroidService() {
|
|||
R.string.notification_listening_for_notifications
|
||||
}
|
||||
val notification = notificationUtils.buildForegroundServiceNotification(notificationSubtitleRes, false)
|
||||
startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
|
||||
startForegroundCompat(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
|
||||
}
|
||||
|
||||
override fun onRescheduleAsked(
|
||||
|
|
|
@ -19,11 +19,12 @@ package im.vector.app.core.session
|
|||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import im.vector.app.core.extensions.startSyncing
|
||||
import im.vector.app.core.notification.EnableNotificationsSettingUpdater
|
||||
import im.vector.app.core.notification.NotificationsSettingUpdater
|
||||
import im.vector.app.core.session.clientinfo.UpdateMatrixClientInfoUseCase
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.session.coroutineScope
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.settings.devices.v2.notification.UpdateNotificationSettingsAccountDataUseCase
|
||||
import im.vector.app.features.sync.SyncUtils
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
@ -35,7 +36,8 @@ class ConfigureAndStartSessionUseCase @Inject constructor(
|
|||
private val webRtcCallManager: WebRtcCallManager,
|
||||
private val updateMatrixClientInfoUseCase: UpdateMatrixClientInfoUseCase,
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val enableNotificationsSettingUpdater: EnableNotificationsSettingUpdater,
|
||||
private val notificationsSettingUpdater: NotificationsSettingUpdater,
|
||||
private val updateNotificationSettingsAccountDataUseCase: UpdateNotificationSettingsAccountDataUseCase,
|
||||
) {
|
||||
|
||||
fun execute(session: Session, startSyncing: Boolean = true) {
|
||||
|
@ -49,11 +51,22 @@ class ConfigureAndStartSessionUseCase @Inject constructor(
|
|||
}
|
||||
session.pushersService().refreshPushers()
|
||||
webRtcCallManager.checkForProtocolsSupportIfNeeded()
|
||||
updateMatrixClientInfoIfNeeded(session)
|
||||
createNotificationSettingsAccountDataIfNeeded(session)
|
||||
notificationsSettingUpdater.onSessionStarted(session)
|
||||
}
|
||||
|
||||
private fun updateMatrixClientInfoIfNeeded(session: Session) {
|
||||
session.coroutineScope.launch {
|
||||
if (vectorPreferences.isClientInfoRecordingEnabled()) {
|
||||
updateMatrixClientInfoUseCase.execute(session)
|
||||
}
|
||||
}
|
||||
enableNotificationsSettingUpdater.onSessionsStarted(session)
|
||||
}
|
||||
|
||||
private fun createNotificationSettingsAccountDataIfNeeded(session: Session) {
|
||||
session.coroutineScope.launch {
|
||||
updateNotificationSettingsAccountDataUseCase.execute(session)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,25 @@ class ShieldImageView @JvmOverloads constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders device shield with the support of unknown shields instead of black shields which is used for rooms.
|
||||
* @param roomEncryptionTrustLevel trust level that is usally calculated with [im.vector.app.features.settings.devices.TrustUtils.shieldForTrust]
|
||||
* @param borderLess if true then the shield icon with border around is used
|
||||
*/
|
||||
fun renderDeviceShield(roomEncryptionTrustLevel: RoomEncryptionTrustLevel?, borderLess: Boolean = false) {
|
||||
isVisible = roomEncryptionTrustLevel != null
|
||||
|
||||
if (roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Default) {
|
||||
contentDescription = context.getString(R.string.a11y_trust_level_default)
|
||||
setImageResource(
|
||||
if (borderLess) R.drawable.ic_shield_unknown_no_border
|
||||
else R.drawable.ic_shield_unknown
|
||||
)
|
||||
} else {
|
||||
render(roomEncryptionTrustLevel, borderLess)
|
||||
}
|
||||
}
|
||||
|
||||
fun render(roomEncryptionTrustLevel: RoomEncryptionTrustLevel?, borderLess: Boolean = false) {
|
||||
isVisible = roomEncryptionTrustLevel != null
|
||||
|
||||
|
@ -45,8 +64,8 @@ class ShieldImageView @JvmOverloads constructor(
|
|||
RoomEncryptionTrustLevel.Default -> {
|
||||
contentDescription = context.getString(R.string.a11y_trust_level_default)
|
||||
setImageResource(
|
||||
if (borderLess) R.drawable.ic_shield_unknown_no_border
|
||||
else R.drawable.ic_shield_unknown
|
||||
if (borderLess) R.drawable.ic_shield_black_no_border
|
||||
else R.drawable.ic_shield_black
|
||||
)
|
||||
}
|
||||
RoomEncryptionTrustLevel.Warning -> {
|
||||
|
@ -137,7 +156,7 @@ class ShieldImageView @JvmOverloads constructor(
|
|||
@DrawableRes
|
||||
fun RoomEncryptionTrustLevel.toDrawableRes(): Int {
|
||||
return when (this) {
|
||||
RoomEncryptionTrustLevel.Default -> R.drawable.ic_shield_unknown
|
||||
RoomEncryptionTrustLevel.Default -> R.drawable.ic_shield_black
|
||||
RoomEncryptionTrustLevel.Warning -> R.drawable.ic_shield_warning
|
||||
RoomEncryptionTrustLevel.Trusted -> R.drawable.ic_shield_trusted
|
||||
RoomEncryptionTrustLevel.E2EWithUnsupportedAlgorithm -> R.drawable.ic_warning_badge
|
||||
|
|
|
@ -16,9 +16,10 @@
|
|||
|
||||
package im.vector.app.features.analytics
|
||||
|
||||
import im.vector.app.features.analytics.errors.ErrorTracker
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface VectorAnalytics : AnalyticsTracker {
|
||||
interface VectorAnalytics : AnalyticsTracker, ErrorTracker {
|
||||
/**
|
||||
* Return a Flow of Boolean, true if the user has given their consent.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.analytics.errors
|
||||
|
||||
interface ErrorTracker {
|
||||
fun trackError(throwable: Throwable)
|
||||
}
|
|
@ -41,7 +41,7 @@ private val IGNORED_OPTIONS: Options? = null
|
|||
@Singleton
|
||||
class DefaultVectorAnalytics @Inject constructor(
|
||||
postHogFactory: PostHogFactory,
|
||||
private val sentryFactory: SentryFactory,
|
||||
private val sentryAnalytics: SentryAnalytics,
|
||||
analyticsConfig: AnalyticsConfig,
|
||||
private val analyticsStore: AnalyticsStore,
|
||||
private val lateInitUserPropertiesFactory: LateInitUserPropertiesFactory,
|
||||
|
@ -97,7 +97,7 @@ class DefaultVectorAnalytics @Inject constructor(
|
|||
setAnalyticsId("")
|
||||
|
||||
// Close Sentry SDK.
|
||||
sentryFactory.stopSentry()
|
||||
sentryAnalytics.stopSentry()
|
||||
}
|
||||
|
||||
private fun observeAnalyticsId() {
|
||||
|
@ -135,8 +135,8 @@ class DefaultVectorAnalytics @Inject constructor(
|
|||
private fun initOrStopSentry() {
|
||||
userConsent?.let {
|
||||
when (it) {
|
||||
true -> sentryFactory.initSentry()
|
||||
false -> sentryFactory.stopSentry()
|
||||
true -> sentryAnalytics.initSentry()
|
||||
false -> sentryAnalytics.stopSentry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -180,4 +180,10 @@ class DefaultVectorAnalytics @Inject constructor(
|
|||
putAll(this@toPostHogUserProperties.filter { it.value != null })
|
||||
}
|
||||
}
|
||||
|
||||
override fun trackError(throwable: Throwable) {
|
||||
sentryAnalytics
|
||||
.takeIf { userConsent == true }
|
||||
?.trackError(throwable)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.app.features.analytics.impl
|
|||
|
||||
import android.content.Context
|
||||
import im.vector.app.features.analytics.AnalyticsConfig
|
||||
import im.vector.app.features.analytics.errors.ErrorTracker
|
||||
import im.vector.app.features.analytics.log.analyticsTag
|
||||
import io.sentry.Sentry
|
||||
import io.sentry.SentryOptions
|
||||
|
@ -25,10 +26,10 @@ import io.sentry.android.core.SentryAndroid
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class SentryFactory @Inject constructor(
|
||||
class SentryAnalytics @Inject constructor(
|
||||
private val context: Context,
|
||||
private val analyticsConfig: AnalyticsConfig,
|
||||
) {
|
||||
) : ErrorTracker {
|
||||
|
||||
fun initSentry() {
|
||||
Timber.tag(analyticsTag.value).d("Initializing Sentry")
|
||||
|
@ -47,4 +48,8 @@ class SentryFactory @Inject constructor(
|
|||
Timber.tag(analyticsTag.value).d("Stopping Sentry")
|
||||
Sentry.close()
|
||||
}
|
||||
|
||||
override fun trackError(throwable: Throwable) {
|
||||
Sentry.captureException(throwable)
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ import android.content.Intent
|
|||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.core.extensions.startForegroundCompat
|
||||
import im.vector.app.core.services.VectorAndroidService
|
||||
import im.vector.app.core.time.Clock
|
||||
import im.vector.app.features.notifications.NotificationUtils
|
||||
|
@ -41,7 +42,7 @@ class ScreenCaptureAndroidService : VectorAndroidService() {
|
|||
private fun showStickyNotification() {
|
||||
val notificationId = clock.epochMillis().toInt()
|
||||
val notification = notificationUtils.buildScreenSharingNotification()
|
||||
startForeground(notificationId, notification)
|
||||
startForegroundCompat(notificationId, notification)
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder {
|
||||
|
|
|
@ -44,8 +44,6 @@ import im.vector.app.core.extensions.restart
|
|||
import im.vector.app.core.extensions.validateBackPressed
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.core.platform.VectorMenuProvider
|
||||
import im.vector.app.core.pushers.FcmHelper
|
||||
import im.vector.app.core.pushers.PushersManager
|
||||
import im.vector.app.core.pushers.UnifiedPushHelper
|
||||
import im.vector.app.core.utils.registerForPermissionsResult
|
||||
import im.vector.app.core.utils.startSharePlainTextIntent
|
||||
|
@ -131,7 +129,6 @@ class HomeActivity :
|
|||
private val serverBackupStatusViewModel: ServerBackupStatusViewModel by viewModel()
|
||||
|
||||
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
|
||||
@Inject lateinit var pushersManager: PushersManager
|
||||
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
|
||||
@Inject lateinit var popupAlertManager: PopupAlertManager
|
||||
@Inject lateinit var shortcutsHandler: ShortcutsHandler
|
||||
|
@ -140,7 +137,6 @@ class HomeActivity :
|
|||
@Inject lateinit var initSyncStepFormatter: InitSyncStepFormatter
|
||||
@Inject lateinit var spaceStateHandler: SpaceStateHandler
|
||||
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper
|
||||
@Inject lateinit var fcmHelper: FcmHelper
|
||||
@Inject lateinit var nightlyProxy: NightlyProxy
|
||||
@Inject lateinit var disclaimerDialog: DisclaimerDialog
|
||||
@Inject lateinit var notificationPermissionManager: NotificationPermissionManager
|
||||
|
@ -212,16 +208,6 @@ class HomeActivity :
|
|||
isNewAppLayoutEnabled = vectorPreferences.isNewAppLayoutEnabled()
|
||||
analyticsScreenName = MobileScreen.ScreenName.Home
|
||||
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false)
|
||||
unifiedPushHelper.register(this) {
|
||||
if (unifiedPushHelper.isEmbeddedDistributor()) {
|
||||
fcmHelper.ensureFcmTokenIsRetrieved(
|
||||
this,
|
||||
pushersManager,
|
||||
homeActivityViewModel.shouldAddHttpPusher()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
sharedActionViewModel = viewModelProvider[HomeSharedActionViewModel::class.java]
|
||||
roomListSharedActionViewModel = viewModelProvider[RoomListSharedActionViewModel::class.java]
|
||||
views.drawerLayout.addDrawerListener(drawerListener)
|
||||
|
@ -284,6 +270,7 @@ class HomeActivity :
|
|||
HomeActivityViewEvents.ShowReleaseNotes -> handleShowReleaseNotes()
|
||||
HomeActivityViewEvents.NotifyUserForThreadsMigration -> handleNotifyUserForThreadsMigration()
|
||||
is HomeActivityViewEvents.MigrateThreads -> migrateThreadsIfNeeded(it.checkSession)
|
||||
is HomeActivityViewEvents.AskUserForPushDistributor -> askUserToSelectPushDistributor()
|
||||
}
|
||||
}
|
||||
homeActivityViewModel.onEach { renderState(it) }
|
||||
|
@ -296,6 +283,12 @@ class HomeActivity :
|
|||
homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted)
|
||||
}
|
||||
|
||||
private fun askUserToSelectPushDistributor() {
|
||||
unifiedPushHelper.showSelectDistributorDialog(this) { selection ->
|
||||
homeActivityViewModel.handle(HomeActivityViewActions.RegisterPushDistributor(selection))
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleShowNotificationDialog() {
|
||||
notificationPermissionManager.eventuallyRequestPermission(this, postPermissionLauncher)
|
||||
}
|
||||
|
@ -419,14 +412,6 @@ class HomeActivity :
|
|||
}
|
||||
|
||||
private fun renderState(state: HomeActivityViewState) {
|
||||
lifecycleScope.launch {
|
||||
if (state.areNotificationsSilenced) {
|
||||
unifiedPushHelper.unregister(pushersManager)
|
||||
} else {
|
||||
unifiedPushHelper.register(this@HomeActivity)
|
||||
}
|
||||
}
|
||||
|
||||
when (val status = state.syncRequestState) {
|
||||
is SyncRequestState.InitialSyncProgressing -> {
|
||||
val initSyncStepStr = initSyncStepFormatter.format(status.initialSyncStep)
|
||||
|
@ -609,7 +594,9 @@ class HomeActivity :
|
|||
serverBackupStatusViewModel.refreshRemoteStateIfNeeded()
|
||||
|
||||
// Check nightly
|
||||
nightlyProxy.onHomeResumed()
|
||||
if (nightlyProxy.canDisplayPopup()) {
|
||||
nightlyProxy.updateApplication()
|
||||
}
|
||||
|
||||
checkNewAppLayoutFlagChange()
|
||||
}
|
||||
|
|
|
@ -21,4 +21,5 @@ import im.vector.app.core.platform.VectorViewModelAction
|
|||
sealed interface HomeActivityViewActions : VectorViewModelAction {
|
||||
object ViewStarted : HomeActivityViewActions
|
||||
object PushPromptHasBeenReviewed : HomeActivityViewActions
|
||||
data class RegisterPushDistributor(val distributor: String) : HomeActivityViewActions
|
||||
}
|
||||
|
|
|
@ -25,9 +25,11 @@ sealed interface HomeActivityViewEvents : VectorViewEvents {
|
|||
val userItem: MatrixItem.UserItem,
|
||||
// val waitForIncomingRequest: Boolean = true,
|
||||
) : HomeActivityViewEvents
|
||||
|
||||
data class CurrentSessionCannotBeVerified(
|
||||
val userItem: MatrixItem.UserItem,
|
||||
) : HomeActivityViewEvents
|
||||
|
||||
data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents
|
||||
object PromptToEnableSessionPush : HomeActivityViewEvents
|
||||
object ShowAnalyticsOptIn : HomeActivityViewEvents
|
||||
|
@ -37,4 +39,5 @@ sealed interface HomeActivityViewEvents : VectorViewEvents {
|
|||
data class MigrateThreads(val checkSession: Boolean) : HomeActivityViewEvents
|
||||
object StartRecoverySetupFlow : HomeActivityViewEvents
|
||||
data class ForceVerification(val sendRequest: Boolean) : HomeActivityViewEvents
|
||||
object AskUserForPushDistributor : HomeActivityViewEvents
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package im.vector.app.features.home
|
||||
|
||||
import androidx.lifecycle.asFlow
|
||||
import com.airbnb.mvrx.Mavericks
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
|
@ -27,7 +26,10 @@ import im.vector.app.core.di.ActiveSessionHolder
|
|||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.VectorFeatures
|
||||
import im.vector.app.core.pushers.EnsureFcmTokenIsRetrievedUseCase
|
||||
import im.vector.app.core.pushers.PushersManager
|
||||
import im.vector.app.core.pushers.RegisterUnifiedPushUseCase
|
||||
import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase
|
||||
import im.vector.app.features.analytics.AnalyticsConfig
|
||||
import im.vector.app.features.analytics.AnalyticsTracker
|
||||
import im.vector.app.features.analytics.extensions.toAnalyticsType
|
||||
|
@ -48,12 +50,10 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.account.LocalNotificationSettingsContent
|
||||
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||
|
@ -62,9 +62,7 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
|||
import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.getUserOrDefault
|
||||
import org.matrix.android.sdk.api.session.pushrules.RuleIds
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
|
@ -89,8 +87,11 @@ class HomeActivityViewModel @AssistedInject constructor(
|
|||
private val analyticsTracker: AnalyticsTracker,
|
||||
private val analyticsConfig: AnalyticsConfig,
|
||||
private val releaseNotesPreferencesStore: ReleaseNotesPreferencesStore,
|
||||
private val vectorFeatures: VectorFeatures,
|
||||
private val stopOngoingVoiceBroadcastUseCase: StopOngoingVoiceBroadcastUseCase,
|
||||
private val pushersManager: PushersManager,
|
||||
private val registerUnifiedPushUseCase: RegisterUnifiedPushUseCase,
|
||||
private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
|
||||
private val ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase,
|
||||
) : VectorViewModel<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(initialState) {
|
||||
|
||||
@AssistedFactory
|
||||
|
@ -114,17 +115,44 @@ class HomeActivityViewModel @AssistedInject constructor(
|
|||
private fun initialize() {
|
||||
if (isInitialized) return
|
||||
isInitialized = true
|
||||
registerUnifiedPushIfNeeded()
|
||||
cleanupFiles()
|
||||
observeInitialSync()
|
||||
checkSessionPushIsOn()
|
||||
observeCrossSigningReset()
|
||||
observeAnalytics()
|
||||
observeReleaseNotes()
|
||||
observeLocalNotificationsSilenced()
|
||||
initThreadsMigration()
|
||||
viewModelScope.launch { stopOngoingVoiceBroadcastUseCase.execute() }
|
||||
}
|
||||
|
||||
private fun registerUnifiedPushIfNeeded() {
|
||||
if (vectorPreferences.areNotificationEnabledForDevice()) {
|
||||
registerUnifiedPush(distributor = "")
|
||||
} else {
|
||||
unregisterUnifiedPush()
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerUnifiedPush(distributor: String) {
|
||||
viewModelScope.launch {
|
||||
when (registerUnifiedPushUseCase.execute(distributor = distributor)) {
|
||||
is RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.NeedToAskUserForDistributor -> {
|
||||
_viewEvents.post(HomeActivityViewEvents.AskUserForPushDistributor)
|
||||
}
|
||||
RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success -> {
|
||||
ensureFcmTokenIsRetrievedUseCase.execute(pushersManager, registerPusher = vectorPreferences.areNotificationEnabledForDevice())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun unregisterUnifiedPush() {
|
||||
viewModelScope.launch {
|
||||
unregisterUnifiedPushUseCase.execute(pushersManager)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeReleaseNotes() = withState { state ->
|
||||
if (vectorPreferences.isNewAppLayoutEnabled()) {
|
||||
// we don't want to show release notes for new users or after relogin
|
||||
|
@ -143,26 +171,6 @@ class HomeActivityViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun shouldAddHttpPusher() = if (vectorPreferences.areNotificationEnabledForDevice()) {
|
||||
val currentSession = activeSessionHolder.getActiveSession()
|
||||
val currentPushers = currentSession.pushersService().getPushers()
|
||||
currentPushers.none { it.deviceId == currentSession.sessionParams.deviceId }
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
fun observeLocalNotificationsSilenced() {
|
||||
val currentSession = activeSessionHolder.getActiveSession()
|
||||
val deviceId = currentSession.cryptoService().getMyCryptoDevice().deviceId
|
||||
viewModelScope.launch {
|
||||
currentSession.accountDataService()
|
||||
.getLiveUserAccountDataEvent(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId)
|
||||
.asFlow()
|
||||
.map { it.getOrNull()?.content?.toModel<LocalNotificationSettingsContent>()?.isSilenced ?: false }
|
||||
.onEach { setState { copy(areNotificationsSilenced = it) } }
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeAnalytics() {
|
||||
if (analyticsConfig.isEnabled) {
|
||||
analyticsStore.didAskUserConsentFlow
|
||||
|
@ -496,6 +504,9 @@ class HomeActivityViewModel @AssistedInject constructor(
|
|||
HomeActivityViewActions.ViewStarted -> {
|
||||
initialize()
|
||||
}
|
||||
is HomeActivityViewActions.RegisterPushDistributor -> {
|
||||
registerUnifiedPush(distributor = action.distributor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,5 +23,4 @@ import org.matrix.android.sdk.api.session.sync.SyncRequestState
|
|||
data class HomeActivityViewState(
|
||||
val syncRequestState: SyncRequestState = SyncRequestState.Idle,
|
||||
val authenticationDescription: AuthenticationDescription? = null,
|
||||
val areNotificationsSilenced: Boolean = false,
|
||||
) : MavericksState
|
||||
|
|
|
@ -17,5 +17,18 @@
|
|||
package im.vector.app.features.home
|
||||
|
||||
interface NightlyProxy {
|
||||
fun onHomeResumed()
|
||||
/**
|
||||
* Return true if this is a nightly build (checking the package of the app), and only once a day.
|
||||
*/
|
||||
fun canDisplayPopup(): Boolean
|
||||
|
||||
/**
|
||||
* Return true if this is a nightly build (checking the package of the app).
|
||||
*/
|
||||
fun isNightlyBuild(): Boolean
|
||||
|
||||
/**
|
||||
* Try to update the application, if update is available. Will also take care of the user sign in.
|
||||
*/
|
||||
fun updateApplication()
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ import im.vector.app.core.utils.onPermissionDeniedDialog
|
|||
import im.vector.app.core.utils.registerForPermissionsResult
|
||||
import im.vector.app.databinding.FragmentComposerBinding
|
||||
import im.vector.app.features.VectorFeatures
|
||||
import im.vector.app.features.analytics.errors.ErrorTracker
|
||||
import im.vector.app.features.attachments.AttachmentType
|
||||
import im.vector.app.features.attachments.AttachmentTypeSelectorBottomSheet
|
||||
import im.vector.app.features.attachments.AttachmentTypeSelectorSharedAction
|
||||
|
@ -116,6 +117,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
|
|||
@Inject lateinit var vectorFeatures: VectorFeatures
|
||||
@Inject lateinit var buildMeta: BuildMeta
|
||||
@Inject lateinit var session: Session
|
||||
@Inject lateinit var errorTracker: ErrorTracker
|
||||
|
||||
private val roomId: String get() = withState(timelineViewModel) { it.roomId }
|
||||
|
||||
|
@ -171,6 +173,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
|
|||
|
||||
views.composerLayout.isGone = vectorPreferences.isRichTextEditorEnabled()
|
||||
views.richTextComposerLayout.isVisible = vectorPreferences.isRichTextEditorEnabled()
|
||||
views.richTextComposerLayout.setOnErrorListener(errorTracker::trackError)
|
||||
|
||||
messageComposerViewModel.observeViewEvents {
|
||||
when (it) {
|
||||
|
|
|
@ -49,10 +49,11 @@ import im.vector.app.databinding.ComposerRichTextLayoutBinding
|
|||
import im.vector.app.databinding.ViewRichTextMenuButtonBinding
|
||||
import io.element.android.wysiwyg.EditorEditText
|
||||
import io.element.android.wysiwyg.inputhandlers.models.InlineFormat
|
||||
import io.element.android.wysiwyg.utils.RustErrorCollector
|
||||
import uniffi.wysiwyg_composer.ActionState
|
||||
import uniffi.wysiwyg_composer.ComposerAction
|
||||
|
||||
class RichTextComposerLayout @JvmOverloads constructor(
|
||||
internal class RichTextComposerLayout @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
|
@ -248,10 +249,15 @@ class RichTextComposerLayout @JvmOverloads constructor(
|
|||
updateMenuStateFor(action, state)
|
||||
}
|
||||
}
|
||||
|
||||
updateEditTextVisibility()
|
||||
}
|
||||
|
||||
fun setOnErrorListener(onError: (e: RichTextEditorException) -> Unit) {
|
||||
views.richTextComposerEditText.rustErrorCollector = RustErrorCollector {
|
||||
onError(RichTextEditorException(it))
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateEditTextVisibility() {
|
||||
views.richTextComposerEditText.isVisible = isTextFormattingEnabled
|
||||
views.richTextMenu.isVisible = isTextFormattingEnabled
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.composer
|
||||
|
||||
internal class RichTextEditorException(
|
||||
cause: Throwable,
|
||||
) : Exception(cause)
|
|
@ -22,6 +22,7 @@ import android.os.Parcelable
|
|||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.extensions.startForegroundCompat
|
||||
import im.vector.app.core.services.VectorAndroidService
|
||||
import im.vector.app.features.location.LocationData
|
||||
import im.vector.app.features.location.LocationTracker
|
||||
|
@ -105,7 +106,7 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca
|
|||
if (foregroundModeStarted) {
|
||||
NotificationManagerCompat.from(this).notify(FOREGROUND_SERVICE_NOTIFICATION_ID, notification)
|
||||
} else {
|
||||
startForeground(FOREGROUND_SERVICE_NOTIFICATION_ID, notification)
|
||||
startForegroundCompat(FOREGROUND_SERVICE_NOTIFICATION_ID, notification)
|
||||
foregroundModeStarted = true
|
||||
}
|
||||
|
||||
|
|
|
@ -22,10 +22,13 @@ import androidx.preference.SeekBarPreference
|
|||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.core.preference.VectorPreference
|
||||
import im.vector.app.core.preference.VectorPreferenceCategory
|
||||
import im.vector.app.core.preference.VectorSwitchPreference
|
||||
import im.vector.app.features.analytics.plan.MobileScreen
|
||||
import im.vector.app.features.home.NightlyProxy
|
||||
import im.vector.app.features.rageshake.RageShake
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class VectorSettingsAdvancedSettingsFragment :
|
||||
|
@ -34,6 +37,8 @@ class VectorSettingsAdvancedSettingsFragment :
|
|||
override var titleRes = R.string.settings_advanced_settings
|
||||
override val preferenceXmlRes = R.xml.vector_settings_advanced_settings
|
||||
|
||||
@Inject lateinit var nightlyProxy: NightlyProxy
|
||||
|
||||
private var rageshake: RageShake? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
@ -57,6 +62,11 @@ class VectorSettingsAdvancedSettingsFragment :
|
|||
}
|
||||
|
||||
override fun bindPref() {
|
||||
setupRageShakeSection()
|
||||
setupNightlySection()
|
||||
}
|
||||
|
||||
private fun setupRageShakeSection() {
|
||||
val isRageShakeAvailable = RageShake.isAvailable(requireContext())
|
||||
|
||||
if (isRageShakeAvailable) {
|
||||
|
@ -86,4 +96,12 @@ class VectorSettingsAdvancedSettingsFragment :
|
|||
findPreference<VectorPreferenceCategory>("SETTINGS_RAGE_SHAKE_CATEGORY_KEY")!!.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupNightlySection() {
|
||||
findPreference<VectorPreferenceCategory>("SETTINGS_NIGHTLY_BUILD_PREFERENCE_KEY")?.isVisible = nightlyProxy.isNightlyBuild()
|
||||
findPreference<VectorPreference>("SETTINGS_NIGHTLY_BUILD_UPDATE_PREFERENCE_KEY")?.setOnPreferenceClickListener {
|
||||
nightlyProxy.updateApplication()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ import im.vector.app.R
|
|||
import im.vector.app.core.error.ErrorFormatter
|
||||
import im.vector.app.core.extensions.singletonEntryPoint
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.utils.toast
|
||||
import im.vector.app.features.analytics.AnalyticsTracker
|
||||
import im.vector.app.features.analytics.plan.MobileScreen
|
||||
|
@ -60,6 +62,19 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick
|
|||
protected lateinit var session: Session
|
||||
protected lateinit var errorFormatter: ErrorFormatter
|
||||
|
||||
/* ==========================================================================================
|
||||
* ViewEvents
|
||||
* ========================================================================================== */
|
||||
|
||||
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
|
||||
viewEvents
|
||||
.stream()
|
||||
.onEach {
|
||||
observer(it)
|
||||
}
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* Views
|
||||
* ========================================================================================== */
|
||||
|
@ -148,7 +163,7 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick
|
|||
}
|
||||
}
|
||||
|
||||
protected fun displayErrorDialog(throwable: Throwable) {
|
||||
protected fun displayErrorDialog(throwable: Throwable?) {
|
||||
displayErrorDialog(errorFormatter.toHumanReadable(throwable))
|
||||
}
|
||||
|
||||
|
|
|
@ -85,9 +85,9 @@ abstract class DeviceItem : VectorEpoxyModel<DeviceItem.Holder>(R.layout.item_de
|
|||
trusted
|
||||
)
|
||||
|
||||
holder.trustIcon.render(shield)
|
||||
holder.trustIcon.renderDeviceShield(shield)
|
||||
} else {
|
||||
holder.trustIcon.render(null)
|
||||
holder.trustIcon.renderDeviceShield(null)
|
||||
}
|
||||
|
||||
val detailedModeLabels = listOf(
|
||||
|
|
|
@ -28,7 +28,6 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
|
|||
import im.vector.app.features.auth.PendingAuthHandler
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
import im.vector.app.features.settings.devices.v2.signout.InterceptSignoutFlowResponseUseCase
|
||||
import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsReAuthNeeded
|
||||
import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsUseCase
|
||||
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
|
||||
|
@ -48,7 +47,6 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
private val refreshDevicesOnCryptoDevicesChangeUseCase: RefreshDevicesOnCryptoDevicesChangeUseCase,
|
||||
private val checkIfCurrentSessionCanBeVerifiedUseCase: CheckIfCurrentSessionCanBeVerifiedUseCase,
|
||||
private val signoutSessionsUseCase: SignoutSessionsUseCase,
|
||||
private val interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase,
|
||||
private val pendingAuthHandler: PendingAuthHandler,
|
||||
refreshDevicesUseCase: RefreshDevicesUseCase,
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
|
|
|
@ -51,7 +51,9 @@ import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationVie
|
|||
import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
|
||||
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
|
||||
import im.vector.app.features.settings.devices.v2.signout.BuildConfirmSignoutDialogUseCase
|
||||
import im.vector.app.features.workers.signout.SignOutUiWorker
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -98,6 +100,8 @@ class VectorSettingsDevicesFragment :
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
initWaitingView()
|
||||
initCurrentSessionHeaderView()
|
||||
initCurrentSessionView()
|
||||
initOtherSessionsHeaderView()
|
||||
initOtherSessionsView()
|
||||
initSecurityRecommendationsView()
|
||||
|
@ -139,6 +143,46 @@ class VectorSettingsDevicesFragment :
|
|||
views.waitingView.waitingStatusText.isVisible = true
|
||||
}
|
||||
|
||||
private fun initCurrentSessionHeaderView() {
|
||||
views.deviceListHeaderCurrentSession.setOnMenuItemClickListener { menuItem ->
|
||||
when (menuItem.itemId) {
|
||||
R.id.currentSessionHeaderRename -> {
|
||||
navigateToRenameCurrentSession()
|
||||
true
|
||||
}
|
||||
R.id.currentSessionHeaderSignout -> {
|
||||
confirmSignoutCurrentSession()
|
||||
true
|
||||
}
|
||||
R.id.currentSessionHeaderSignoutOtherSessions -> {
|
||||
confirmMultiSignoutOtherSessions()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToRenameCurrentSession() = withState(viewModel) { state ->
|
||||
val currentDeviceId = state.currentSessionCrossSigningInfo.deviceId
|
||||
if (currentDeviceId.isNotEmpty()) {
|
||||
viewNavigator.navigateToRenameSession(
|
||||
context = requireActivity(),
|
||||
deviceId = currentDeviceId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun confirmSignoutCurrentSession() {
|
||||
activity?.let { SignOutUiWorker(it).perform() }
|
||||
}
|
||||
|
||||
private fun initCurrentSessionView() {
|
||||
views.deviceListCurrentSession.viewVerifyButton.debouncedClicks {
|
||||
viewModel.handle(DevicesAction.VerifyCurrentSession)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initOtherSessionsHeaderView() {
|
||||
views.deviceListHeaderOtherSessions.setOnMenuItemClickListener { menuItem ->
|
||||
when (menuItem.itemId) {
|
||||
|
@ -247,7 +291,7 @@ class VectorSettingsDevicesFragment :
|
|||
val otherDevices = devices?.filter { it.deviceInfo.deviceId != currentDeviceId }
|
||||
|
||||
renderSecurityRecommendations(state.inactiveSessionsCount, state.unverifiedSessionsCount, isCurrentSessionVerified)
|
||||
renderCurrentDevice(currentDeviceInfo)
|
||||
renderCurrentSessionView(currentDeviceInfo, hasOtherDevices = otherDevices?.isNotEmpty().orFalse())
|
||||
renderOtherSessionsView(otherDevices, state.isShowingIpAddress)
|
||||
} else {
|
||||
hideSecurityRecommendations()
|
||||
|
@ -310,11 +354,11 @@ class VectorSettingsDevicesFragment :
|
|||
hideOtherSessionsView()
|
||||
} else {
|
||||
views.deviceListHeaderOtherSessions.isVisible = true
|
||||
val color = colorProvider.getColorFromAttribute(R.attr.colorError)
|
||||
val colorDestructive = colorProvider.getColorFromAttribute(R.attr.colorError)
|
||||
val multiSignoutItem = views.deviceListHeaderOtherSessions.menu.findItem(R.id.otherSessionsHeaderMultiSignout)
|
||||
val nbDevices = otherDevices.size
|
||||
multiSignoutItem.title = stringProvider.getQuantityString(R.plurals.device_manager_other_sessions_multi_signout_all, nbDevices, nbDevices)
|
||||
multiSignoutItem.setTextColor(color)
|
||||
multiSignoutItem.setTextColor(colorDestructive)
|
||||
views.deviceListOtherSessions.isVisible = true
|
||||
val devices = if (isShowingIpAddress) otherDevices else otherDevices.map { it.copy(deviceInfo = it.deviceInfo.copy(lastSeenIp = null)) }
|
||||
views.deviceListOtherSessions.render(
|
||||
|
@ -335,13 +379,30 @@ class VectorSettingsDevicesFragment :
|
|||
views.deviceListOtherSessions.isVisible = false
|
||||
}
|
||||
|
||||
private fun renderCurrentDevice(currentDeviceInfo: DeviceFullInfo?) {
|
||||
private fun renderCurrentSessionView(currentDeviceInfo: DeviceFullInfo?, hasOtherDevices: Boolean) {
|
||||
currentDeviceInfo?.let {
|
||||
renderCurrentSessionHeaderView(hasOtherDevices)
|
||||
renderCurrentSessionListView(it)
|
||||
} ?: run {
|
||||
hideCurrentSessionView()
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderCurrentSessionHeaderView(hasOtherDevices: Boolean) {
|
||||
views.deviceListHeaderCurrentSession.isVisible = true
|
||||
val colorDestructive = colorProvider.getColorFromAttribute(R.attr.colorError)
|
||||
val signoutSessionItem = views.deviceListHeaderCurrentSession.menu.findItem(R.id.currentSessionHeaderSignout)
|
||||
signoutSessionItem.setTextColor(colorDestructive)
|
||||
val signoutOtherSessionsItem = views.deviceListHeaderCurrentSession.menu.findItem(R.id.currentSessionHeaderSignoutOtherSessions)
|
||||
signoutOtherSessionsItem.setTextColor(colorDestructive)
|
||||
signoutOtherSessionsItem.isVisible = hasOtherDevices
|
||||
}
|
||||
|
||||
private fun renderCurrentSessionListView(currentDeviceInfo: DeviceFullInfo) {
|
||||
views.deviceListCurrentSession.isVisible = true
|
||||
val viewState = SessionInfoViewState(
|
||||
isCurrentSession = true,
|
||||
deviceFullInfo = it
|
||||
deviceFullInfo = currentDeviceInfo
|
||||
)
|
||||
views.deviceListCurrentSession.render(viewState, dateFormatter, drawableProvider, colorProvider, stringProvider)
|
||||
views.deviceListCurrentSession.debouncedClicks {
|
||||
|
@ -350,12 +411,6 @@ class VectorSettingsDevicesFragment :
|
|||
views.deviceListCurrentSession.viewDetailsButton.debouncedClicks {
|
||||
currentDeviceInfo.deviceInfo.deviceId?.let { deviceId -> navigateToSessionOverview(deviceId) }
|
||||
}
|
||||
views.deviceListCurrentSession.viewVerifyButton.debouncedClicks {
|
||||
viewModel.handle(DevicesAction.VerifyCurrentSession)
|
||||
}
|
||||
} ?: run {
|
||||
hideCurrentSessionView()
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToSessionOverview(deviceId: String) {
|
||||
|
|
|
@ -20,6 +20,7 @@ import android.content.Context
|
|||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
import im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsActivity
|
||||
import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity
|
||||
import im.vector.app.features.settings.devices.v2.rename.RenameSessionActivity
|
||||
import javax.inject.Inject
|
||||
|
||||
class VectorSettingsDevicesViewNavigator @Inject constructor() {
|
||||
|
@ -38,4 +39,8 @@ class VectorSettingsDevicesViewNavigator @Inject constructor() {
|
|||
OtherSessionsActivity.newIntent(context, titleResourceId, defaultFilter, excludeCurrentDevice)
|
||||
)
|
||||
}
|
||||
|
||||
fun navigateToRenameSession(context: Context, deviceId: String) {
|
||||
context.startActivity(RenameSessionActivity.newIntent(context, deviceId))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.la
|
|||
} else {
|
||||
setDeviceTypeIconUseCase.execute(deviceType, holder.otherSessionDeviceTypeImageView, stringProvider)
|
||||
}
|
||||
holder.otherSessionVerificationStatusImageView.render(roomEncryptionTrustLevel)
|
||||
holder.otherSessionVerificationStatusImageView.renderDeviceShield(roomEncryptionTrustLevel)
|
||||
holder.otherSessionNameTextView.text = sessionName
|
||||
holder.otherSessionDescriptionTextView.text = sessionDescription
|
||||
sessionDescriptionColor?.let {
|
||||
|
|
|
@ -90,7 +90,7 @@ class SessionInfoView @JvmOverloads constructor(
|
|||
hasLearnMoreLink: Boolean,
|
||||
isVerifyButtonVisible: Boolean,
|
||||
) {
|
||||
views.sessionInfoVerificationStatusImageView.render(encryptionTrustLevel)
|
||||
views.sessionInfoVerificationStatusImageView.renderDeviceShield(encryptionTrustLevel)
|
||||
when {
|
||||
encryptionTrustLevel == RoomEncryptionTrustLevel.Trusted -> renderCrossSigningVerified(isCurrentSession)
|
||||
encryptionTrustLevel == RoomEncryptionTrustLevel.Default && !isCurrentSession -> renderCrossSigningUnknown()
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.settings.devices.v2.notification
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import javax.inject.Inject
|
||||
|
||||
class CanToggleNotificationsViaAccountDataUseCase @Inject constructor(
|
||||
private val getNotificationSettingsAccountDataUpdatesUseCase: GetNotificationSettingsAccountDataUpdatesUseCase,
|
||||
) {
|
||||
|
||||
fun execute(session: Session, deviceId: String): Flow<Boolean> {
|
||||
return getNotificationSettingsAccountDataUpdatesUseCase.execute(session, deviceId)
|
||||
.map { it?.isSilenced != null }
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ import org.matrix.android.sdk.api.session.Session
|
|||
import org.matrix.android.sdk.flow.unwrap
|
||||
import javax.inject.Inject
|
||||
|
||||
class CanTogglePushNotificationsViaPusherUseCase @Inject constructor() {
|
||||
class CanToggleNotificationsViaPusherUseCase @Inject constructor() {
|
||||
|
||||
fun execute(session: Session): Flow<Boolean> {
|
||||
return session
|
|
@ -17,14 +17,13 @@
|
|||
package im.vector.app.features.settings.devices.v2.notification
|
||||
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
|
||||
import javax.inject.Inject
|
||||
|
||||
class CheckIfCanTogglePushNotificationsViaAccountDataUseCase @Inject constructor() {
|
||||
class CheckIfCanToggleNotificationsViaAccountDataUseCase @Inject constructor(
|
||||
private val getNotificationSettingsAccountDataUseCase: GetNotificationSettingsAccountDataUseCase,
|
||||
) {
|
||||
|
||||
fun execute(session: Session, deviceId: String): Boolean {
|
||||
return session
|
||||
.accountDataService()
|
||||
.getUserAccountDataEvent(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId) != null
|
||||
return getNotificationSettingsAccountDataUseCase.execute(session, deviceId)?.isSilenced != null
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ package im.vector.app.features.settings.devices.v2.notification
|
|||
import org.matrix.android.sdk.api.session.Session
|
||||
import javax.inject.Inject
|
||||
|
||||
class CheckIfCanTogglePushNotificationsViaPusherUseCase @Inject constructor() {
|
||||
class CheckIfCanToggleNotificationsViaPusherUseCase @Inject constructor() {
|
||||
|
||||
fun execute(session: Session): Boolean {
|
||||
return session
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.settings.devices.v2.notification
|
||||
|
||||
import org.matrix.android.sdk.api.account.LocalNotificationSettingsContent
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Delete the content of any associated notification settings to the current session.
|
||||
*/
|
||||
class DeleteNotificationSettingsAccountDataUseCase @Inject constructor(
|
||||
private val getNotificationSettingsAccountDataUseCase: GetNotificationSettingsAccountDataUseCase,
|
||||
private val setNotificationSettingsAccountDataUseCase: SetNotificationSettingsAccountDataUseCase,
|
||||
) {
|
||||
|
||||
suspend fun execute(session: Session) {
|
||||
val deviceId = session.sessionParams.deviceId ?: return
|
||||
if (getNotificationSettingsAccountDataUseCase.execute(session, deviceId)?.isSilenced != null) {
|
||||
val emptyNotificationSettingsContent = LocalNotificationSettingsContent(
|
||||
isSilenced = null
|
||||
)
|
||||
setNotificationSettingsAccountDataUseCase.execute(session, deviceId, emptyNotificationSettingsContent)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.settings.devices.v2.notification
|
||||
|
||||
import androidx.lifecycle.asFlow
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.matrix.android.sdk.api.account.LocalNotificationSettingsContent
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetNotificationSettingsAccountDataUpdatesUseCase @Inject constructor() {
|
||||
|
||||
fun execute(session: Session, deviceId: String): Flow<LocalNotificationSettingsContent?> {
|
||||
return session
|
||||
.accountDataService()
|
||||
.getLiveUserAccountDataEvent(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId)
|
||||
.asFlow()
|
||||
.map { it.getOrNull()?.content?.toModel<LocalNotificationSettingsContent>() }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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.settings.devices.v2.notification
|
||||
|
||||
import org.matrix.android.sdk.api.account.LocalNotificationSettingsContent
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetNotificationSettingsAccountDataUseCase @Inject constructor() {
|
||||
|
||||
fun execute(session: Session, deviceId: String): LocalNotificationSettingsContent? {
|
||||
return session
|
||||
.accountDataService()
|
||||
.getUserAccountDataEvent(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId)
|
||||
?.content
|
||||
.toModel<LocalNotificationSettingsContent>()
|
||||
}
|
||||
}
|
|
@ -30,33 +30,47 @@ import org.matrix.android.sdk.flow.unwrap
|
|||
import javax.inject.Inject
|
||||
|
||||
class GetNotificationsStatusUseCase @Inject constructor(
|
||||
private val canTogglePushNotificationsViaPusherUseCase: CanTogglePushNotificationsViaPusherUseCase,
|
||||
private val checkIfCanTogglePushNotificationsViaAccountDataUseCase: CheckIfCanTogglePushNotificationsViaAccountDataUseCase,
|
||||
private val canToggleNotificationsViaPusherUseCase: CanToggleNotificationsViaPusherUseCase,
|
||||
private val canToggleNotificationsViaAccountDataUseCase: CanToggleNotificationsViaAccountDataUseCase,
|
||||
) {
|
||||
|
||||
fun execute(session: Session, deviceId: String): Flow<NotificationsStatus> {
|
||||
return when {
|
||||
checkIfCanTogglePushNotificationsViaAccountDataUseCase.execute(session, deviceId) -> {
|
||||
return canToggleNotificationsViaAccountDataUseCase.execute(session, deviceId)
|
||||
.flatMapLatest { canToggle ->
|
||||
if (canToggle) {
|
||||
notificationStatusFromAccountData(session, deviceId)
|
||||
} else {
|
||||
notificationStatusFromPusher(session, deviceId)
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun notificationStatusFromAccountData(session: Session, deviceId: String) =
|
||||
session.flow()
|
||||
.liveUserAccountData(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId)
|
||||
.unwrap()
|
||||
.map { it.content.toModel<LocalNotificationSettingsContent>()?.isSilenced?.not() }
|
||||
.map { if (it == true) NotificationsStatus.ENABLED else NotificationsStatus.DISABLED }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
else -> canTogglePushNotificationsViaPusherUseCase.execute(session)
|
||||
|
||||
private fun notificationStatusFromPusher(session: Session, deviceId: String) =
|
||||
canToggleNotificationsViaPusherUseCase.execute(session)
|
||||
.flatMapLatest { canToggle ->
|
||||
if (canToggle) {
|
||||
session.flow()
|
||||
.livePushers()
|
||||
.map { it.filter { pusher -> pusher.deviceId == deviceId } }
|
||||
.map { it.takeIf { it.isNotEmpty() }?.any { pusher -> pusher.enabled } }
|
||||
.map { if (it == true) NotificationsStatus.ENABLED else NotificationsStatus.DISABLED }
|
||||
.map {
|
||||
when (it) {
|
||||
true -> NotificationsStatus.ENABLED
|
||||
false -> NotificationsStatus.DISABLED
|
||||
else -> NotificationsStatus.NOT_SUPPORTED
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
} else {
|
||||
flowOf(NotificationsStatus.NOT_SUPPORTED)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue