Merge remote-tracking branch 'upstream/develop' into sc
Change-Id: Ifed09540802774e7b1d3f2ab787a7f42f1030b28 Conflicts: vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt
This commit is contained in:
commit
87100a8536
|
@ -11,7 +11,7 @@ jobs:
|
||||||
- run: |
|
- run: |
|
||||||
npm install --save-dev @babel/plugin-transform-flow-strip-types
|
npm install --save-dev @babel/plugin-transform-flow-strip-types
|
||||||
- name: Danger
|
- name: Danger
|
||||||
uses: danger/danger-js@11.1.4
|
uses: danger/danger-js@11.2.0
|
||||||
with:
|
with:
|
||||||
args: "--dangerfile tools/danger/dangerfile.js"
|
args: "--dangerfile tools/danger/dangerfile.js"
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -66,7 +66,7 @@ jobs:
|
||||||
yarn add danger-plugin-lint-report --dev
|
yarn add danger-plugin-lint-report --dev
|
||||||
- name: Danger lint
|
- name: Danger lint
|
||||||
if: always()
|
if: always()
|
||||||
uses: danger/danger-js@11.1.4
|
uses: danger/danger-js@11.2.0
|
||||||
with:
|
with:
|
||||||
args: "--dangerfile tools/danger/dangerfile-lint.js"
|
args: "--dangerfile tools/danger/dangerfile-lint.js"
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -17,7 +17,8 @@ jobs:
|
||||||
contains(github.event.issue.labels.*.name, 'Z-IA') ||
|
contains(github.event.issue.labels.*.name, 'Z-IA') ||
|
||||||
contains(github.event.issue.labels.*.name, 'A-Themes-Custom') ||
|
contains(github.event.issue.labels.*.name, 'A-Themes-Custom') ||
|
||||||
contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') ||
|
contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') ||
|
||||||
contains(github.event.issue.labels.*.name, 'A-Tags')
|
contains(github.event.issue.labels.*.name, 'A-Tags') ||
|
||||||
|
contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v5
|
- uses: actions/github-script@v5
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -45,10 +45,10 @@ plugins {
|
||||||
// Detekt
|
// Detekt
|
||||||
id "io.gitlab.arturbosch.detekt" version "1.22.0"
|
id "io.gitlab.arturbosch.detekt" version "1.22.0"
|
||||||
// Ksp
|
// 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
|
// Dependency Analysis
|
||||||
id 'com.autonomousapps.dependency-analysis' version "1.16.0"
|
id 'com.autonomousapps.dependency-analysis' version "1.17.0"
|
||||||
// Gradle doctor
|
// Gradle doctor
|
||||||
id "com.osacky.doctor" version "0.8.1"
|
id "com.osacky.doctor" version "0.8.1"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Fix bad pills color background. For light and dark theme the color is now 61708B (iso EleWeb)
|
|
@ -0,0 +1 @@
|
||||||
|
Add Z-Labs label for rich text editor and migrate to new label naming.
|
|
@ -0,0 +1 @@
|
||||||
|
Save m.local_notification_settings.<device-id> event in account_data
|
|
@ -0,0 +1 @@
|
||||||
|
Update notifications setting when m.local_notification_settings.<device-id> event changes for current device
|
|
@ -0,0 +1 @@
|
||||||
|
ANR when asking to select the notification method
|
|
@ -0,0 +1 @@
|
||||||
|
[Rich text editor] Fix keyboard closing after collapsing editor
|
|
@ -0,0 +1,3 @@
|
||||||
|
Rich Text Editor: fix several issues related to insets:
|
||||||
|
* Empty space displayed at the bottom when you don't have permissions to send messages into a room.
|
||||||
|
* Wrong insets being kept when you exit the room screen and the keyboard is displayed, then come back to it.
|
|
@ -0,0 +1,2 @@
|
||||||
|
Fix crash in message composer when room is missing
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Fix crash when invalid homeserver url is entered.
|
|
@ -0,0 +1 @@
|
||||||
|
[Session manager] Add action to signout all the other session
|
|
@ -0,0 +1 @@
|
||||||
|
Remind unverified sessions with a banner once a week
|
|
@ -0,0 +1 @@
|
||||||
|
Fix usage of unknown shield in room summary
|
|
@ -0,0 +1 @@
|
||||||
|
Fix crash when the network is not available.
|
|
@ -17,7 +17,7 @@ def markwon = "4.6.2"
|
||||||
def moshi = "1.14.0"
|
def moshi = "1.14.0"
|
||||||
def lifecycle = "2.5.1"
|
def lifecycle = "2.5.1"
|
||||||
def flowBinding = "1.2.0"
|
def flowBinding = "1.2.0"
|
||||||
def flipper = "0.174.0"
|
def flipper = "0.176.0"
|
||||||
def epoxy = "5.0.0"
|
def epoxy = "5.0.0"
|
||||||
def mavericks = "3.0.1"
|
def mavericks = "3.0.1"
|
||||||
def glide = "4.14.2"
|
def glide = "4.14.2"
|
||||||
|
@ -26,7 +26,7 @@ def jjwt = "0.11.5"
|
||||||
// Temporary version to unblock #6929. Once 0.16.0 is released we should use it, and revert
|
// Temporary version to unblock #6929. Once 0.16.0 is released we should use it, and revert
|
||||||
// the whole commit which set version 0.16.0-SNAPSHOT
|
// the whole commit which set version 0.16.0-SNAPSHOT
|
||||||
def vanniktechEmoji = "0.16.0-SNAPSHOT"
|
def vanniktechEmoji = "0.16.0-SNAPSHOT"
|
||||||
def sentry = "6.7.0"
|
def sentry = "6.9.0"
|
||||||
def fragment = "1.5.4"
|
def fragment = "1.5.4"
|
||||||
// Testing
|
// Testing
|
||||||
def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819
|
def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819
|
||||||
|
@ -99,7 +99,7 @@ ext.libs = [
|
||||||
],
|
],
|
||||||
element : [
|
element : [
|
||||||
'opusencoder' : "io.element.android:opusencoder:1.1.0",
|
'opusencoder' : "io.element.android:opusencoder:1.1.0",
|
||||||
'wysiwyg' : "io.element.android:wysiwyg:0.7.0.1"
|
'wysiwyg' : "io.element.android:wysiwyg:0.8.0"
|
||||||
],
|
],
|
||||||
squareup : [
|
squareup : [
|
||||||
'moshi' : "com.squareup.moshi:moshi:$moshi",
|
'moshi' : "com.squareup.moshi:moshi:$moshi",
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
||||||
|
Основні зміни в цій версії: Нова реалізація повноекранного режиму для редактора розширеного тексту та виправлення помилок.
|
||||||
|
Перелік усіх змін: https://github.com/vector-im/element-android/releases
|
|
@ -0,0 +1,2 @@
|
||||||
|
此版本中的主要變動:格式化文字編輯器的全螢幕模式新實作與臭蟲修復。
|
||||||
|
完整的變更紀錄:https://github.com/vector-im/element-android/releases
|
Binary file not shown.
|
@ -1,6 +1,7 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=db9c8211ed63f61f60292c69e80d89196f9eb36665e369e7f00ac4cc841c2219
|
distributionSha256Sum=312eb12875e1747e05c2f81a4789902d7e4ec5defbd1eefeaccc08acf096505d
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
|
||||||
|
networkTimeout=10000
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
@ -80,10 +80,10 @@ do
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
@ -143,12 +143,16 @@ fi
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC3045
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
'' | soft) :;; #(
|
'' | soft) :;; #(
|
||||||
*)
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC3045
|
||||||
ulimit -n "$MAX_FD" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
|
|
|
@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
|
|
@ -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="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_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="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="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="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>
|
<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.
|
<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
|
\n
|
||||||
\nPro zobrazování oznámení povolte přístup na dalších vyskakovacích oknech.</string>
|
\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_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 rozšířený textový editor</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_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_confirm_security_code">Potvrdit</string>
|
||||||
<string name="qr_code_login_try_again">Zkuste to znovu</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_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_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="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_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_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>
|
<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="quoting">Citace</string>
|
||||||
<string name="replying_to">Odpovídám na %s</string>
|
<string name="replying_to">Odpovídám na %s</string>
|
||||||
<string name="editing">Úpravy</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>
|
</resources>
|
|
@ -2815,7 +2815,7 @@
|
||||||
<string name="qr_code_login_header_failed_other_description">Die Anfrage ist fehlgeschlagen.</string>
|
<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_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_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_pause_voice_broadcast">Pausiere Sprachübertragung</string>
|
||||||
<string name="a11y_stop_voice_broadcast_record">Stoppe Aufzeichnung der 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>
|
<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="replying_to">%s antworten</string>
|
||||||
<string name="device_manager_other_sessions_hide_ip_address">IP-Adresse ausblenden</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="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>
|
</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_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_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="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_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_stop_voice_broadcast_record">Lõpeta ringhäälingukõne salvestamine</string>
|
||||||
<string name="a11y_pause_voice_broadcast_record">Peata 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_video">saatis video.</string>
|
||||||
<string name="message_reply_to_sender_sent_sticker">saatis kleepsu.</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="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>
|
</resources>
|
|
@ -943,7 +943,7 @@
|
||||||
\n
|
\n
|
||||||
\nپیامهایتان با قفلهایی امن شدهاند و فقط شما و گیرندگان دیگر، کلیدهای یکتا را برای قفلگشاییشان دارید.</string>
|
\nپیامهایتان با قفلهایی امن شدهاند و فقط شما و گیرندگان دیگر، کلیدهای یکتا را برای قفلگشاییشان دارید.</string>
|
||||||
<string name="room_profile_section_security">امنیت</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_more">بیشتر</string>
|
||||||
<string name="room_profile_section_admin">کنشهای مدیر</string>
|
<string name="room_profile_section_admin">کنشهای مدیر</string>
|
||||||
<string name="room_profile_section_more_settings">تنظمیات اتاق</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_poll">نظرسنجیها</string>
|
||||||
<string name="attachment_type_selector_file">پیوستها</string>
|
<string name="attachment_type_selector_file">پیوستها</string>
|
||||||
<string name="attachment_type_selector_sticker">برچسبها</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="voice_broadcast_live">زنده</string>
|
||||||
<string name="qr_code_login_confirm_security_code">تأیید</string>
|
<string name="qr_code_login_confirm_security_code">تأیید</string>
|
||||||
<string name="three">۳</string>
|
<string name="three">۳</string>
|
||||||
|
@ -2844,4 +2844,9 @@
|
||||||
<string name="quoting">نقل کردن</string>
|
<string name="quoting">نقل کردن</string>
|
||||||
<string name="replying_to">پاسخ دادن به %s</string>
|
<string name="replying_to">پاسخ دادن به %s</string>
|
||||||
<string name="editing">ویرایش کردن</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>
|
</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_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="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="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_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_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>
|
<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="quoting">Citation de</string>
|
||||||
<string name="replying_to">Réponse à %s</string>
|
<string name="replying_to">Réponse à %s</string>
|
||||||
<string name="editing">Modification</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>
|
</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="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_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="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_pause_voice_broadcast">Jeda siaran suara</string>
|
||||||
<string name="a11y_play_voice_broadcast">Mainkan atau lanjutkan 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>
|
<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="editing">Mengedit</string>
|
||||||
<string name="device_manager_other_sessions_show_ip_address">Tampilkan alamat IP</string>
|
<string name="device_manager_other_sessions_show_ip_address">Tampilkan alamat IP</string>
|
||||||
<string name="replying_to">Membalas ke %s</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>
|
</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_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_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="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_pause_voice_broadcast">Sospendi trasmissione vocale</string>
|
||||||
<string name="a11y_play_voice_broadcast">Avvia o riprendi 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>
|
<string name="a11y_stop_voice_broadcast_record">Ferma registrazione trasmissione vocale</string>
|
||||||
|
@ -2857,4 +2857,6 @@
|
||||||
<string name="quoting">Citazione</string>
|
<string name="quoting">Citazione</string>
|
||||||
<string name="replying_to">Risposta a %s</string>
|
<string name="replying_to">Risposta a %s</string>
|
||||||
<string name="editing">Modifica</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>
|
</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_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.
|
<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
|
\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_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_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>
|
<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="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_forward">Avançar rápido 30 segundos</string>
|
||||||
<string name="a11y_voice_broadcast_fast_backward">Retroceder 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
|
\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">
|
<plurals name="device_manager_other_sessions_multi_signout_all">
|
||||||
<item quantity="one">Fazer signout de %1$d sessão</item>
|
<item quantity="one">Fazer signout de %1$d sessão</item>
|
||||||
<item quantity="other">Fazer signout de %1$d sessões</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="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_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="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_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_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>
|
<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="device_manager_other_sessions_show_ip_address">Zobraziť IP adresu</string>
|
||||||
<string name="replying_to">Odpoveď na %s</string>
|
<string name="replying_to">Odpoveď na %s</string>
|
||||||
<string name="editing">Úprava</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>
|
</resources>
|
|
@ -2659,7 +2659,7 @@
|
||||||
\nKy shërbyes Home mund të mos jetë formësuar të shfaqë harta.</string>
|
\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="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="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_pause_voice_broadcast">Ndal transmetim zanor</string>
|
||||||
<string name="a11y_play_voice_broadcast">Luani ose vazhdoni luajtje transmetimi 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>
|
<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="a11y_voice_broadcast_fast_backward">Kthim prapa 30 sekonda</string>
|
||||||
<string name="replying_to">Si përgjigje për %s</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="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>
|
</resources>
|
|
@ -2852,4 +2852,18 @@
|
||||||
<string name="error_voice_broadcast_unauthorized_title">Kan inte starta en ny röstsändning</string>
|
<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_forward">Spola framåt 30 sekunder</string>
|
||||||
<string name="a11y_voice_broadcast_fast_backward">Spola tillbaka 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>
|
</resources>
|
|
@ -2922,7 +2922,7 @@
|
||||||
<string name="qr_code_login_header_failed_other_description">Запит не виконаний.</string>
|
<string name="qr_code_login_header_failed_other_description">Запит не виконаний.</string>
|
||||||
<string name="labs_enable_voice_broadcast_summary">Можливість записувати та надсилати голосові трансляції до стрічки кімнати.</string>
|
<string name="labs_enable_voice_broadcast_summary">Можливість записувати та надсилати голосові трансляції до стрічки кімнати.</string>
|
||||||
<string name="labs_enable_voice_broadcast_title">Увімкнути голосові трансляції (в активній розробці)</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_pause_voice_broadcast">Призупинити голосову трансляцію</string>
|
||||||
<string name="a11y_play_voice_broadcast">Відтворити або поновити відтворення голосової трансляції</string>
|
<string name="a11y_play_voice_broadcast">Відтворити або поновити відтворення голосової трансляції</string>
|
||||||
<string name="a11y_stop_voice_broadcast_record">Припинити запис голосової трансляції</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="device_manager_other_sessions_multi_signout_selection">Вийти</string>
|
||||||
<string name="voice_broadcast_recording_time_left">Залишилося %1$s</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_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="message_reply_to_prefix">У відповідь на</string>
|
||||||
<string name="device_manager_other_sessions_hide_ip_address">Сховати IP-адресу</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_created_poll">створює опитування.</string>
|
||||||
<string name="message_reply_to_sender_sent_sticker">відправив наліпку.</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_video">надсилає відео.</string>
|
||||||
<string name="message_reply_to_sender_sent_image">відправив зображення.</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_voice_message">надсилає голосове повідомлення.</string>
|
||||||
<string name="device_manager_other_sessions_show_ip_address">Показати IP-адресу</string>
|
<string name="device_manager_other_sessions_show_ip_address">Показати IP-адресу</string>
|
||||||
<string name="quoting">Цитуючи</string>
|
<string name="quoting">Цитуючи</string>
|
||||||
<string name="replying_to">У відповідь на %s</string>
|
<string name="replying_to">У відповідь %s</string>
|
||||||
<string name="editing">Редагування</string>
|
<string name="editing">Редагування</string>
|
||||||
|
<string name="settings_enable_direct_share_summary">Показувати останні бесіди в системному меню загального доступу</string>
|
||||||
|
<string name="settings_enable_direct_share_title">Увімкнути пряме поширення</string>
|
||||||
</resources>
|
</resources>
|
|
@ -1007,7 +1007,7 @@
|
||||||
<string name="settings_discovery_disconnect_with_bound_pid">您当前在身份服务器 %1$s 上共享电子邮件地址或电话号码。您需要重新连接到 %2$s 才能停止共享它们。</string>
|
<string name="settings_discovery_disconnect_with_bound_pid">您当前在身份服务器 %1$s 上共享电子邮件地址或电话号码。您需要重新连接到 %2$s 才能停止共享它们。</string>
|
||||||
<string name="settings_agree_to_terms">同意身份服务器 (%s) 服务条款使你可以通过电子邮件地址或电话号码被发现。</string>
|
<string name="settings_agree_to_terms">同意身份服务器 (%s) 服务条款使你可以通过电子邮件地址或电话号码被发现。</string>
|
||||||
<string name="labs_allow_extended_logging">启用详细日志。</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_terms_not_accepted">接收你的主服务器条款和条件后请重试。</string>
|
||||||
<string name="error_network_timeout">服务器似乎响应时间太长,这可能是由于连接不良或服务器错误引起的。请稍后再试。</string>
|
<string name="error_network_timeout">服务器似乎响应时间太长,这可能是由于连接不良或服务器错误引起的。请稍后再试。</string>
|
||||||
<string name="send_attachment">发送附件</string>
|
<string name="send_attachment">发送附件</string>
|
||||||
|
@ -1205,7 +1205,7 @@
|
||||||
<string name="settings_advanced_settings">高级设置</string>
|
<string name="settings_advanced_settings">高级设置</string>
|
||||||
<string name="settings_developer_mode">开发者模式</string>
|
<string name="settings_developer_mode">开发者模式</string>
|
||||||
<string name="settings_developer_mode_summary">开发者模式激活隐藏的功能,也可能使应用不稳定。仅供开发者使用!</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">检测阈值</string>
|
||||||
<string name="settings_rageshake_detection_threshold_summary">摇动手机以测试检测阈值</string>
|
<string name="settings_rageshake_detection_threshold_summary">摇动手机以测试检测阈值</string>
|
||||||
<string name="rageshake_detected">检测到摇动!</string>
|
<string name="rageshake_detected">检测到摇动!</string>
|
||||||
|
@ -1213,7 +1213,7 @@
|
||||||
<string name="devices_current_device">当前会话</string>
|
<string name="devices_current_device">当前会话</string>
|
||||||
<string name="devices_other_devices">其它会话</string>
|
<string name="devices_other_devices">其它会话</string>
|
||||||
<string name="autocomplete_limited_results">仅显示第一个结果,请输入更多字符…</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="settings_developer_mode_fail_fast_summary">发生意外错误时,${app_name} 可能更经常崩溃</string>
|
||||||
<string name="command_description_shrug">在明文消息前添加 ¯\\_(ツ)_/¯</string>
|
<string name="command_description_shrug">在明文消息前添加 ¯\\_(ツ)_/¯</string>
|
||||||
<string name="create_room_encryption_title">启用加密</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_detail_other_session_unknown">验证您当前的会话以显示此会话的验证状态。</string>
|
||||||
<string name="device_manager_verification_status_unknown">未知的验证状态</string>
|
<string name="device_manager_verification_status_unknown">未知的验证状态</string>
|
||||||
<string name="tooltip_attachment_voice_broadcast">开始语音广播</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="a11y_pause_voice_broadcast">暂停语音广播</string>
|
||||||
<string name="voice_broadcast_live">实时</string>
|
<string name="voice_broadcast_live">实时</string>
|
||||||
<string name="action_got_it">知道了</string>
|
<string name="action_got_it">知道了</string>
|
||||||
|
@ -2789,4 +2789,19 @@
|
||||||
<plurals name="x_selected">
|
<plurals name="x_selected">
|
||||||
<item quantity="other">已选择 %1$d</item>
|
<item quantity="other">已选择 %1$d</item>
|
||||||
</plurals>
|
</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>
|
</resources>
|
|
@ -2760,7 +2760,7 @@
|
||||||
<string name="qr_code_login_header_failed_other_description">請求失敗。</string>
|
<string name="qr_code_login_header_failed_other_description">請求失敗。</string>
|
||||||
<string name="labs_enable_voice_broadcast_summary">可以在聊天室時間軸中錄製並傳送語音廣播。</string>
|
<string name="labs_enable_voice_broadcast_summary">可以在聊天室時間軸中錄製並傳送語音廣播。</string>
|
||||||
<string name="labs_enable_voice_broadcast_title">啟用語音廣播(正在積極開發中)</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_pause_voice_broadcast">暫停語音廣播</string>
|
||||||
<string name="a11y_play_voice_broadcast">播放或繼續語音廣播</string>
|
<string name="a11y_play_voice_broadcast">播放或繼續語音廣播</string>
|
||||||
<string name="a11y_stop_voice_broadcast_record">停止語音廣播錄製</string>
|
<string name="a11y_stop_voice_broadcast_record">停止語音廣播錄製</string>
|
||||||
|
@ -2810,4 +2810,6 @@
|
||||||
<string name="quoting">引用</string>
|
<string name="quoting">引用</string>
|
||||||
<string name="replying_to">回覆給 %s</string>
|
<string name="replying_to">回覆給 %s</string>
|
||||||
<string name="editing">正在編輯</string>
|
<string name="editing">正在編輯</string>
|
||||||
|
<string name="settings_enable_direct_share_summary">在系統分享選單中顯示最近聊天</string>
|
||||||
|
<string name="settings_enable_direct_share_title">啟用直接分享</string>
|
||||||
</resources>
|
</resources>
|
|
@ -2649,8 +2649,12 @@
|
||||||
<string name="unencrypted">Unencrypted</string>
|
<string name="unencrypted">Unencrypted</string>
|
||||||
<string name="encrypted_unverified">Encrypted by an unverified device</string>
|
<string name="encrypted_unverified">Encrypted by an unverified device</string>
|
||||||
<string name="key_authenticity_not_guaranteed">The authenticity of this encrypted message can\'t be guaranteed on this device.</string>
|
<string name="key_authenticity_not_guaranteed">The authenticity of this encrypted message can\'t be guaranteed on this device.</string>
|
||||||
<string name="review_logins">Review where you’re logged in</string>
|
<!-- TODO TO BE REMOVED -->
|
||||||
<string name="verify_other_sessions">Verify all your sessions to ensure your account & messages are safe</string>
|
<string name="review_logins" tools:ignore="UnusedResources">Review where you’re logged in</string>
|
||||||
|
<!-- TODO TO BE REMOVED -->
|
||||||
|
<string name="verify_other_sessions" tools:ignore="UnusedResources">Verify all your sessions to ensure your account & messages are safe</string>
|
||||||
|
<string name="review_unverified_sessions_title">You have unverified sessions</string>
|
||||||
|
<string name="review_unverified_sessions_description">Review to ensure your account is safe</string>
|
||||||
<!-- Argument will be replaced by the other session name (e.g, Desktop, mobile) -->
|
<!-- Argument will be replaced by the other session name (e.g, Desktop, mobile) -->
|
||||||
<string name="verify_this_session">Verify the new login accessing your account: %1$s</string>
|
<string name="verify_this_session">Verify the new login accessing your account: %1$s</string>
|
||||||
|
|
||||||
|
@ -3359,6 +3363,7 @@
|
||||||
<item quantity="one">Sign out of %1$d session</item>
|
<item quantity="one">Sign out of %1$d session</item>
|
||||||
<item quantity="other">Sign out of %1$d sessions</item>
|
<item quantity="other">Sign out of %1$d sessions</item>
|
||||||
</plurals>
|
</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_show_ip_address">Show IP address</string>
|
||||||
<string name="device_manager_other_sessions_hide_ip_address">Hide 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>
|
<string name="device_manager_session_overview_signout">Sign out of this session</string>
|
||||||
|
|
|
@ -44,4 +44,4 @@
|
||||||
<color name="palette_black_800">#15191E</color>
|
<color name="palette_black_800">#15191E</color>
|
||||||
<color name="palette_black_950">#21262C</color>
|
<color name="palette_black_950">#21262C</color>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
<item name="vctr_list_separator">?vctr_content_quinary</item>
|
<item name="vctr_list_separator">?vctr_content_quinary</item>
|
||||||
<item name="vctr_list_separator_system">?vctr_system</item>
|
<item name="vctr_list_separator_system">?vctr_system</item>
|
||||||
<item name="vctr_list_separator_on_surface">?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 -->
|
<!-- Material color -->
|
||||||
<item name="colorPrimary">@color/element_accent_dark</item>
|
<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">?vctr_content_quinary</item>
|
||||||
<item name="vctr_list_separator_system">?vctr_system</item>
|
<item name="vctr_list_separator_system">?vctr_system</item>
|
||||||
<item name="vctr_list_separator_on_surface">?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 -->
|
<!-- Material color -->
|
||||||
<item name="colorPrimary">@color/element_accent_light</item>
|
<item name="colorPrimary">@color/element_accent_light</item>
|
||||||
|
|
|
@ -62,7 +62,7 @@ android {
|
||||||
// that the app's state is completely cleared between tests.
|
// that the app's state is completely cleared between tests.
|
||||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||||
|
|
||||||
buildConfigField "String", "SDK_VERSION", "\"1.5.11\""
|
buildConfigField "String", "SDK_VERSION", "\"1.5.12\""
|
||||||
|
|
||||||
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
||||||
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
||||||
|
|
|
@ -21,5 +21,6 @@ import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class LocalNotificationSettingsContent(
|
data class LocalNotificationSettingsContent(
|
||||||
@Json(name = "is_silenced") val isSilenced: Boolean = false
|
@Json(name = "is_silenced")
|
||||||
|
val isSilenced: Boolean?
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,43 +19,81 @@
|
||||||
# Ignore any error to not stop the script
|
# Ignore any error to not stop the script
|
||||||
set +e
|
set +e
|
||||||
|
|
||||||
printf "\n"
|
printf "\n================================================================================\n"
|
||||||
printf "================================================================================\n"
|
|
||||||
printf "| Welcome to the release script! |\n"
|
printf "| Welcome to the release script! |\n"
|
||||||
printf "================================================================================\n"
|
printf "================================================================================\n"
|
||||||
|
|
||||||
releaseScriptLocation="${RELEASE_SCRIPT_PATH}"
|
printf "Checking environment...\n"
|
||||||
|
envError=0
|
||||||
|
|
||||||
if [[ -z "${releaseScriptLocation}" ]]; then
|
# Path of the key store (it's a file)
|
||||||
printf "Fatal: RELEASE_SCRIPT_PATH is not defined in the environment. Please set to the path of your local file 'releaseElement2.sh'.\n"
|
keyStorePath="${ELEMENT_KEYSTORE_PATH}"
|
||||||
exit 1
|
if [[ -z "${keyStorePath}" ]]; then
|
||||||
|
printf "Fatal: ELEMENT_KEYSTORE_PATH is not defined in the environment.\n"
|
||||||
|
envError=1
|
||||||
|
fi
|
||||||
|
# Keystore password
|
||||||
|
keyStorePassword="${ELEMENT_KEYSTORE_PASSWORD}"
|
||||||
|
if [[ -z "${keyStorePassword}" ]]; then
|
||||||
|
printf "Fatal: ELEMENT_KEYSTORE_PASSWORD is not defined in the environment.\n"
|
||||||
|
envError=1
|
||||||
|
fi
|
||||||
|
# Key password
|
||||||
|
keyPassword="${ELEMENT_KEY_PASSWORD}"
|
||||||
|
if [[ -z "${keyPassword}" ]]; then
|
||||||
|
printf "Fatal: ELEMENT_KEY_PASSWORD is not defined in the environment.\n"
|
||||||
|
envError=1
|
||||||
|
fi
|
||||||
|
# GitHub token
|
||||||
|
gitHubToken="${ELEMENT_GITHUB_TOKEN}"
|
||||||
|
if [[ -z "${gitHubToken}" ]]; then
|
||||||
|
printf "Fatal: ELEMENT_GITHUB_TOKEN is not defined in the environment.\n"
|
||||||
|
envError=1
|
||||||
|
fi
|
||||||
|
# Android home
|
||||||
|
androidHome="${ANDROID_HOME}"
|
||||||
|
if [[ -z "${androidHome}" ]]; then
|
||||||
|
printf "Fatal: ANDROID_HOME is not defined in the environment.\n"
|
||||||
|
envError=1
|
||||||
|
fi
|
||||||
|
# @elementbot:matrix.org matrix token / Not mandatory
|
||||||
|
elementBotToken="${ELEMENT_BOT_MATRIX_TOKEN}"
|
||||||
|
if [[ -z "${elementBotToken}" ]]; then
|
||||||
|
printf "Warning: ELEMENT_BOT_MATRIX_TOKEN is not defined in the environment.\n"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
releaseScriptFullPath="${releaseScriptLocation}/releaseElement2.sh"
|
if [ ${envError} == 1 ]; then
|
||||||
|
|
||||||
if [[ ! -f ${releaseScriptFullPath} ]]; then
|
|
||||||
printf "Fatal: release script not found at ${releaseScriptFullPath}.\n"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
buildToolsVersion="30.0.2"
|
||||||
|
buildToolsPath="${androidHome}/build-tools/${buildToolsVersion}"
|
||||||
|
|
||||||
|
if [[ ! -d ${buildToolsPath} ]]; then
|
||||||
|
printf "Fatal: ${buildToolsPath} folder not found, ensure that you have installed the SDK version ${buildToolsVersion}.\n"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Check if git flow is enabled
|
# Check if git flow is enabled
|
||||||
git flow config >/dev/null 2>&1
|
git flow config >/dev/null 2>&1
|
||||||
if [[ $? == 0 ]]
|
if [[ $? == 0 ]]
|
||||||
then
|
then
|
||||||
printf "Git flow is initialized"
|
printf "Git flow is initialized\n"
|
||||||
else
|
else
|
||||||
printf "Git flow is not initialized. Initializing...\n"
|
printf "Git flow is not initialized. Initializing...\n"
|
||||||
# All default value, just set 'v' for tag prefix
|
# All default value, just set 'v' for tag prefix
|
||||||
git flow init -d -t 'v'
|
git flow init -d -t 'v'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
printf "OK\n"
|
||||||
|
|
||||||
|
printf "\n================================================================================\n"
|
||||||
# Guessing version to propose a default version
|
# Guessing version to propose a default version
|
||||||
versionMajorCandidate=`grep "ext.versionMajor" ./vector-app/build.gradle | cut -d " " -f3`
|
versionMajorCandidate=`grep "ext.versionMajor" ./vector-app/build.gradle | cut -d " " -f3`
|
||||||
versionMinorCandidate=`grep "ext.versionMinor" ./vector-app/build.gradle | cut -d " " -f3`
|
versionMinorCandidate=`grep "ext.versionMinor" ./vector-app/build.gradle | cut -d " " -f3`
|
||||||
versionPatchCandidate=`grep "ext.versionPatch" ./vector-app/build.gradle | cut -d " " -f3`
|
versionPatchCandidate=`grep "ext.versionPatch" ./vector-app/build.gradle | cut -d " " -f3`
|
||||||
versionCandidate="${versionMajorCandidate}.${versionMinorCandidate}.${versionPatchCandidate}"
|
versionCandidate="${versionMajorCandidate}.${versionMinorCandidate}.${versionPatchCandidate}"
|
||||||
|
|
||||||
printf "\n"
|
|
||||||
read -p "Please enter the release version (example: ${versionCandidate}). Just press enter if ${versionCandidate} is correct. " version
|
read -p "Please enter the release version (example: ${versionCandidate}). Just press enter if ${versionCandidate} is correct. " version
|
||||||
version=${version:-${versionCandidate}}
|
version=${version:-${versionCandidate}}
|
||||||
|
|
||||||
|
@ -225,17 +263,93 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf "\n================================================================================\n"
|
printf "\n================================================================================\n"
|
||||||
read -p "Wait for the GitHub action https://github.com/vector-im/element-android/actions/workflows/build.yml?query=branch%3Amain to build the 'main' branch. Press enter when it's done."
|
printf "Wait for the GitHub action https://github.com/vector-im/element-android/actions/workflows/build.yml?query=branch%3Amain to build the 'main' branch.\n"
|
||||||
|
read -p "After GHA is finished, please enter the artifact URL (for 'vector-gplay-release-unsigned'): " artifactUrl
|
||||||
|
|
||||||
printf "\n================================================================================\n"
|
printf "\n================================================================================\n"
|
||||||
printf "Running the release script...\n"
|
printf "Downloading the artifact...\n"
|
||||||
cd ${releaseScriptLocation}
|
|
||||||
${releaseScriptFullPath} "v${version}"
|
# Download files
|
||||||
cd -
|
targetPath="./tmp/Element/${version}"
|
||||||
|
|
||||||
|
# Ignore error
|
||||||
|
set +e
|
||||||
|
|
||||||
|
python3 ./tools/release/download_github_artifacts.py \
|
||||||
|
--token ${gitHubToken} \
|
||||||
|
--artifactUrl ${artifactUrl} \
|
||||||
|
--directory ${targetPath} \
|
||||||
|
--ignoreErrors
|
||||||
|
|
||||||
|
# Do not ignore error
|
||||||
|
set -e
|
||||||
|
|
||||||
printf "\n================================================================================\n"
|
printf "\n================================================================================\n"
|
||||||
apkPath="${releaseScriptLocation}/Element/v${version}/vector-gplay-arm64-v8a-release-signed.apk"
|
printf "Unzipping the artifact...\n"
|
||||||
printf "Installing apk on a real device...\n"
|
|
||||||
|
unzip ${targetPath}/vector-gplay-release-unsigned.zip -d ${targetPath}
|
||||||
|
|
||||||
|
# Flatten folder hierarchy
|
||||||
|
mv ${targetPath}/gplay/release/* ${targetPath}
|
||||||
|
rm -rf ${targetPath}/gplay
|
||||||
|
|
||||||
|
printf "\n================================================================================\n"
|
||||||
|
printf "Signing the APKs...\n"
|
||||||
|
|
||||||
|
cp ${targetPath}/vector-gplay-arm64-v8a-release-unsigned.apk \
|
||||||
|
${targetPath}/vector-gplay-arm64-v8a-release-signed.apk
|
||||||
|
./tools/release/sign_apk_unsafe.sh \
|
||||||
|
${keyStorePath} \
|
||||||
|
${targetPath}/vector-gplay-arm64-v8a-release-signed.apk \
|
||||||
|
${keyStorePassword} \
|
||||||
|
${keyPassword}
|
||||||
|
|
||||||
|
cp ${targetPath}/vector-gplay-armeabi-v7a-release-unsigned.apk \
|
||||||
|
${targetPath}/vector-gplay-armeabi-v7a-release-signed.apk
|
||||||
|
./tools/release/sign_apk_unsafe.sh \
|
||||||
|
${keyStorePath} \
|
||||||
|
${targetPath}/vector-gplay-armeabi-v7a-release-signed.apk \
|
||||||
|
${keyStorePassword} \
|
||||||
|
${keyPassword}
|
||||||
|
|
||||||
|
cp ${targetPath}/vector-gplay-x86-release-unsigned.apk \
|
||||||
|
${targetPath}/vector-gplay-x86-release-signed.apk
|
||||||
|
./tools/release/sign_apk_unsafe.sh \
|
||||||
|
${keyStorePath} \
|
||||||
|
${targetPath}/vector-gplay-x86-release-signed.apk \
|
||||||
|
${keyStorePassword} \
|
||||||
|
${keyPassword}
|
||||||
|
|
||||||
|
cp ${targetPath}/vector-gplay-x86_64-release-unsigned.apk \
|
||||||
|
${targetPath}/vector-gplay-x86_64-release-signed.apk
|
||||||
|
./tools/release/sign_apk_unsafe.sh \
|
||||||
|
${keyStorePath} \
|
||||||
|
${targetPath}/vector-gplay-x86_64-release-signed.apk \
|
||||||
|
${keyStorePassword} \
|
||||||
|
${keyPassword}
|
||||||
|
|
||||||
|
# Ref: https://docs.fastlane.tools/getting-started/android/beta-deployment/#uploading-your-app
|
||||||
|
# set SUPPLY_APK_PATHS="${targetPath}/vector-gplay-arm64-v8a-release-unsigned.apk,${targetPath}/vector-gplay-armeabi-v7a-release-unsigned.apk,${targetPath}/vector-gplay-x86-release-unsigned.apk,${targetPath}/vector-gplay-x86_64-release-unsigned.apk"
|
||||||
|
#
|
||||||
|
# ./fastlane beta
|
||||||
|
|
||||||
|
printf "\n================================================================================\n"
|
||||||
|
printf "Please check the information below:\n"
|
||||||
|
|
||||||
|
printf "File vector-gplay-arm64-v8a-release-signed.apk:\n"
|
||||||
|
${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-arm64-v8a-release-signed.apk | grep package
|
||||||
|
printf "File vector-gplay-armeabi-v7a-release-signed.apk:\n"
|
||||||
|
${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-armeabi-v7a-release-signed.apk | grep package
|
||||||
|
printf "File vector-gplay-x86-release-signed.apk:\n"
|
||||||
|
${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-x86-release-signed.apk | grep package
|
||||||
|
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================================================================================\n"
|
||||||
|
read -p "Installing apk on a real device, press enter when a real device is connected. "
|
||||||
|
apkPath="${targetPath}/vector-gplay-arm64-v8a-release-signed.apk"
|
||||||
adb -d install ${apkPath}
|
adb -d install ${apkPath}
|
||||||
|
|
||||||
read -p "Please run the APK on your phone to check that the upgrade went well (no init sync, etc.). Press enter when it's done."
|
read -p "Please run the APK on your phone to check that the upgrade went well (no init sync, etc.). Press enter when it's done."
|
||||||
|
@ -245,9 +359,25 @@ read -p "Create the release on gitHub from the tag https://github.com/vector-im/
|
||||||
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. Press enter when it's done."
|
||||||
|
|
||||||
printf "\n================================================================================\n"
|
printf "\n================================================================================\n"
|
||||||
printf "Ping the Android Internal room. Here is an example of message which can be sent:\n\n"
|
printf "Message for the Android internal room:\n\n"
|
||||||
printf "@room Element Android ${version} is ready to be tested. You can get if from https://github.com/vector-im/element-android/releases/tag/v${version}. Please report any feedback here. Thanks!\n\n"
|
message="@room Element Android ${version} is ready to be tested. You can get if from https://github.com/vector-im/element-android/releases/tag/v${version}. Please report any feedback here. Thanks!"
|
||||||
read -p "Press enter when it's done."
|
printf "${message}\n\n"
|
||||||
|
|
||||||
|
if [[ -z "${elementBotToken}" ]]; then
|
||||||
|
read -p "ELEMENT_BOT_MATRIX_TOKEN is not defined in the environment. Cannot send the message for you. Please send it manually, and press enter when it's done "
|
||||||
|
else
|
||||||
|
read -p "Send this message to the room (yes/no) default to yes? " doSend
|
||||||
|
doSend=${doSend:-yes}
|
||||||
|
if [ ${doSend} == "yes" ]; then
|
||||||
|
printf "Sending message...\n"
|
||||||
|
transactionId=`openssl rand -hex 16`
|
||||||
|
# Element Android internal
|
||||||
|
matrixRoomId="!LiSLXinTDCsepePiYW:matrix.org"
|
||||||
|
curl -X PUT --data $"{\"msgtype\":\"m.text\",\"body\":\"${message}\"}" -H "Authorization: Bearer ${elementBotToken}" https://matrix-client.matrix.org/_matrix/client/r0/rooms/${matrixRoomId}/send/m.room.message/\$local.${transactionId}
|
||||||
|
else
|
||||||
|
printf "Message not sent, please send it manually!\n"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
printf "\n================================================================================\n"
|
printf "\n================================================================================\n"
|
||||||
printf "Congratulation! Kudos for using this script! Have a nice day!\n"
|
printf "Congratulation! Kudos for using this script! Have a nice day!\n"
|
||||||
|
|
|
@ -37,7 +37,7 @@ ext.versionMinor = 5
|
||||||
// Note: even values are reserved for regular release, odd values for hotfix release.
|
// Note: even values are reserved for regular release, odd values for hotfix release.
|
||||||
// When creating a hotfix, you should decrease the value, since the current value
|
// When creating a hotfix, you should decrease the value, since the current value
|
||||||
// is the value for the next regular release.
|
// is the value for the next regular release.
|
||||||
ext.versionPatch = 11
|
ext.versionPatch = 12
|
||||||
|
|
||||||
ext.scVersion = 62
|
ext.scVersion = 62
|
||||||
|
|
||||||
|
@ -396,14 +396,14 @@ dependencies {
|
||||||
// Plant Timber tree for test
|
// Plant Timber tree for test
|
||||||
androidTestImplementation libs.tests.timberJunitRule
|
androidTestImplementation libs.tests.timberJunitRule
|
||||||
// "The one who serves a great Espresso"
|
// "The one who serves a great Espresso"
|
||||||
androidTestImplementation('com.adevinta.android:barista:4.2.0') {
|
androidTestImplementation('com.adevinta.android:barista:4.3.0') {
|
||||||
exclude group: 'org.jetbrains.kotlin'
|
exclude group: 'org.jetbrains.kotlin'
|
||||||
}
|
}
|
||||||
androidTestImplementation libs.mockk.mockkAndroid
|
androidTestImplementation libs.mockk.mockkAndroid
|
||||||
androidTestUtil libs.androidx.orchestrator
|
androidTestUtil libs.androidx.orchestrator
|
||||||
androidTestImplementation libs.androidx.fragmentTesting
|
androidTestImplementation libs.androidx.fragmentTesting
|
||||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.21"
|
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.22"
|
||||||
debugImplementation libs.androidx.fragmentTesting
|
debugImplementation libs.androidx.fragmentTesting
|
||||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
|
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,9 @@ class DebugVectorFeatures(
|
||||||
override fun isVoiceBroadcastEnabled(): Boolean = read(DebugFeatureKeys.voiceBroadcastEnabled)
|
override fun isVoiceBroadcastEnabled(): Boolean = read(DebugFeatureKeys.voiceBroadcastEnabled)
|
||||||
?: vectorFeatures.isVoiceBroadcastEnabled()
|
?: vectorFeatures.isVoiceBroadcastEnabled()
|
||||||
|
|
||||||
|
override fun isUnverifiedSessionsAlertEnabled(): Boolean = read(DebugFeatureKeys.unverifiedSessionsAlertEnabled)
|
||||||
|
?: vectorFeatures.isUnverifiedSessionsAlertEnabled()
|
||||||
|
|
||||||
fun <T> override(value: T?, key: Preferences.Key<T>) = updatePreferences {
|
fun <T> override(value: T?, key: Preferences.Key<T>) = updatePreferences {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
it.remove(key)
|
it.remove(key)
|
||||||
|
@ -151,4 +154,5 @@ object DebugFeatureKeys {
|
||||||
val qrCodeLoginForAllServers = booleanPreferencesKey("qr-code-login-for-all-servers")
|
val qrCodeLoginForAllServers = booleanPreferencesKey("qr-code-login-for-all-servers")
|
||||||
val reciprocateQrCodeLogin = booleanPreferencesKey("reciprocate-qr-code-login")
|
val reciprocateQrCodeLogin = booleanPreferencesKey("reciprocate-qr-code-login")
|
||||||
val voiceBroadcastEnabled = booleanPreferencesKey("voice-broadcast-enabled")
|
val voiceBroadcastEnabled = booleanPreferencesKey("voice-broadcast-enabled")
|
||||||
|
val unverifiedSessionsAlertEnabled = booleanPreferencesKey("unverified-sessions-alert-enabled")
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
package im.vector.app.push.fcm
|
package im.vector.app.push.fcm
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.pushers.FcmHelper
|
import im.vector.app.core.pushers.FcmHelper
|
||||||
|
@ -44,7 +43,7 @@ class FdroidFcmHelper @Inject constructor(
|
||||||
// No op
|
// No op
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) {
|
override fun ensureFcmTokenIsRetrieved(pushersManager: PushersManager, registerPusher: Boolean) {
|
||||||
// No op
|
// No op
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.app.push.fcm
|
package im.vector.app.push.fcm
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.widget.Toast
|
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.ConnectionResult
|
||||||
import com.google.android.gms.common.GoogleApiAvailability
|
import com.google.android.gms.common.GoogleApiAvailability
|
||||||
import com.google.firebase.messaging.FirebaseMessaging
|
import com.google.firebase.messaging.FirebaseMessaging
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.di.DefaultPreferences
|
import im.vector.app.core.di.DefaultPreferences
|
||||||
|
@ -36,8 +36,8 @@ import javax.inject.Inject
|
||||||
* It has an alter ego in the fdroid variant.
|
* It has an alter ego in the fdroid variant.
|
||||||
*/
|
*/
|
||||||
class GoogleFcmHelper @Inject constructor(
|
class GoogleFcmHelper @Inject constructor(
|
||||||
@DefaultPreferences
|
@ApplicationContext private val context: Context,
|
||||||
private val sharedPrefs: SharedPreferences,
|
@DefaultPreferences private val sharedPrefs: SharedPreferences,
|
||||||
) : FcmHelper {
|
) : FcmHelper {
|
||||||
companion object {
|
companion object {
|
||||||
private const val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN"
|
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) {
|
override fun ensureFcmTokenIsRetrieved(pushersManager: PushersManager, registerPusher: Boolean) {
|
||||||
// if (TextUtils.isEmpty(getFcmToken(activity))) {
|
|
||||||
// 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features'
|
// '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 {
|
try {
|
||||||
FirebaseMessaging.getInstance().token
|
FirebaseMessaging.getInstance().token
|
||||||
.addOnSuccessListener { token ->
|
.addOnSuccessListener { token ->
|
||||||
|
@ -75,7 +74,7 @@ class GoogleFcmHelper @Inject constructor(
|
||||||
Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed")
|
Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed")
|
||||||
}
|
}
|
||||||
} else {
|
} 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.")
|
Timber.e("No valid Google Play Services found. Cannot use FCM.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package im.vector.app.config
|
package im.vector.app.config
|
||||||
|
|
||||||
|
import kotlin.time.Duration.Companion.days
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set of flags to configure the application.
|
* Set of flags to configure the application.
|
||||||
*/
|
*/
|
||||||
|
@ -93,4 +95,6 @@ object Config {
|
||||||
* Can be disabled by providing Analytics.Disabled
|
* Can be disabled by providing Analytics.Disabled
|
||||||
*/
|
*/
|
||||||
val NIGHTLY_ANALYTICS_CONFIG = RELEASE_ANALYTICS_CONFIG.copy(sentryEnvironment = "NIGHTLY")
|
val NIGHTLY_ANALYTICS_CONFIG = RELEASE_ANALYTICS_CONFIG.copy(sentryEnvironment = "NIGHTLY")
|
||||||
|
|
||||||
|
val SHOW_UNVERIFIED_SESSIONS_ALERT_AFTER_MILLIS = 7.days.inWholeMilliseconds // 1 Week
|
||||||
}
|
}
|
||||||
|
|
|
@ -327,11 +327,11 @@ dependencies {
|
||||||
// Plant Timber tree for test
|
// Plant Timber tree for test
|
||||||
androidTestImplementation libs.tests.timberJunitRule
|
androidTestImplementation libs.tests.timberJunitRule
|
||||||
// "The one who serves a great Espresso"
|
// "The one who serves a great Espresso"
|
||||||
androidTestImplementation('com.adevinta.android:barista:4.2.0') {
|
androidTestImplementation('com.adevinta.android:barista:4.3.0') {
|
||||||
exclude group: 'org.jetbrains.kotlin'
|
exclude group: 'org.jetbrains.kotlin'
|
||||||
}
|
}
|
||||||
androidTestImplementation libs.mockk.mockkAndroid
|
androidTestImplementation libs.mockk.mockkAndroid
|
||||||
androidTestUtil libs.androidx.orchestrator
|
androidTestUtil libs.androidx.orchestrator
|
||||||
debugImplementation libs.androidx.fragmentTesting
|
debugImplementation libs.androidx.fragmentTesting
|
||||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.21"
|
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.22"
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,9 @@
|
||||||
<application android:supportsRtl="true">
|
<application android:supportsRtl="true">
|
||||||
|
|
||||||
<!-- Sentry auto-initialization disable -->
|
<!-- 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 -->
|
<!-- No limit for screen ratio: avoid black strips -->
|
||||||
<meta-data
|
<meta-data
|
||||||
|
@ -335,6 +337,7 @@
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".core.services.CallAndroidService"
|
android:name=".core.services.CallAndroidService"
|
||||||
|
android:foregroundServiceType="phoneCall"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
<!-- in order to get headset button events -->
|
<!-- in order to get headset button events -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
@ -346,6 +349,7 @@
|
||||||
<service
|
<service
|
||||||
android:name=".core.services.VectorSyncAndroidService"
|
android:name=".core.services.VectorSyncAndroidService"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="dataSync"
|
||||||
tools:ignore="Instantiatable" />
|
tools:ignore="Instantiatable" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
|
|
|
@ -19,7 +19,7 @@ package im.vector.app.core.di
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import im.vector.app.ActiveSessionDataSource
|
import im.vector.app.ActiveSessionDataSource
|
||||||
import im.vector.app.core.extensions.startSyncing
|
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.services.GuardServiceStarter
|
||||||
import im.vector.app.core.session.ConfigureAndStartSessionUseCase
|
import im.vector.app.core.session.ConfigureAndStartSessionUseCase
|
||||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||||
|
@ -47,12 +47,12 @@ class ActiveSessionHolder @Inject constructor(
|
||||||
private val pushRuleTriggerListener: PushRuleTriggerListener,
|
private val pushRuleTriggerListener: PushRuleTriggerListener,
|
||||||
private val sessionListener: SessionListener,
|
private val sessionListener: SessionListener,
|
||||||
private val imageManager: ImageManager,
|
private val imageManager: ImageManager,
|
||||||
private val unifiedPushHelper: UnifiedPushHelper,
|
|
||||||
private val guardServiceStarter: GuardServiceStarter,
|
private val guardServiceStarter: GuardServiceStarter,
|
||||||
private val sessionInitializer: SessionInitializer,
|
private val sessionInitializer: SessionInitializer,
|
||||||
private val applicationContext: Context,
|
private val applicationContext: Context,
|
||||||
private val authenticationService: AuthenticationService,
|
private val authenticationService: AuthenticationService,
|
||||||
private val configureAndStartSessionUseCase: ConfigureAndStartSessionUseCase,
|
private val configureAndStartSessionUseCase: ConfigureAndStartSessionUseCase,
|
||||||
|
private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private var activeSessionReference: AtomicReference<Session?> = AtomicReference()
|
private var activeSessionReference: AtomicReference<Session?> = AtomicReference()
|
||||||
|
@ -86,7 +86,7 @@ class ActiveSessionHolder @Inject constructor(
|
||||||
incomingVerificationRequestHandler.stop()
|
incomingVerificationRequestHandler.stop()
|
||||||
pushRuleTriggerListener.stop()
|
pushRuleTriggerListener.stop()
|
||||||
// No need to unregister the pusher, the sign out will (should?) do it server side.
|
// 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()
|
guardServiceStarter.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -105,6 +105,7 @@ import im.vector.app.features.settings.ignored.IgnoredUsersViewModel
|
||||||
import im.vector.app.features.settings.labs.VectorSettingsLabsViewModel
|
import im.vector.app.features.settings.labs.VectorSettingsLabsViewModel
|
||||||
import im.vector.app.features.settings.legals.LegalsViewModel
|
import im.vector.app.features.settings.legals.LegalsViewModel
|
||||||
import im.vector.app.features.settings.locale.LocalePickerViewModel
|
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.push.PushGatewaysViewModel
|
||||||
import im.vector.app.features.settings.threepids.ThreePidsSettingsViewModel
|
import im.vector.app.features.settings.threepids.ThreePidsSettingsViewModel
|
||||||
import im.vector.app.features.share.IncomingShareViewModel
|
import im.vector.app.features.share.IncomingShareViewModel
|
||||||
|
@ -683,4 +684,11 @@ interface MavericksViewModelModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@MavericksViewModelKey(AttachmentTypeSelectorViewModel::class)
|
@MavericksViewModelKey(AttachmentTypeSelectorViewModel::class)
|
||||||
fun attachmentTypeSelectorViewModelFactory(factory: AttachmentTypeSelectorViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
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.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen changes in Pusher or Account Data to update the local setting for notification toggle.
|
||||||
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
class EnableNotificationsSettingUpdater @Inject constructor(
|
class NotificationsSettingUpdater @Inject constructor(
|
||||||
private val updateEnableNotificationsSettingOnChangeUseCase: UpdateEnableNotificationsSettingOnChangeUseCase,
|
private val updateEnableNotificationsSettingOnChangeUseCase: UpdateEnableNotificationsSettingOnChangeUseCase,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private var job: Job? = null
|
private var job: Job? = null
|
||||||
|
|
||||||
fun onSessionsStarted(session: Session) {
|
fun onSessionStarted(session: Session) {
|
||||||
|
updateEnableNotificationsSettingOnChange(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateEnableNotificationsSettingOnChange(session: Session) {
|
||||||
job?.cancel()
|
job?.cancel()
|
||||||
job = session.coroutineScope.launch {
|
job = session.coroutineScope.launch {
|
||||||
updateEnableNotificationsSettingOnChangeUseCase.execute(session)
|
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
|
package im.vector.app.core.pushers
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
|
||||||
interface FcmHelper {
|
interface FcmHelper {
|
||||||
|
@ -39,11 +38,10 @@ interface FcmHelper {
|
||||||
/**
|
/**
|
||||||
* onNewToken may not be called on application upgrade, so ensure my shared pref is set.
|
* 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 pushersManager the instance to register the pusher on.
|
||||||
* @param registerPusher whether the pusher should be registered.
|
* @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)
|
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,14 @@
|
||||||
package im.vector.app.core.pushers
|
package im.vector.app.core.pushers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.annotation.MainThread
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.core.utils.getApplicationLabel
|
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 im.vector.app.features.settings.VectorPreferences
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.matrix.android.sdk.api.Matrix
|
import org.matrix.android.sdk.api.Matrix
|
||||||
import org.matrix.android.sdk.api.cache.CacheStrategy
|
import org.matrix.android.sdk.api.cache.CacheStrategy
|
||||||
import org.matrix.android.sdk.api.util.MatrixJsonParser
|
import org.matrix.android.sdk.api.util.MatrixJsonParser
|
||||||
|
@ -43,88 +39,13 @@ class UnifiedPushHelper @Inject constructor(
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val vectorPreferences: VectorPreferences,
|
private val vectorPreferences: VectorPreferences,
|
||||||
private val matrix: Matrix,
|
private val matrix: Matrix,
|
||||||
private val vectorFeatures: VectorFeatures,
|
|
||||||
private val fcmHelper: FcmHelper,
|
private val fcmHelper: FcmHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// Called when the home activity starts
|
@MainThread
|
||||||
// or when notifications are enabled
|
fun showSelectDistributorDialog(
|
||||||
fun register(
|
context: Context,
|
||||||
activity: FragmentActivity,
|
onDistributorSelected: (String) -> Unit,
|
||||||
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>
|
|
||||||
) {
|
) {
|
||||||
val internalDistributorName = stringProvider.getString(
|
val internalDistributorName = stringProvider.getString(
|
||||||
if (fcmHelper.isFirebaseAvailable()) {
|
if (fcmHelper.isFirebaseAvailable()) {
|
||||||
|
@ -134,6 +55,7 @@ class UnifiedPushHelper @Inject constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val distributors = UnifiedPush.getDistributors(context)
|
||||||
val distributorsName = distributors.map {
|
val distributorsName = distributors.map {
|
||||||
if (it == context.packageName) {
|
if (it == context.packageName) {
|
||||||
internalDistributorName
|
internalDistributorName
|
||||||
|
@ -142,44 +64,23 @@ class UnifiedPushHelper @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(activity)
|
MaterialAlertDialogBuilder(context)
|
||||||
.setTitle(stringProvider.getString(R.string.unifiedpush_getdistributors_dialog_title))
|
.setTitle(stringProvider.getString(R.string.unifiedpush_getdistributors_dialog_title))
|
||||||
.setItems(distributorsName.toTypedArray()) { _, which ->
|
.setItems(distributorsName.toTypedArray()) { _, which ->
|
||||||
val distributor = distributors[which]
|
val distributor = distributors[which]
|
||||||
|
onDistributorSelected(distributor)
|
||||||
activity.lifecycleScope.launch {
|
|
||||||
UnifiedPush.saveDistributor(context, distributor)
|
|
||||||
Timber.i("Saving distributor: $distributor")
|
|
||||||
UnifiedPush.registerApp(context)
|
|
||||||
onDoneRunnable?.run()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.setOnCancelListener {
|
.setOnCancelListener {
|
||||||
// By default, use internal solution (fcm/background sync)
|
// we do not want to change the distributor on behalf of the user
|
||||||
UnifiedPush.saveDistributor(context, context.packageName)
|
if (UnifiedPush.getDistributor(context).isEmpty()) {
|
||||||
UnifiedPush.registerApp(context)
|
// By default, use internal solution (fcm/background sync)
|
||||||
onDoneRunnable?.run()
|
onDistributorSelected(context.packageName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
.show()
|
.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)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class DiscoveryResponse(
|
internal data class DiscoveryResponse(
|
||||||
@Json(name = "unifiedpush") val unifiedpush: DiscoveryUnifiedPush = DiscoveryUnifiedPush()
|
@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 com.airbnb.mvrx.Mavericks
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import im.vector.app.core.extensions.singletonEntryPoint
|
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.CallArgs
|
||||||
import im.vector.app.features.call.VectorCallActivity
|
import im.vector.app.features.call.VectorCallActivity
|
||||||
import im.vector.app.features.call.telecom.CallConnection
|
import im.vector.app.features.call.telecom.CallConnection
|
||||||
|
@ -181,7 +182,7 @@ class CallAndroidService : VectorAndroidService() {
|
||||||
fromBg = fromBg
|
fromBg = fromBg
|
||||||
)
|
)
|
||||||
if (knownCalls.isEmpty()) {
|
if (knownCalls.isEmpty()) {
|
||||||
startForeground(callId.hashCode(), notification)
|
startForegroundCompat(callId.hashCode(), notification)
|
||||||
} else {
|
} else {
|
||||||
notificationManager.notify(callId.hashCode(), notification)
|
notificationManager.notify(callId.hashCode(), notification)
|
||||||
}
|
}
|
||||||
|
@ -201,7 +202,7 @@ class CallAndroidService : VectorAndroidService() {
|
||||||
}
|
}
|
||||||
val notification = notificationUtils.buildCallEndedNotification(false)
|
val notification = notificationUtils.buildCallEndedNotification(false)
|
||||||
val notificationId = callId.hashCode()
|
val notificationId = callId.hashCode()
|
||||||
startForeground(notificationId, notification)
|
startForegroundCompat(notificationId, notification)
|
||||||
if (knownCalls.isEmpty()) {
|
if (knownCalls.isEmpty()) {
|
||||||
Timber.tag(loggerTag.value).v("No more call, stop the service")
|
Timber.tag(loggerTag.value).v("No more call, stop the service")
|
||||||
stopForegroundCompat()
|
stopForegroundCompat()
|
||||||
|
@ -236,7 +237,7 @@ class CallAndroidService : VectorAndroidService() {
|
||||||
title = callInformation.opponentMatrixItem?.getBestName() ?: callInformation.opponentUserId
|
title = callInformation.opponentMatrixItem?.getBestName() ?: callInformation.opponentUserId
|
||||||
)
|
)
|
||||||
if (knownCalls.isEmpty()) {
|
if (knownCalls.isEmpty()) {
|
||||||
startForeground(callId.hashCode(), notification)
|
startForegroundCompat(callId.hashCode(), notification)
|
||||||
} else {
|
} else {
|
||||||
notificationManager.notify(callId.hashCode(), notification)
|
notificationManager.notify(callId.hashCode(), notification)
|
||||||
}
|
}
|
||||||
|
@ -260,7 +261,7 @@ class CallAndroidService : VectorAndroidService() {
|
||||||
title = callInformation.opponentMatrixItem?.getBestName() ?: callInformation.opponentUserId
|
title = callInformation.opponentMatrixItem?.getBestName() ?: callInformation.opponentUserId
|
||||||
)
|
)
|
||||||
if (knownCalls.isEmpty()) {
|
if (knownCalls.isEmpty()) {
|
||||||
startForeground(callId.hashCode(), notification)
|
startForegroundCompat(callId.hashCode(), notification)
|
||||||
} else {
|
} else {
|
||||||
notificationManager.notify(callId.hashCode(), notification)
|
notificationManager.notify(callId.hashCode(), notification)
|
||||||
}
|
}
|
||||||
|
@ -273,9 +274,9 @@ class CallAndroidService : VectorAndroidService() {
|
||||||
callRingPlayerOutgoing?.stop()
|
callRingPlayerOutgoing?.stop()
|
||||||
val notification = notificationUtils.buildCallEndedNotification(false)
|
val notification = notificationUtils.buildCallEndedNotification(false)
|
||||||
if (callId != null) {
|
if (callId != null) {
|
||||||
startForeground(callId.hashCode(), notification)
|
startForegroundCompat(callId.hashCode(), notification)
|
||||||
} else {
|
} else {
|
||||||
startForeground(DEFAULT_NOTIFICATION_ID, notification)
|
startForegroundCompat(DEFAULT_NOTIFICATION_ID, notification)
|
||||||
}
|
}
|
||||||
if (knownCalls.isEmpty()) {
|
if (knownCalls.isEmpty()) {
|
||||||
mediaSession?.isActive = false
|
mediaSession?.isActive = false
|
||||||
|
|
|
@ -32,6 +32,7 @@ import androidx.work.Worker
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.startForegroundCompat
|
||||||
import im.vector.app.core.platform.PendingIntentCompat
|
import im.vector.app.core.platform.PendingIntentCompat
|
||||||
import im.vector.app.core.time.Clock
|
import im.vector.app.core.time.Clock
|
||||||
import im.vector.app.core.time.DefaultClock
|
import im.vector.app.core.time.DefaultClock
|
||||||
|
@ -98,7 +99,7 @@ class VectorSyncAndroidService : SyncAndroidService() {
|
||||||
R.string.notification_listening_for_notifications
|
R.string.notification_listening_for_notifications
|
||||||
}
|
}
|
||||||
val notification = notificationUtils.buildForegroundServiceNotification(notificationSubtitleRes, false)
|
val notification = notificationUtils.buildForegroundServiceNotification(notificationSubtitleRes, false)
|
||||||
startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
|
startForegroundCompat(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRescheduleAsked(
|
override fun onRescheduleAsked(
|
||||||
|
|
|
@ -19,11 +19,12 @@ package im.vector.app.core.session
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import im.vector.app.core.extensions.startSyncing
|
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.core.session.clientinfo.UpdateMatrixClientInfoUseCase
|
||||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||||
import im.vector.app.features.session.coroutineScope
|
import im.vector.app.features.session.coroutineScope
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
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 im.vector.app.features.sync.SyncUtils
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
@ -35,7 +36,8 @@ class ConfigureAndStartSessionUseCase @Inject constructor(
|
||||||
private val webRtcCallManager: WebRtcCallManager,
|
private val webRtcCallManager: WebRtcCallManager,
|
||||||
private val updateMatrixClientInfoUseCase: UpdateMatrixClientInfoUseCase,
|
private val updateMatrixClientInfoUseCase: UpdateMatrixClientInfoUseCase,
|
||||||
private val vectorPreferences: VectorPreferences,
|
private val vectorPreferences: VectorPreferences,
|
||||||
private val enableNotificationsSettingUpdater: EnableNotificationsSettingUpdater,
|
private val notificationsSettingUpdater: NotificationsSettingUpdater,
|
||||||
|
private val updateNotificationSettingsAccountDataUseCase: UpdateNotificationSettingsAccountDataUseCase,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun execute(session: Session, startSyncing: Boolean = true) {
|
fun execute(session: Session, startSyncing: Boolean = true) {
|
||||||
|
@ -49,11 +51,22 @@ class ConfigureAndStartSessionUseCase @Inject constructor(
|
||||||
}
|
}
|
||||||
session.pushersService().refreshPushers()
|
session.pushersService().refreshPushers()
|
||||||
webRtcCallManager.checkForProtocolsSupportIfNeeded()
|
webRtcCallManager.checkForProtocolsSupportIfNeeded()
|
||||||
|
updateMatrixClientInfoIfNeeded(session)
|
||||||
|
createNotificationSettingsAccountDataIfNeeded(session)
|
||||||
|
notificationsSettingUpdater.onSessionStarted(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateMatrixClientInfoIfNeeded(session: Session) {
|
||||||
session.coroutineScope.launch {
|
session.coroutineScope.launch {
|
||||||
if (vectorPreferences.isClientInfoRecordingEnabled()) {
|
if (vectorPreferences.isClientInfoRecordingEnabled()) {
|
||||||
updateMatrixClientInfoUseCase.execute(session)
|
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) {
|
fun render(roomEncryptionTrustLevel: RoomEncryptionTrustLevel?, borderLess: Boolean = false) {
|
||||||
isVisible = roomEncryptionTrustLevel != null
|
isVisible = roomEncryptionTrustLevel != null
|
||||||
|
|
||||||
|
@ -45,8 +64,8 @@ class ShieldImageView @JvmOverloads constructor(
|
||||||
RoomEncryptionTrustLevel.Default -> {
|
RoomEncryptionTrustLevel.Default -> {
|
||||||
contentDescription = context.getString(R.string.a11y_trust_level_default)
|
contentDescription = context.getString(R.string.a11y_trust_level_default)
|
||||||
setImageResource(
|
setImageResource(
|
||||||
if (borderLess) R.drawable.ic_shield_unknown_no_border
|
if (borderLess) R.drawable.ic_shield_black_no_border
|
||||||
else R.drawable.ic_shield_unknown
|
else R.drawable.ic_shield_black
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
RoomEncryptionTrustLevel.Warning -> {
|
RoomEncryptionTrustLevel.Warning -> {
|
||||||
|
@ -137,7 +156,7 @@ class ShieldImageView @JvmOverloads constructor(
|
||||||
@DrawableRes
|
@DrawableRes
|
||||||
fun RoomEncryptionTrustLevel.toDrawableRes(): Int {
|
fun RoomEncryptionTrustLevel.toDrawableRes(): Int {
|
||||||
return when (this) {
|
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.Warning -> R.drawable.ic_shield_warning
|
||||||
RoomEncryptionTrustLevel.Trusted -> R.drawable.ic_shield_trusted
|
RoomEncryptionTrustLevel.Trusted -> R.drawable.ic_shield_trusted
|
||||||
RoomEncryptionTrustLevel.E2EWithUnsupportedAlgorithm -> R.drawable.ic_warning_badge
|
RoomEncryptionTrustLevel.E2EWithUnsupportedAlgorithm -> R.drawable.ic_warning_badge
|
||||||
|
|
|
@ -608,26 +608,33 @@ class ExpandingBottomSheetBehavior<V : View> : CoordinatorLayout.Behavior<V> {
|
||||||
initialPaddingBottom = view.paddingBottom
|
initialPaddingBottom = view.paddingBottom
|
||||||
|
|
||||||
// This should only be used to set initial insets and other edge cases where the insets can't be applied using an animation.
|
// This should only be used to set initial insets and other edge cases where the insets can't be applied using an animation.
|
||||||
var applyInsetsFromAnimation = false
|
var isAnimating = false
|
||||||
|
|
||||||
// This will animated inset changes, making them look a lot better. However, it won't update initial insets.
|
// This will animate inset changes, making them look a lot better. However, it won't update initial insets.
|
||||||
ViewCompat.setWindowInsetsAnimationCallback(view, object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
|
ViewCompat.setWindowInsetsAnimationCallback(view, object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
|
||||||
|
override fun onPrepare(animation: WindowInsetsAnimationCompat) {
|
||||||
|
isAnimating = true
|
||||||
|
}
|
||||||
|
|
||||||
override fun onProgress(insets: WindowInsetsCompat, runningAnimations: MutableList<WindowInsetsAnimationCompat>): WindowInsetsCompat {
|
override fun onProgress(insets: WindowInsetsCompat, runningAnimations: MutableList<WindowInsetsAnimationCompat>): WindowInsetsCompat {
|
||||||
return applyInsets(view, insets)
|
return if (isAnimating) {
|
||||||
|
applyInsets(view, insets)
|
||||||
|
} else {
|
||||||
|
insets
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEnd(animation: WindowInsetsAnimationCompat) {
|
override fun onEnd(animation: WindowInsetsAnimationCompat) {
|
||||||
applyInsetsFromAnimation = false
|
isAnimating = false
|
||||||
view.requestApplyInsets()
|
view.requestApplyInsets()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(view) { _: View, insets: WindowInsetsCompat ->
|
ViewCompat.setOnApplyWindowInsetsListener(view) { _: View, insets: WindowInsetsCompat ->
|
||||||
if (!applyInsetsFromAnimation) {
|
if (isAnimating) {
|
||||||
applyInsetsFromAnimation = true
|
|
||||||
applyInsets(view, insets)
|
|
||||||
} else {
|
|
||||||
insets
|
insets
|
||||||
|
} else {
|
||||||
|
applyInsets(view, insets)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ interface VectorFeatures {
|
||||||
fun isQrCodeLoginForAllServers(): Boolean
|
fun isQrCodeLoginForAllServers(): Boolean
|
||||||
fun isReciprocateQrCodeLogin(): Boolean
|
fun isReciprocateQrCodeLogin(): Boolean
|
||||||
fun isVoiceBroadcastEnabled(): Boolean
|
fun isVoiceBroadcastEnabled(): Boolean
|
||||||
|
fun isUnverifiedSessionsAlertEnabled(): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
class DefaultVectorFeatures : VectorFeatures {
|
class DefaultVectorFeatures : VectorFeatures {
|
||||||
|
@ -63,4 +64,5 @@ class DefaultVectorFeatures : VectorFeatures {
|
||||||
override fun isQrCodeLoginForAllServers(): Boolean = false
|
override fun isQrCodeLoginForAllServers(): Boolean = false
|
||||||
override fun isReciprocateQrCodeLogin(): Boolean = false
|
override fun isReciprocateQrCodeLogin(): Boolean = false
|
||||||
override fun isVoiceBroadcastEnabled(): Boolean = true
|
override fun isVoiceBroadcastEnabled(): Boolean = true
|
||||||
|
override fun isUnverifiedSessionsAlertEnabled(): Boolean = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import android.content.Intent
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import im.vector.app.core.extensions.startForegroundCompat
|
||||||
import im.vector.app.core.services.VectorAndroidService
|
import im.vector.app.core.services.VectorAndroidService
|
||||||
import im.vector.app.core.time.Clock
|
import im.vector.app.core.time.Clock
|
||||||
import im.vector.app.features.notifications.NotificationUtils
|
import im.vector.app.features.notifications.NotificationUtils
|
||||||
|
@ -41,7 +42,7 @@ class ScreenCaptureAndroidService : VectorAndroidService() {
|
||||||
private fun showStickyNotification() {
|
private fun showStickyNotification() {
|
||||||
val notificationId = clock.epochMillis().toInt()
|
val notificationId = clock.epochMillis().toInt()
|
||||||
val notification = notificationUtils.buildScreenSharingNotification()
|
val notification = notificationUtils.buildScreenSharingNotification()
|
||||||
startForeground(notificationId, notification)
|
startForegroundCompat(notificationId, notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(intent: Intent?): IBinder {
|
override fun onBind(intent: Intent?): IBinder {
|
||||||
|
|
|
@ -45,8 +45,6 @@ import im.vector.app.core.extensions.restart
|
||||||
import im.vector.app.core.extensions.validateBackPressed
|
import im.vector.app.core.extensions.validateBackPressed
|
||||||
import im.vector.app.core.platform.VectorBaseActivity
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
import im.vector.app.core.platform.VectorMenuProvider
|
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.pushers.UnifiedPushHelper
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.core.utils.registerForPermissionsResult
|
import im.vector.app.core.utils.registerForPermissionsResult
|
||||||
|
@ -134,7 +132,6 @@ class HomeActivity :
|
||||||
private val serverBackupStatusViewModel: ServerBackupStatusViewModel by viewModel()
|
private val serverBackupStatusViewModel: ServerBackupStatusViewModel by viewModel()
|
||||||
|
|
||||||
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
|
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
|
||||||
@Inject lateinit var pushersManager: PushersManager
|
|
||||||
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
|
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
|
||||||
@Inject lateinit var popupAlertManager: PopupAlertManager
|
@Inject lateinit var popupAlertManager: PopupAlertManager
|
||||||
@Inject lateinit var shortcutsHandler: ShortcutsHandler
|
@Inject lateinit var shortcutsHandler: ShortcutsHandler
|
||||||
|
@ -143,7 +140,6 @@ class HomeActivity :
|
||||||
@Inject lateinit var initSyncStepFormatter: InitSyncStepFormatter
|
@Inject lateinit var initSyncStepFormatter: InitSyncStepFormatter
|
||||||
@Inject lateinit var spaceStateHandler: SpaceStateHandler
|
@Inject lateinit var spaceStateHandler: SpaceStateHandler
|
||||||
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper
|
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper
|
||||||
@Inject lateinit var fcmHelper: FcmHelper
|
|
||||||
@Inject lateinit var nightlyProxy: NightlyProxy
|
@Inject lateinit var nightlyProxy: NightlyProxy
|
||||||
@Inject lateinit var disclaimerDialog: DisclaimerDialog
|
@Inject lateinit var disclaimerDialog: DisclaimerDialog
|
||||||
@Inject lateinit var notificationPermissionManager: NotificationPermissionManager
|
@Inject lateinit var notificationPermissionManager: NotificationPermissionManager
|
||||||
|
@ -215,16 +211,6 @@ class HomeActivity :
|
||||||
isNewAppLayoutEnabled = vectorPreferences.isNewAppLayoutEnabled()
|
isNewAppLayoutEnabled = vectorPreferences.isNewAppLayoutEnabled()
|
||||||
analyticsScreenName = MobileScreen.ScreenName.Home
|
analyticsScreenName = MobileScreen.ScreenName.Home
|
||||||
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false)
|
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false)
|
||||||
unifiedPushHelper.register(this) {
|
|
||||||
if (unifiedPushHelper.isEmbeddedDistributor()) {
|
|
||||||
fcmHelper.ensureFcmTokenIsRetrieved(
|
|
||||||
this,
|
|
||||||
pushersManager,
|
|
||||||
homeActivityViewModel.shouldAddHttpPusher()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sharedActionViewModel = viewModelProvider[HomeSharedActionViewModel::class.java]
|
sharedActionViewModel = viewModelProvider[HomeSharedActionViewModel::class.java]
|
||||||
roomListSharedActionViewModel = viewModelProvider[RoomListSharedActionViewModel::class.java]
|
roomListSharedActionViewModel = viewModelProvider[RoomListSharedActionViewModel::class.java]
|
||||||
views.drawerLayout.addDrawerListener(drawerListener)
|
views.drawerLayout.addDrawerListener(drawerListener)
|
||||||
|
@ -286,6 +272,7 @@ class HomeActivity :
|
||||||
HomeActivityViewEvents.ShowReleaseNotes -> handleShowReleaseNotes()
|
HomeActivityViewEvents.ShowReleaseNotes -> handleShowReleaseNotes()
|
||||||
HomeActivityViewEvents.NotifyUserForThreadsMigration -> handleNotifyUserForThreadsMigration()
|
HomeActivityViewEvents.NotifyUserForThreadsMigration -> handleNotifyUserForThreadsMigration()
|
||||||
is HomeActivityViewEvents.MigrateThreads -> migrateThreadsIfNeeded(it.checkSession)
|
is HomeActivityViewEvents.MigrateThreads -> migrateThreadsIfNeeded(it.checkSession)
|
||||||
|
is HomeActivityViewEvents.AskUserForPushDistributor -> askUserToSelectPushDistributor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
homeActivityViewModel.onEach { renderState(it) }
|
homeActivityViewModel.onEach { renderState(it) }
|
||||||
|
@ -298,6 +285,12 @@ class HomeActivity :
|
||||||
homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted)
|
homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun askUserToSelectPushDistributor() {
|
||||||
|
unifiedPushHelper.showSelectDistributorDialog(this) { selection ->
|
||||||
|
homeActivityViewModel.handle(HomeActivityViewActions.RegisterPushDistributor(selection))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleShowNotificationDialog() {
|
private fun handleShowNotificationDialog() {
|
||||||
notificationPermissionManager.eventuallyRequestPermission(this, postPermissionLauncher)
|
notificationPermissionManager.eventuallyRequestPermission(this, postPermissionLauncher)
|
||||||
}
|
}
|
||||||
|
@ -430,14 +423,6 @@ class HomeActivity :
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderState(state: HomeActivityViewState) {
|
private fun renderState(state: HomeActivityViewState) {
|
||||||
lifecycleScope.launch {
|
|
||||||
if (state.areNotificationsSilenced) {
|
|
||||||
unifiedPushHelper.unregister(pushersManager)
|
|
||||||
} else {
|
|
||||||
unifiedPushHelper.register(this@HomeActivity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
when (val status = state.syncRequestState) {
|
when (val status = state.syncRequestState) {
|
||||||
is SyncRequestState.InitialSyncProgressing -> {
|
is SyncRequestState.InitialSyncProgressing -> {
|
||||||
val initSyncStepStr = initSyncStepFormatter.format(status.initialSyncStep)
|
val initSyncStepStr = initSyncStepFormatter.format(status.initialSyncStep)
|
||||||
|
|
|
@ -21,4 +21,5 @@ import im.vector.app.core.platform.VectorViewModelAction
|
||||||
sealed interface HomeActivityViewActions : VectorViewModelAction {
|
sealed interface HomeActivityViewActions : VectorViewModelAction {
|
||||||
object ViewStarted : HomeActivityViewActions
|
object ViewStarted : HomeActivityViewActions
|
||||||
object PushPromptHasBeenReviewed : HomeActivityViewActions
|
object PushPromptHasBeenReviewed : HomeActivityViewActions
|
||||||
|
data class RegisterPushDistributor(val distributor: String) : HomeActivityViewActions
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,9 +25,11 @@ sealed interface HomeActivityViewEvents : VectorViewEvents {
|
||||||
val userItem: MatrixItem.UserItem,
|
val userItem: MatrixItem.UserItem,
|
||||||
val waitForIncomingRequest: Boolean = true,
|
val waitForIncomingRequest: Boolean = true,
|
||||||
) : HomeActivityViewEvents
|
) : HomeActivityViewEvents
|
||||||
|
|
||||||
data class CurrentSessionCannotBeVerified(
|
data class CurrentSessionCannotBeVerified(
|
||||||
val userItem: MatrixItem.UserItem,
|
val userItem: MatrixItem.UserItem,
|
||||||
) : HomeActivityViewEvents
|
) : HomeActivityViewEvents
|
||||||
|
|
||||||
data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents
|
data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents
|
||||||
object PromptToEnableSessionPush : HomeActivityViewEvents
|
object PromptToEnableSessionPush : HomeActivityViewEvents
|
||||||
object ShowAnalyticsOptIn : HomeActivityViewEvents
|
object ShowAnalyticsOptIn : HomeActivityViewEvents
|
||||||
|
@ -37,4 +39,5 @@ sealed interface HomeActivityViewEvents : VectorViewEvents {
|
||||||
data class MigrateThreads(val checkSession: Boolean) : HomeActivityViewEvents
|
data class MigrateThreads(val checkSession: Boolean) : HomeActivityViewEvents
|
||||||
object StartRecoverySetupFlow : HomeActivityViewEvents
|
object StartRecoverySetupFlow : HomeActivityViewEvents
|
||||||
data class ForceVerification(val sendRequest: Boolean) : HomeActivityViewEvents
|
data class ForceVerification(val sendRequest: Boolean) : HomeActivityViewEvents
|
||||||
|
object AskUserForPushDistributor : HomeActivityViewEvents
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package im.vector.app.features.home
|
package im.vector.app.features.home
|
||||||
|
|
||||||
import androidx.lifecycle.asFlow
|
|
||||||
import com.airbnb.mvrx.Mavericks
|
import com.airbnb.mvrx.Mavericks
|
||||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
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.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
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.AnalyticsConfig
|
||||||
import im.vector.app.features.analytics.AnalyticsTracker
|
import im.vector.app.features.analytics.AnalyticsTracker
|
||||||
import im.vector.app.features.analytics.extensions.toAnalyticsType
|
import im.vector.app.features.analytics.extensions.toAnalyticsType
|
||||||
|
@ -48,12 +50,10 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.onCompletion
|
import kotlinx.coroutines.flow.onCompletion
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.takeWhile
|
import kotlinx.coroutines.flow.takeWhile
|
||||||
import kotlinx.coroutines.launch
|
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.UIABaseAuth
|
||||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||||
|
@ -62,11 +62,9 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||||
import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage
|
import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
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.crypto.crosssigning.CrossSigningService
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
||||||
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.getUserOrDefault
|
||||||
import org.matrix.android.sdk.api.session.pushrules.RuleIds
|
import org.matrix.android.sdk.api.session.pushrules.RuleIds
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
|
@ -92,8 +90,11 @@ class HomeActivityViewModel @AssistedInject constructor(
|
||||||
private val analyticsTracker: AnalyticsTracker,
|
private val analyticsTracker: AnalyticsTracker,
|
||||||
private val analyticsConfig: AnalyticsConfig,
|
private val analyticsConfig: AnalyticsConfig,
|
||||||
private val releaseNotesPreferencesStore: ReleaseNotesPreferencesStore,
|
private val releaseNotesPreferencesStore: ReleaseNotesPreferencesStore,
|
||||||
private val vectorFeatures: VectorFeatures,
|
|
||||||
private val stopOngoingVoiceBroadcastUseCase: StopOngoingVoiceBroadcastUseCase,
|
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) {
|
) : VectorViewModel<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(initialState) {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
@ -117,17 +118,44 @@ class HomeActivityViewModel @AssistedInject constructor(
|
||||||
private fun initialize() {
|
private fun initialize() {
|
||||||
if (isInitialized) return
|
if (isInitialized) return
|
||||||
isInitialized = true
|
isInitialized = true
|
||||||
|
registerUnifiedPushIfNeeded()
|
||||||
cleanupFiles()
|
cleanupFiles()
|
||||||
observeInitialSync()
|
observeInitialSync()
|
||||||
checkSessionPushIsOn()
|
checkSessionPushIsOn()
|
||||||
observeCrossSigningReset()
|
observeCrossSigningReset()
|
||||||
observeAnalytics()
|
observeAnalytics()
|
||||||
//observeReleaseNotes()
|
//observeReleaseNotes()
|
||||||
observeLocalNotificationsSilenced()
|
|
||||||
initThreadsMigration()
|
initThreadsMigration()
|
||||||
viewModelScope.launch { stopOngoingVoiceBroadcastUseCase.execute() }
|
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 ->
|
private fun observeReleaseNotes() = withState { state ->
|
||||||
if (vectorPreferences.isNewAppLayoutEnabled()) {
|
if (vectorPreferences.isNewAppLayoutEnabled()) {
|
||||||
|
@ -148,26 +176,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().getMyDevice().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() {
|
private fun observeAnalytics() {
|
||||||
if (analyticsConfig.isEnabled) {
|
if (analyticsConfig.isEnabled) {
|
||||||
analyticsStore.didAskUserConsentFlow
|
analyticsStore.didAskUserConsentFlow
|
||||||
|
@ -503,6 +511,9 @@ class HomeActivityViewModel @AssistedInject constructor(
|
||||||
HomeActivityViewActions.ViewStarted -> {
|
HomeActivityViewActions.ViewStarted -> {
|
||||||
initialize()
|
initialize()
|
||||||
}
|
}
|
||||||
|
is HomeActivityViewActions.RegisterPushDistributor -> {
|
||||||
|
registerUnifiedPush(distributor = action.distributor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,5 +23,4 @@ import org.matrix.android.sdk.api.session.sync.SyncRequestState
|
||||||
data class HomeActivityViewState(
|
data class HomeActivityViewState(
|
||||||
val syncRequestState: SyncRequestState = SyncRequestState.Idle,
|
val syncRequestState: SyncRequestState = SyncRequestState.Idle,
|
||||||
val authenticationDescription: AuthenticationDescription? = null,
|
val authenticationDescription: AuthenticationDescription? = null,
|
||||||
val areNotificationsSilenced: Boolean = false,
|
|
||||||
) : MavericksState
|
) : MavericksState
|
||||||
|
|
|
@ -487,12 +487,12 @@ class HomeDetailFragment :
|
||||||
.requestSessionVerification(vectorBaseActivity, newest.deviceId ?: "")
|
.requestSessionVerification(vectorBaseActivity, newest.deviceId ?: "")
|
||||||
}
|
}
|
||||||
unknownDeviceDetectorSharedViewModel.handle(
|
unknownDeviceDetectorSharedViewModel.handle(
|
||||||
UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(newest.deviceId?.let { listOf(it) }.orEmpty())
|
UnknownDeviceDetectorSharedViewModel.Action.IgnoreNewLogin(newest.deviceId?.let { listOf(it) }.orEmpty())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
dismissedAction = Runnable {
|
dismissedAction = Runnable {
|
||||||
unknownDeviceDetectorSharedViewModel.handle(
|
unknownDeviceDetectorSharedViewModel.handle(
|
||||||
UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(newest.deviceId?.let { listOf(it) }.orEmpty())
|
UnknownDeviceDetectorSharedViewModel.Action.IgnoreNewLogin(newest.deviceId?.let { listOf(it) }.orEmpty())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -504,8 +504,8 @@ class HomeDetailFragment :
|
||||||
alertManager.postVectorAlert(
|
alertManager.postVectorAlert(
|
||||||
VerificationVectorAlert(
|
VerificationVectorAlert(
|
||||||
uid = uid,
|
uid = uid,
|
||||||
title = getString(R.string.review_logins),
|
title = getString(R.string.review_unverified_sessions_title),
|
||||||
description = getString(R.string.verify_other_sessions),
|
description = getString(R.string.review_unverified_sessions_description),
|
||||||
iconId = R.drawable.ic_shield_warning
|
iconId = R.drawable.ic_shield_warning
|
||||||
).apply {
|
).apply {
|
||||||
viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer)
|
viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer)
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home
|
||||||
|
|
||||||
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class IsNewLoginAlertShownUseCase @Inject constructor(
|
||||||
|
private val vectorPreferences: VectorPreferences,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun execute(deviceId: String?): Boolean {
|
||||||
|
deviceId ?: return false
|
||||||
|
|
||||||
|
return vectorPreferences.isNewLoginAlertShownForDevice(deviceId)
|
||||||
|
}
|
||||||
|
}
|
|
@ -264,12 +264,12 @@ class NewHomeDetailFragment :
|
||||||
.requestSessionVerification(vectorBaseActivity, newest.deviceId ?: "")
|
.requestSessionVerification(vectorBaseActivity, newest.deviceId ?: "")
|
||||||
}
|
}
|
||||||
unknownDeviceDetectorSharedViewModel.handle(
|
unknownDeviceDetectorSharedViewModel.handle(
|
||||||
UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(newest.deviceId?.let { listOf(it) }.orEmpty())
|
UnknownDeviceDetectorSharedViewModel.Action.IgnoreNewLogin(newest.deviceId?.let { listOf(it) }.orEmpty())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
dismissedAction = Runnable {
|
dismissedAction = Runnable {
|
||||||
unknownDeviceDetectorSharedViewModel.handle(
|
unknownDeviceDetectorSharedViewModel.handle(
|
||||||
UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(newest.deviceId?.let { listOf(it) }.orEmpty())
|
UnknownDeviceDetectorSharedViewModel.Action.IgnoreNewLogin(newest.deviceId?.let { listOf(it) }.orEmpty())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -281,8 +281,8 @@ class NewHomeDetailFragment :
|
||||||
alertManager.postVectorAlert(
|
alertManager.postVectorAlert(
|
||||||
VerificationVectorAlert(
|
VerificationVectorAlert(
|
||||||
uid = uid,
|
uid = uid,
|
||||||
title = getString(R.string.review_logins),
|
title = getString(R.string.review_unverified_sessions_title),
|
||||||
description = getString(R.string.verify_other_sessions),
|
description = getString(R.string.review_unverified_sessions_description),
|
||||||
iconId = R.drawable.ic_shield_warning
|
iconId = R.drawable.ic_shield_warning
|
||||||
).apply {
|
).apply {
|
||||||
viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer)
|
viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer)
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home
|
||||||
|
|
||||||
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SetNewLoginAlertShownUseCase @Inject constructor(
|
||||||
|
private val vectorPreferences: VectorPreferences,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun execute(deviceIds: List<String>) {
|
||||||
|
deviceIds.forEach {
|
||||||
|
vectorPreferences.setNewLoginAlertShownForDevice(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.home
|
||||||
|
|
||||||
|
import im.vector.app.core.time.Clock
|
||||||
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SetUnverifiedSessionsAlertShownUseCase @Inject constructor(
|
||||||
|
private val vectorPreferences: VectorPreferences,
|
||||||
|
private val clock: Clock,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun execute(deviceIds: List<String>) {
|
||||||
|
val epochMillis = clock.epochMillis()
|
||||||
|
deviceIds.forEach {
|
||||||
|
vectorPreferences.setUnverifiedSessionsAlertLastShownMillis(it, epochMillis)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import im.vector.app.config.Config
|
||||||
|
import im.vector.app.core.time.Clock
|
||||||
|
import im.vector.app.features.VectorFeatures
|
||||||
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ShouldShowUnverifiedSessionsAlertUseCase @Inject constructor(
|
||||||
|
private val vectorFeatures: VectorFeatures,
|
||||||
|
private val vectorPreferences: VectorPreferences,
|
||||||
|
private val clock: Clock,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun execute(deviceId: String?): Boolean {
|
||||||
|
deviceId ?: return false
|
||||||
|
|
||||||
|
val isUnverifiedSessionsAlertEnabled = vectorFeatures.isUnverifiedSessionsAlertEnabled()
|
||||||
|
val unverifiedSessionsAlertLastShownMillis = vectorPreferences.getUnverifiedSessionsAlertLastShownMillis(deviceId)
|
||||||
|
return isUnverifiedSessionsAlertEnabled &&
|
||||||
|
clock.epochMillis() - unverifiedSessionsAlertLastShownMillis >= Config.SHOW_UNVERIFIED_SESSIONS_ALERT_AFTER_MILLIS
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,6 @@ package im.vector.app.features.home
|
||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.MavericksState
|
import com.airbnb.mvrx.MavericksState
|
||||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||||
import com.airbnb.mvrx.Success
|
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
|
@ -33,7 +32,6 @@ import im.vector.app.core.platform.EmptyViewEvents
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.core.platform.VectorViewModelAction
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
import im.vector.app.core.time.Clock
|
import im.vector.app.core.time.Clock
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
@ -63,12 +61,16 @@ data class DeviceDetectionInfo(
|
||||||
class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(
|
class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(
|
||||||
@Assisted initialState: UnknownDevicesState,
|
@Assisted initialState: UnknownDevicesState,
|
||||||
session: Session,
|
session: Session,
|
||||||
private val vectorPreferences: VectorPreferences,
|
|
||||||
clock: Clock,
|
clock: Clock,
|
||||||
|
private val shouldShowUnverifiedSessionsAlertUseCase: ShouldShowUnverifiedSessionsAlertUseCase,
|
||||||
|
private val setUnverifiedSessionsAlertShownUseCase: SetUnverifiedSessionsAlertShownUseCase,
|
||||||
|
private val isNewLoginAlertShownUseCase: IsNewLoginAlertShownUseCase,
|
||||||
|
private val setNewLoginAlertShownUseCase: SetNewLoginAlertShownUseCase,
|
||||||
) : VectorViewModel<UnknownDevicesState, UnknownDeviceDetectorSharedViewModel.Action, EmptyViewEvents>(initialState) {
|
) : VectorViewModel<UnknownDevicesState, UnknownDeviceDetectorSharedViewModel.Action, EmptyViewEvents>(initialState) {
|
||||||
|
|
||||||
sealed class Action : VectorViewModelAction {
|
sealed class Action : VectorViewModelAction {
|
||||||
data class IgnoreDevice(val deviceIds: List<String>) : Action()
|
data class IgnoreDevice(val deviceIds: List<String>) : Action()
|
||||||
|
data class IgnoreNewLogin(val deviceIds: List<String>) : Action()
|
||||||
}
|
}
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
@ -86,8 +88,6 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val ignoredDeviceList = ArrayList<String>()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val currentSessionTs = session.cryptoService().getCryptoDeviceInfo(session.myUserId)
|
val currentSessionTs = session.cryptoService().getCryptoDeviceInfo(session.myUserId)
|
||||||
.firstOrNull { it.deviceId == session.sessionParams.deviceId }
|
.firstOrNull { it.deviceId == session.sessionParams.deviceId }
|
||||||
|
@ -95,12 +95,6 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(
|
||||||
?: clock.epochMillis()
|
?: clock.epochMillis()
|
||||||
Timber.v("## Detector - Current Session first time seen $currentSessionTs")
|
Timber.v("## Detector - Current Session first time seen $currentSessionTs")
|
||||||
|
|
||||||
ignoredDeviceList.addAll(
|
|
||||||
vectorPreferences.getUnknownDeviceDismissedList().also {
|
|
||||||
Timber.v("## Detector - Remembered ignored list $it")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
combine(
|
combine(
|
||||||
session.flow().liveUserCryptoDevices(session.myUserId),
|
session.flow().liveUserCryptoDevices(session.myUserId),
|
||||||
session.flow().liveMyDevicesInfo(),
|
session.flow().liveMyDevicesInfo(),
|
||||||
|
@ -114,13 +108,15 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(
|
||||||
cryptoList.firstOrNull { info.deviceId == it.deviceId }?.isVerified?.not().orFalse()
|
cryptoList.firstOrNull { info.deviceId == it.deviceId }?.isVerified?.not().orFalse()
|
||||||
}
|
}
|
||||||
// filter out ignored devices
|
// filter out ignored devices
|
||||||
.filter { !ignoredDeviceList.contains(it.deviceId) }
|
.filter { shouldShowUnverifiedSessionsAlertUseCase.execute(it.deviceId) }
|
||||||
.sortedByDescending { it.lastSeenTs }
|
.sortedByDescending { it.lastSeenTs }
|
||||||
.map { deviceInfo ->
|
.map { deviceInfo ->
|
||||||
val deviceKnownSince = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }?.firstTimeSeenLocalTs ?: 0
|
val deviceKnownSince = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }?.firstTimeSeenLocalTs ?: 0
|
||||||
|
val isNew = isNewLoginAlertShownUseCase.execute(deviceInfo.deviceId).not() && deviceKnownSince > currentSessionTs
|
||||||
|
|
||||||
DeviceDetectionInfo(
|
DeviceDetectionInfo(
|
||||||
deviceInfo,
|
deviceInfo,
|
||||||
deviceKnownSince > currentSessionTs + 60_000, // short window to avoid false positive,
|
isNew,
|
||||||
pInfo.getOrNull()?.selfSigned != null // adding this to pass distinct when cross sign change
|
pInfo.getOrNull()?.selfSigned != null // adding this to pass distinct when cross sign change
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -150,22 +146,11 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(
|
||||||
override fun handle(action: Action) {
|
override fun handle(action: Action) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is Action.IgnoreDevice -> {
|
is Action.IgnoreDevice -> {
|
||||||
ignoredDeviceList.addAll(action.deviceIds)
|
setUnverifiedSessionsAlertShownUseCase.execute(action.deviceIds)
|
||||||
// local echo
|
}
|
||||||
withState { state ->
|
is Action.IgnoreNewLogin -> {
|
||||||
state.unknownSessions.invoke()?.let { detectedSessions ->
|
setNewLoginAlertShownUseCase.execute(action.deviceIds)
|
||||||
val updated = detectedSessions.filter { !action.deviceIds.contains(it.deviceInfo.deviceId) }
|
|
||||||
setState {
|
|
||||||
copy(unknownSessions = Success(updated))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
|
||||||
vectorPreferences.storeUnknownDeviceDismissedList(ignoredDeviceList)
|
|
||||||
super.onCleared()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -268,7 +268,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
|
||||||
) { mainState, messageComposerState, attachmentState ->
|
) { mainState, messageComposerState, attachmentState ->
|
||||||
if (mainState.tombstoneEvent != null) return@withState
|
if (mainState.tombstoneEvent != null) return@withState
|
||||||
|
|
||||||
(composer as? View)?.isInvisible = !messageComposerState.isComposerVisible
|
(composer as? View)?.isVisible = messageComposerState.isComposerVisible
|
||||||
composer.sendButton.isInvisible = !messageComposerState.isSendButtonVisible
|
composer.sendButton.isInvisible = !messageComposerState.isSendButtonVisible
|
||||||
(composer as? RichTextComposerLayout)?.isTextFormattingEnabled = attachmentState.isTextFormattingEnabled
|
(composer as? RichTextComposerLayout)?.isTextFormattingEnabled = attachmentState.isTextFormattingEnabled
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,7 @@ import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
import org.matrix.android.sdk.api.session.getRoomSummary
|
import org.matrix.android.sdk.api.session.getRoomSummary
|
||||||
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
import org.matrix.android.sdk.api.session.room.getStateEvent
|
import org.matrix.android.sdk.api.session.room.getStateEvent
|
||||||
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||||
|
@ -89,39 +90,44 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
private val voiceBroadcastHelper: VoiceBroadcastHelper,
|
private val voiceBroadcastHelper: VoiceBroadcastHelper,
|
||||||
) : VectorViewModel<MessageComposerViewState, MessageComposerAction, MessageComposerViewEvents>(initialState) {
|
) : VectorViewModel<MessageComposerViewState, MessageComposerAction, MessageComposerViewEvents>(initialState) {
|
||||||
|
|
||||||
private val room = session.getRoom(initialState.roomId)!!
|
private val room = session.getRoom(initialState.roomId)
|
||||||
|
|
||||||
// Keep it out of state to avoid invalidate being called
|
// Keep it out of state to avoid invalidate being called
|
||||||
private var currentComposerText: CharSequence = ""
|
private var currentComposerText: CharSequence = ""
|
||||||
|
|
||||||
init {
|
init {
|
||||||
loadDraftIfAny()
|
if (room != null) {
|
||||||
observePowerLevelAndEncryption()
|
loadDraftIfAny(room)
|
||||||
observeVoiceBroadcast()
|
observePowerLevelAndEncryption(room)
|
||||||
subscribeToStateInternal()
|
observeVoiceBroadcast(room)
|
||||||
|
subscribeToStateInternal()
|
||||||
|
} else {
|
||||||
|
onRoomError()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handle(action: MessageComposerAction) {
|
override fun handle(action: MessageComposerAction) {
|
||||||
|
val room = this.room ?: return
|
||||||
when (action) {
|
when (action) {
|
||||||
is MessageComposerAction.EnterEditMode -> handleEnterEditMode(action)
|
is MessageComposerAction.EnterEditMode -> handleEnterEditMode(room, action)
|
||||||
is MessageComposerAction.EnterQuoteMode -> handleEnterQuoteMode(action)
|
is MessageComposerAction.EnterQuoteMode -> handleEnterQuoteMode(room, action)
|
||||||
is MessageComposerAction.EnterRegularMode -> handleEnterRegularMode(action)
|
is MessageComposerAction.EnterRegularMode -> handleEnterRegularMode(action)
|
||||||
is MessageComposerAction.EnterReplyMode -> handleEnterReplyMode(action)
|
is MessageComposerAction.EnterReplyMode -> handleEnterReplyMode(room, action)
|
||||||
is MessageComposerAction.SendMessage -> handleSendMessage(action)
|
is MessageComposerAction.SendMessage -> handleSendMessage(room, action)
|
||||||
is MessageComposerAction.UserIsTyping -> handleUserIsTyping(action)
|
is MessageComposerAction.UserIsTyping -> handleUserIsTyping(room, action)
|
||||||
is MessageComposerAction.OnTextChanged -> handleOnTextChanged(action)
|
is MessageComposerAction.OnTextChanged -> handleOnTextChanged(action)
|
||||||
is MessageComposerAction.OnVoiceRecordingUiStateChanged -> handleOnVoiceRecordingUiStateChanged(action)
|
is MessageComposerAction.OnVoiceRecordingUiStateChanged -> handleOnVoiceRecordingUiStateChanged(action)
|
||||||
is MessageComposerAction.StartRecordingVoiceMessage -> handleStartRecordingVoiceMessage()
|
is MessageComposerAction.StartRecordingVoiceMessage -> handleStartRecordingVoiceMessage(room)
|
||||||
is MessageComposerAction.EndRecordingVoiceMessage -> handleEndRecordingVoiceMessage(action.isCancelled, action.rootThreadEventId)
|
is MessageComposerAction.EndRecordingVoiceMessage -> handleEndRecordingVoiceMessage(room, action.isCancelled, action.rootThreadEventId)
|
||||||
is MessageComposerAction.PlayOrPauseVoicePlayback -> handlePlayOrPauseVoicePlayback(action)
|
is MessageComposerAction.PlayOrPauseVoicePlayback -> handlePlayOrPauseVoicePlayback(action)
|
||||||
MessageComposerAction.PauseRecordingVoiceMessage -> handlePauseRecordingVoiceMessage()
|
MessageComposerAction.PauseRecordingVoiceMessage -> handlePauseRecordingVoiceMessage()
|
||||||
MessageComposerAction.PlayOrPauseRecordingPlayback -> handlePlayOrPauseRecordingPlayback()
|
MessageComposerAction.PlayOrPauseRecordingPlayback -> handlePlayOrPauseRecordingPlayback()
|
||||||
is MessageComposerAction.InitializeVoiceRecorder -> handleInitializeVoiceRecorder(action.attachmentData)
|
is MessageComposerAction.InitializeVoiceRecorder -> handleInitializeVoiceRecorder(room, action.attachmentData)
|
||||||
is MessageComposerAction.OnEntersBackground -> handleEntersBackground(action.composerText)
|
is MessageComposerAction.OnEntersBackground -> handleEntersBackground(room, action.composerText)
|
||||||
is MessageComposerAction.VoiceWaveformTouchedUp -> handleVoiceWaveformTouchedUp(action)
|
is MessageComposerAction.VoiceWaveformTouchedUp -> handleVoiceWaveformTouchedUp(action)
|
||||||
is MessageComposerAction.VoiceWaveformMovedTo -> handleVoiceWaveformMovedTo(action)
|
is MessageComposerAction.VoiceWaveformMovedTo -> handleVoiceWaveformMovedTo(action)
|
||||||
is MessageComposerAction.AudioSeekBarMovedTo -> handleAudioSeekBarMovedTo(action)
|
is MessageComposerAction.AudioSeekBarMovedTo -> handleAudioSeekBarMovedTo(action)
|
||||||
is MessageComposerAction.SlashCommandConfirmed -> handleSlashCommandConfirmed(action)
|
is MessageComposerAction.SlashCommandConfirmed -> handleSlashCommandConfirmed(room, action)
|
||||||
is MessageComposerAction.InsertUserDisplayName -> handleInsertUserDisplayName(action)
|
is MessageComposerAction.InsertUserDisplayName -> handleInsertUserDisplayName(action)
|
||||||
is MessageComposerAction.SetFullScreen -> handleSetFullScreen(action)
|
is MessageComposerAction.SetFullScreen -> handleSetFullScreen(action)
|
||||||
// SC
|
// SC
|
||||||
|
@ -162,7 +168,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
copy(sendMode = SendMode.Regular(newText, action.fromSharing))
|
copy(sendMode = SendMode.Regular(newText, action.fromSharing))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEnterEditMode(action: MessageComposerAction.EnterEditMode) {
|
private fun handleEnterEditMode(room: Room, action: MessageComposerAction.EnterEditMode) {
|
||||||
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
||||||
val formatted = vectorPreferences.isRichTextEditorEnabled()
|
val formatted = vectorPreferences.isRichTextEditorEnabled()
|
||||||
setState { copy(sendMode = SendMode.Edit(timelineEvent, timelineEvent.getTextEditableContent(formatted))) }
|
setState { copy(sendMode = SendMode.Edit(timelineEvent, timelineEvent.getTextEditableContent(formatted))) }
|
||||||
|
@ -173,7 +179,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
setState { copy(isFullScreen = action.isFullScreen) }
|
setState { copy(isFullScreen = action.isFullScreen) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observePowerLevelAndEncryption() {
|
private fun observePowerLevelAndEncryption(room: Room) {
|
||||||
combine(
|
combine(
|
||||||
PowerLevelsFlowFactory(room).createFlow(),
|
PowerLevelsFlowFactory(room).createFlow(),
|
||||||
room.flow().liveRoomSummary().unwrap()
|
room.flow().liveRoomSummary().unwrap()
|
||||||
|
@ -199,7 +205,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeVoiceBroadcast() {
|
private fun observeVoiceBroadcast(room: Room) {
|
||||||
room.stateService().getStateEventLive(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId))
|
room.stateService().getStateEventLive(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId))
|
||||||
.asFlow()
|
.asFlow()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -209,19 +215,19 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEnterQuoteMode(action: MessageComposerAction.EnterQuoteMode) {
|
private fun handleEnterQuoteMode(room: Room, action: MessageComposerAction.EnterQuoteMode) {
|
||||||
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
||||||
setState { copy(sendMode = SendMode.Quote(timelineEvent, currentComposerText)) }
|
setState { copy(sendMode = SendMode.Quote(timelineEvent, currentComposerText)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEnterReplyMode(action: MessageComposerAction.EnterReplyMode) {
|
private fun handleEnterReplyMode(room: Room, action: MessageComposerAction.EnterReplyMode) {
|
||||||
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
||||||
setState { copy(sendMode = SendMode.Reply(timelineEvent, currentComposerText)) }
|
setState { copy(sendMode = SendMode.Reply(timelineEvent, currentComposerText)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSendMessage(action: MessageComposerAction.SendMessage) {
|
private fun handleSendMessage(room: Room, action: MessageComposerAction.SendMessage) {
|
||||||
withState { state ->
|
withState { state ->
|
||||||
analyticsTracker.capture(state.toAnalyticsComposer()).also {
|
analyticsTracker.capture(state.toAnalyticsComposer()).also {
|
||||||
setState { copy(startsThread = false) }
|
setState { copy(startsThread = false) }
|
||||||
|
@ -251,7 +257,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ErrorSyntax -> {
|
is ParsedCommand.ErrorSyntax -> {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandError(parsedCommand.command))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandError(parsedCommand.command))
|
||||||
|
@ -277,7 +283,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
room.sendService().sendTextMessage(parsedCommand.message, autoMarkdown = false)
|
room.sendService().sendTextMessage(parsedCommand.message, autoMarkdown = false)
|
||||||
}
|
}
|
||||||
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendFormattedText -> {
|
is ParsedCommand.SendFormattedText -> {
|
||||||
// Send the text message to the room, without markdown
|
// Send the text message to the room, without markdown
|
||||||
|
@ -295,23 +301,23 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ChangeRoomName -> {
|
is ParsedCommand.ChangeRoomName -> {
|
||||||
handleChangeRoomNameSlashCommand(parsedCommand)
|
handleChangeRoomNameSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.Invite -> {
|
is ParsedCommand.Invite -> {
|
||||||
handleInviteSlashCommand(parsedCommand)
|
handleInviteSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.Invite3Pid -> {
|
is ParsedCommand.Invite3Pid -> {
|
||||||
handleInvite3pidSlashCommand(parsedCommand)
|
handleInvite3pidSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SetUserPowerLevel -> {
|
is ParsedCommand.SetUserPowerLevel -> {
|
||||||
handleSetUserPowerLevel(parsedCommand)
|
handleSetUserPowerLevel(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.DevTools -> {
|
is ParsedCommand.DevTools -> {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ClearScalarToken -> {
|
is ParsedCommand.ClearScalarToken -> {
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -320,29 +326,29 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
is ParsedCommand.SetMarkdown -> {
|
is ParsedCommand.SetMarkdown -> {
|
||||||
vectorPreferences.setMarkdownEnabled(parsedCommand.enable)
|
vectorPreferences.setMarkdownEnabled(parsedCommand.enable)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.BanUser -> {
|
is ParsedCommand.BanUser -> {
|
||||||
handleBanSlashCommand(parsedCommand)
|
handleBanSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.UnbanUser -> {
|
is ParsedCommand.UnbanUser -> {
|
||||||
handleUnbanSlashCommand(parsedCommand)
|
handleUnbanSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.IgnoreUser -> {
|
is ParsedCommand.IgnoreUser -> {
|
||||||
handleIgnoreSlashCommand(parsedCommand)
|
handleIgnoreSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.UnignoreUser -> {
|
is ParsedCommand.UnignoreUser -> {
|
||||||
handleUnignoreSlashCommand(parsedCommand)
|
handleUnignoreSlashCommand(parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.RemoveUser -> {
|
is ParsedCommand.RemoveUser -> {
|
||||||
handleRemoveSlashCommand(parsedCommand)
|
handleRemoveSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.JoinRoom -> {
|
is ParsedCommand.JoinRoom -> {
|
||||||
handleJoinToAnotherRoomSlashCommand(parsedCommand)
|
handleJoinToAnotherRoomSlashCommand(parsedCommand)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.PartRoom -> {
|
is ParsedCommand.PartRoom -> {
|
||||||
handlePartSlashCommand(parsedCommand)
|
handlePartSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendEmote -> {
|
is ParsedCommand.SendEmote -> {
|
||||||
if (state.rootThreadEventId != null) {
|
if (state.rootThreadEventId != null) {
|
||||||
|
@ -360,7 +366,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendRainbow -> {
|
is ParsedCommand.SendRainbow -> {
|
||||||
val message = parsedCommand.message.toString()
|
val message = parsedCommand.message.toString()
|
||||||
|
@ -374,7 +380,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
room.sendService().sendFormattedTextMessage(message, rainbowGenerator.generate(message))
|
room.sendService().sendFormattedTextMessage(message, rainbowGenerator.generate(message))
|
||||||
}
|
}
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendRainbowEmote -> {
|
is ParsedCommand.SendRainbowEmote -> {
|
||||||
val message = parsedCommand.message.toString()
|
val message = parsedCommand.message.toString()
|
||||||
|
@ -390,7 +396,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendSpoiler -> {
|
is ParsedCommand.SendSpoiler -> {
|
||||||
val text = "[${stringProvider.getString(R.string.spoiler)}](${parsedCommand.message})"
|
val text = "[${stringProvider.getString(R.string.spoiler)}](${parsedCommand.message})"
|
||||||
|
@ -408,53 +414,53 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendShrug -> {
|
is ParsedCommand.SendShrug -> {
|
||||||
sendPrefixedMessage("¯\\_(ツ)_/¯", parsedCommand.message, state.rootThreadEventId)
|
sendPrefixedMessage(room, "¯\\_(ツ)_/¯", parsedCommand.message, state.rootThreadEventId)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendLenny -> {
|
is ParsedCommand.SendLenny -> {
|
||||||
sendPrefixedMessage("( ͡° ͜ʖ ͡°)", parsedCommand.message, state.rootThreadEventId)
|
sendPrefixedMessage(room, "( ͡° ͜ʖ ͡°)", parsedCommand.message, state.rootThreadEventId)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendTableFlip -> {
|
is ParsedCommand.SendTableFlip -> {
|
||||||
sendPrefixedMessage("(╯°□°)╯︵ ┻━┻", parsedCommand.message, state.rootThreadEventId)
|
sendPrefixedMessage(room, "(╯°□°)╯︵ ┻━┻", parsedCommand.message, state.rootThreadEventId)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendChatEffect -> {
|
is ParsedCommand.SendChatEffect -> {
|
||||||
sendChatEffect(parsedCommand)
|
sendChatEffect(room, parsedCommand)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ChangeTopic -> {
|
is ParsedCommand.ChangeTopic -> {
|
||||||
handleChangeTopicSlashCommand(parsedCommand)
|
handleChangeTopicSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ChangeDisplayName -> {
|
is ParsedCommand.ChangeDisplayName -> {
|
||||||
handleChangeDisplayNameSlashCommand(parsedCommand)
|
handleChangeDisplayNameSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ChangeDisplayNameForRoom -> {
|
is ParsedCommand.ChangeDisplayNameForRoom -> {
|
||||||
handleChangeDisplayNameForRoomSlashCommand(parsedCommand)
|
handleChangeDisplayNameForRoomSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ChangeRoomAvatar -> {
|
is ParsedCommand.ChangeRoomAvatar -> {
|
||||||
handleChangeRoomAvatarSlashCommand(parsedCommand)
|
handleChangeRoomAvatarSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ChangeAvatarForRoom -> {
|
is ParsedCommand.ChangeAvatarForRoom -> {
|
||||||
handleChangeAvatarForRoomSlashCommand(parsedCommand)
|
handleChangeAvatarForRoomSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ShowUser -> {
|
is ParsedCommand.ShowUser -> {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
handleWhoisSlashCommand(parsedCommand)
|
handleWhoisSlashCommand(parsedCommand)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.DiscardSession -> {
|
is ParsedCommand.DiscardSession -> {
|
||||||
if (room.roomCryptoService().isEncrypted()) {
|
if (room.roomCryptoService().isEncrypted()) {
|
||||||
session.cryptoService().discardOutboundSession(room.roomId)
|
session.cryptoService().discardOutboundSession(room.roomId)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
} else {
|
} else {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
_viewEvents.post(
|
_viewEvents.post(
|
||||||
|
@ -479,7 +485,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
null,
|
null,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
||||||
|
@ -498,7 +504,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
null,
|
null,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
||||||
|
@ -511,7 +517,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
session.spaceService().joinSpace(parsedCommand.spaceIdOrAlias)
|
session.spaceService().joinSpace(parsedCommand.spaceIdOrAlias)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
||||||
|
@ -523,7 +529,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
session.roomService().leaveRoom(parsedCommand.roomId)
|
session.roomService().leaveRoom(parsedCommand.roomId)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
||||||
|
@ -539,7 +545,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -588,7 +594,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is SendMode.Quote -> {
|
is SendMode.Quote -> {
|
||||||
room.sendService().sendQuotedTextMessage(
|
room.sendService().sendQuotedTextMessage(
|
||||||
|
@ -599,7 +605,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
rootThreadEventId = state.rootThreadEventId
|
rootThreadEventId = state.rootThreadEventId
|
||||||
)
|
)
|
||||||
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is SendMode.Reply -> {
|
is SendMode.Reply -> {
|
||||||
val timelineEvent = state.sendMode.timelineEvent
|
val timelineEvent = state.sendMode.timelineEvent
|
||||||
|
@ -624,7 +630,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is SendMode.Voice -> {
|
is SendMode.Voice -> {
|
||||||
// do nothing
|
// do nothing
|
||||||
|
@ -633,10 +639,10 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun popDraft() = withState {
|
private fun popDraft(room: Room) = withState {
|
||||||
if (it.sendMode is SendMode.Regular && it.sendMode.fromSharing) {
|
if (it.sendMode is SendMode.Regular && it.sendMode.fromSharing) {
|
||||||
// If we were sharing, we want to get back our last value from draft
|
// If we were sharing, we want to get back our last value from draft
|
||||||
loadDraftIfAny()
|
loadDraftIfAny(room)
|
||||||
} else {
|
} else {
|
||||||
// Otherwise we clear the composer and remove the draft from db
|
// Otherwise we clear the composer and remove the draft from db
|
||||||
setState { copy(sendMode = SendMode.Regular("", false)) }
|
setState { copy(sendMode = SendMode.Regular("", false)) }
|
||||||
|
@ -646,7 +652,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadDraftIfAny() {
|
private fun loadDraftIfAny(room: Room) {
|
||||||
val currentDraft = room.draftService().getDraft()
|
val currentDraft = room.draftService().getDraft()
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
|
@ -675,7 +681,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUserIsTyping(action: MessageComposerAction.UserIsTyping) {
|
private fun handleUserIsTyping(room: Room, action: MessageComposerAction.UserIsTyping) {
|
||||||
if (vectorPreferences.sendTypingNotifs()) {
|
if (vectorPreferences.sendTypingNotifs()) {
|
||||||
if (action.isTyping) {
|
if (action.isTyping) {
|
||||||
room.typingService().userIsTyping()
|
room.typingService().userIsTyping()
|
||||||
|
@ -685,7 +691,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendChatEffect(sendChatEffect: ParsedCommand.SendChatEffect) {
|
private fun sendChatEffect(room: Room, sendChatEffect: ParsedCommand.SendChatEffect) {
|
||||||
// If message is blank, convert to an emote, with default message
|
// If message is blank, convert to an emote, with default message
|
||||||
if (sendChatEffect.message.isBlank()) {
|
if (sendChatEffect.message.isBlank()) {
|
||||||
val defaultMessage = stringProvider.getString(
|
val defaultMessage = stringProvider.getString(
|
||||||
|
@ -737,25 +743,25 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
|
private fun handleChangeTopicSlashCommand(room: Room, changeTopic: ParsedCommand.ChangeTopic) {
|
||||||
launchSlashCommandFlowSuspendable(changeTopic) {
|
launchSlashCommandFlowSuspendable(room, changeTopic) {
|
||||||
room.stateService().updateTopic(changeTopic.topic)
|
room.stateService().updateTopic(changeTopic.topic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) {
|
private fun handleInviteSlashCommand(room: Room, invite: ParsedCommand.Invite) {
|
||||||
launchSlashCommandFlowSuspendable(invite) {
|
launchSlashCommandFlowSuspendable(room, invite) {
|
||||||
room.membershipService().invite(invite.userId, invite.reason)
|
room.membershipService().invite(invite.userId, invite.reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleInvite3pidSlashCommand(invite: ParsedCommand.Invite3Pid) {
|
private fun handleInvite3pidSlashCommand(room: Room, invite: ParsedCommand.Invite3Pid) {
|
||||||
launchSlashCommandFlowSuspendable(invite) {
|
launchSlashCommandFlowSuspendable(room, invite) {
|
||||||
room.membershipService().invite3pid(invite.threePid)
|
room.membershipService().invite3pid(invite.threePid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSetUserPowerLevel(setUserPowerLevel: ParsedCommand.SetUserPowerLevel) {
|
private fun handleSetUserPowerLevel(room: Room, setUserPowerLevel: ParsedCommand.SetUserPowerLevel) {
|
||||||
val newPowerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)
|
val newPowerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)
|
||||||
?.content
|
?.content
|
||||||
?.toModel<PowerLevelsContent>()
|
?.toModel<PowerLevelsContent>()
|
||||||
|
@ -763,19 +769,19 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
?.toContent()
|
?.toContent()
|
||||||
?: return
|
?: return
|
||||||
|
|
||||||
launchSlashCommandFlowSuspendable(setUserPowerLevel) {
|
launchSlashCommandFlowSuspendable(room, setUserPowerLevel) {
|
||||||
room.stateService().sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent)
|
room.stateService().sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeDisplayNameSlashCommand(changeDisplayName: ParsedCommand.ChangeDisplayName) {
|
private fun handleChangeDisplayNameSlashCommand(room: Room, changeDisplayName: ParsedCommand.ChangeDisplayName) {
|
||||||
launchSlashCommandFlowSuspendable(changeDisplayName) {
|
launchSlashCommandFlowSuspendable(room, changeDisplayName) {
|
||||||
session.profileService().setDisplayName(session.myUserId, changeDisplayName.displayName)
|
session.profileService().setDisplayName(session.myUserId, changeDisplayName.displayName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePartSlashCommand(command: ParsedCommand.PartRoom) {
|
private fun handlePartSlashCommand(room: Room, command: ParsedCommand.PartRoom) {
|
||||||
launchSlashCommandFlowSuspendable(command) {
|
launchSlashCommandFlowSuspendable(room, command) {
|
||||||
if (command.roomAlias == null) {
|
if (command.roomAlias == null) {
|
||||||
// Leave the current room
|
// Leave the current room
|
||||||
room
|
room
|
||||||
|
@ -790,39 +796,39 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRemoveSlashCommand(removeUser: ParsedCommand.RemoveUser) {
|
private fun handleRemoveSlashCommand(room: Room, removeUser: ParsedCommand.RemoveUser) {
|
||||||
launchSlashCommandFlowSuspendable(removeUser) {
|
launchSlashCommandFlowSuspendable(room, removeUser) {
|
||||||
room.membershipService().remove(removeUser.userId, removeUser.reason)
|
room.membershipService().remove(removeUser.userId, removeUser.reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleBanSlashCommand(ban: ParsedCommand.BanUser) {
|
private fun handleBanSlashCommand(room: Room, ban: ParsedCommand.BanUser) {
|
||||||
launchSlashCommandFlowSuspendable(ban) {
|
launchSlashCommandFlowSuspendable(room, ban) {
|
||||||
room.membershipService().ban(ban.userId, ban.reason)
|
room.membershipService().ban(ban.userId, ban.reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUnbanSlashCommand(unban: ParsedCommand.UnbanUser) {
|
private fun handleUnbanSlashCommand(room: Room, unban: ParsedCommand.UnbanUser) {
|
||||||
launchSlashCommandFlowSuspendable(unban) {
|
launchSlashCommandFlowSuspendable(room, unban) {
|
||||||
room.membershipService().unban(unban.userId, unban.reason)
|
room.membershipService().unban(unban.userId, unban.reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeRoomNameSlashCommand(changeRoomName: ParsedCommand.ChangeRoomName) {
|
private fun handleChangeRoomNameSlashCommand(room: Room, changeRoomName: ParsedCommand.ChangeRoomName) {
|
||||||
launchSlashCommandFlowSuspendable(changeRoomName) {
|
launchSlashCommandFlowSuspendable(room, changeRoomName) {
|
||||||
room.stateService().updateName(changeRoomName.name)
|
room.stateService().updateName(changeRoomName.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMyRoomMemberContent(): RoomMemberContent? {
|
private fun getMyRoomMemberContent(room: Room): RoomMemberContent? {
|
||||||
return room.getStateEvent(EventType.STATE_ROOM_MEMBER, QueryStringValue.Equals(session.myUserId))
|
return room.getStateEvent(EventType.STATE_ROOM_MEMBER, QueryStringValue.Equals(session.myUserId))
|
||||||
?.content
|
?.content
|
||||||
?.toModel<RoomMemberContent>()
|
?.toModel<RoomMemberContent>()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeDisplayNameForRoomSlashCommand(changeDisplayName: ParsedCommand.ChangeDisplayNameForRoom) {
|
private fun handleChangeDisplayNameForRoomSlashCommand(room: Room, changeDisplayName: ParsedCommand.ChangeDisplayNameForRoom) {
|
||||||
launchSlashCommandFlowSuspendable(changeDisplayName) {
|
launchSlashCommandFlowSuspendable(room, changeDisplayName) {
|
||||||
getMyRoomMemberContent()
|
getMyRoomMemberContent(room)
|
||||||
?.copy(displayName = changeDisplayName.displayName)
|
?.copy(displayName = changeDisplayName.displayName)
|
||||||
?.toContent()
|
?.toContent()
|
||||||
?.let {
|
?.let {
|
||||||
|
@ -831,15 +837,15 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeRoomAvatarSlashCommand(changeAvatar: ParsedCommand.ChangeRoomAvatar) {
|
private fun handleChangeRoomAvatarSlashCommand(room: Room, changeAvatar: ParsedCommand.ChangeRoomAvatar) {
|
||||||
launchSlashCommandFlowSuspendable(changeAvatar) {
|
launchSlashCommandFlowSuspendable(room, changeAvatar) {
|
||||||
room.stateService().sendStateEvent(EventType.STATE_ROOM_AVATAR, stateKey = "", RoomAvatarContent(changeAvatar.url).toContent())
|
room.stateService().sendStateEvent(EventType.STATE_ROOM_AVATAR, stateKey = "", RoomAvatarContent(changeAvatar.url).toContent())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeAvatarForRoomSlashCommand(changeAvatar: ParsedCommand.ChangeAvatarForRoom) {
|
private fun handleChangeAvatarForRoomSlashCommand(room: Room, changeAvatar: ParsedCommand.ChangeAvatarForRoom) {
|
||||||
launchSlashCommandFlowSuspendable(changeAvatar) {
|
launchSlashCommandFlowSuspendable(room, changeAvatar) {
|
||||||
getMyRoomMemberContent()
|
getMyRoomMemberContent(room)
|
||||||
?.copy(avatarUrl = changeAvatar.url)
|
?.copy(avatarUrl = changeAvatar.url)
|
||||||
?.toContent()
|
?.toContent()
|
||||||
?.let {
|
?.let {
|
||||||
|
@ -848,8 +854,8 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleIgnoreSlashCommand(ignore: ParsedCommand.IgnoreUser) {
|
private fun handleIgnoreSlashCommand(room: Room, ignore: ParsedCommand.IgnoreUser) {
|
||||||
launchSlashCommandFlowSuspendable(ignore) {
|
launchSlashCommandFlowSuspendable(room, ignore) {
|
||||||
session.userService().ignoreUserIds(listOf(ignore.userId))
|
session.userService().ignoreUserIds(listOf(ignore.userId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -858,15 +864,15 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandConfirmationRequest(unignore))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandConfirmationRequest(unignore))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSlashCommandConfirmed(action: MessageComposerAction.SlashCommandConfirmed) {
|
private fun handleSlashCommandConfirmed(room: Room, action: MessageComposerAction.SlashCommandConfirmed) {
|
||||||
when (action.parsedCommand) {
|
when (action.parsedCommand) {
|
||||||
is ParsedCommand.UnignoreUser -> handleUnignoreSlashCommandConfirmed(action.parsedCommand)
|
is ParsedCommand.UnignoreUser -> handleUnignoreSlashCommandConfirmed(room, action.parsedCommand)
|
||||||
else -> TODO("Not handled yet")
|
else -> TODO("Not handled yet")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUnignoreSlashCommandConfirmed(unignore: ParsedCommand.UnignoreUser) {
|
private fun handleUnignoreSlashCommandConfirmed(room: Room, unignore: ParsedCommand.UnignoreUser) {
|
||||||
launchSlashCommandFlowSuspendable(unignore) {
|
launchSlashCommandFlowSuspendable(room, unignore) {
|
||||||
session.userService().unIgnoreUserIds(listOf(unignore.userId))
|
session.userService().unIgnoreUserIds(listOf(unignore.userId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -875,7 +881,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(MessageComposerViewEvents.OpenRoomMemberProfile(whois.userId))
|
_viewEvents.post(MessageComposerViewEvents.OpenRoomMemberProfile(whois.userId))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendPrefixedMessage(prefix: String, message: CharSequence, rootThreadEventId: String?) {
|
private fun sendPrefixedMessage(room: Room, prefix: String, message: CharSequence, rootThreadEventId: String?) {
|
||||||
val sequence = buildString {
|
val sequence = buildString {
|
||||||
append(prefix)
|
append(prefix)
|
||||||
if (message.isNotEmpty()) {
|
if (message.isNotEmpty()) {
|
||||||
|
@ -891,7 +897,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
/**
|
/**
|
||||||
* Convert a send mode to a draft and save the draft.
|
* Convert a send mode to a draft and save the draft.
|
||||||
*/
|
*/
|
||||||
private fun handleSaveTextDraft(draft: String) = withState {
|
private fun handleSaveTextDraft(room: Room, draft: String) = withState {
|
||||||
session.coroutineScope.launch {
|
session.coroutineScope.launch {
|
||||||
when {
|
when {
|
||||||
it.sendMode is SendMode.Regular && !it.sendMode.fromSharing -> {
|
it.sendMode is SendMode.Regular && !it.sendMode.fromSharing -> {
|
||||||
|
@ -914,7 +920,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleStartRecordingVoiceMessage() {
|
private fun handleStartRecordingVoiceMessage(room: Room) {
|
||||||
try {
|
try {
|
||||||
audioMessageHelper.startRecording(room.roomId)
|
audioMessageHelper.startRecording(room.roomId)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
|
@ -922,7 +928,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEndRecordingVoiceMessage(isCancelled: Boolean, rootThreadEventId: String? = null) {
|
private fun handleEndRecordingVoiceMessage(room: Room, isCancelled: Boolean, rootThreadEventId: String? = null) {
|
||||||
audioMessageHelper.stopPlayback()
|
audioMessageHelper.stopPlayback()
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
audioMessageHelper.deleteRecording()
|
audioMessageHelper.deleteRecording()
|
||||||
|
@ -969,7 +975,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
audioMessageHelper.stopAllVoiceActions(deleteRecord)
|
audioMessageHelper.stopAllVoiceActions(deleteRecord)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleInitializeVoiceRecorder(attachmentData: ContentAttachmentData) {
|
private fun handleInitializeVoiceRecorder(room: Room, attachmentData: ContentAttachmentData) {
|
||||||
audioMessageHelper.initializeRecorder(room.roomId, attachmentData)
|
audioMessageHelper.initializeRecorder(room.roomId, attachmentData)
|
||||||
setState { copy(voiceRecordingUiState = VoiceMessageRecorderView.RecordingUiState.Draft) }
|
setState { copy(voiceRecordingUiState = VoiceMessageRecorderView.RecordingUiState.Draft) }
|
||||||
}
|
}
|
||||||
|
@ -990,7 +996,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
audioMessageHelper.movePlaybackTo(action.eventId, action.percentage, action.duration)
|
audioMessageHelper.movePlaybackTo(action.eventId, action.percentage, action.duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEntersBackground(composerText: String) {
|
private fun handleEntersBackground(room: Room, composerText: String) {
|
||||||
// Always stop all voice actions. It may be playing in timeline or active recording
|
// Always stop all voice actions. It may be playing in timeline or active recording
|
||||||
val playingAudioContent = audioMessageHelper.stopAllVoiceActions(deleteRecord = false)
|
val playingAudioContent = audioMessageHelper.stopAllVoiceActions(deleteRecord = false)
|
||||||
// TODO remove this when there will be a listening indicator outside of the timeline
|
// TODO remove this when there will be a listening indicator outside of the timeline
|
||||||
|
@ -1006,7 +1012,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
handleSaveTextDraft(draft = composerText)
|
handleSaveTextDraft(room = room, draft = composerText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1014,12 +1020,12 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(MessageComposerViewEvents.InsertUserDisplayName(action.userId))
|
_viewEvents.post(MessageComposerViewEvents.InsertUserDisplayName(action.userId))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launchSlashCommandFlowSuspendable(parsedCommand: ParsedCommand, block: suspend () -> Unit) {
|
private fun launchSlashCommandFlowSuspendable(room: Room, parsedCommand: ParsedCommand, block: suspend () -> Unit) {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val event = try {
|
val event = try {
|
||||||
block()
|
block()
|
||||||
popDraft()
|
popDraft(room)
|
||||||
MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)
|
MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
MessageComposerViewEvents.SlashCommandResultError(failure)
|
MessageComposerViewEvents.SlashCommandResultError(failure)
|
||||||
|
@ -1028,6 +1034,10 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onRoomError() = setState {
|
||||||
|
copy(isRoomError = true)
|
||||||
|
}
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory : MavericksAssistedViewModelFactory<MessageComposerViewModel, MessageComposerViewState> {
|
interface Factory : MavericksAssistedViewModelFactory<MessageComposerViewModel, MessageComposerViewState> {
|
||||||
override fun create(initialState: MessageComposerViewState): MessageComposerViewModel
|
override fun create(initialState: MessageComposerViewState): MessageComposerViewModel
|
||||||
|
|
|
@ -62,6 +62,7 @@ fun CanSendStatus.boolean(): Boolean {
|
||||||
|
|
||||||
data class MessageComposerViewState(
|
data class MessageComposerViewState(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
|
val isRoomError: Boolean = false,
|
||||||
val canSendMessage: CanSendStatus = CanSendStatus.Allowed,
|
val canSendMessage: CanSendStatus = CanSendStatus.Allowed,
|
||||||
val isSendButtonActive: Boolean = false,
|
val isSendButtonActive: Boolean = false,
|
||||||
val isSendButtonVisible: Boolean = false,
|
val isSendButtonVisible: Boolean = false,
|
||||||
|
@ -89,8 +90,8 @@ data class MessageComposerViewState(
|
||||||
|
|
||||||
val isVoiceMessageIdle = !isVoiceRecording
|
val isVoiceMessageIdle = !isVoiceRecording
|
||||||
|
|
||||||
val isComposerVisible = canSendMessage.boolean() && !isVoiceRecording
|
val isComposerVisible = canSendMessage.boolean() && !isVoiceRecording && !isRoomError
|
||||||
val isVoiceMessageRecorderVisible = canSendMessage.boolean() && !isSendButtonVisible
|
val isVoiceMessageRecorderVisible = canSendMessage.boolean() && !isSendButtonVisible && !isRoomError
|
||||||
|
|
||||||
constructor(args: TimelineArgs) : this(
|
constructor(args: TimelineArgs) : this(
|
||||||
roomId = args.roomId,
|
roomId = args.roomId,
|
||||||
|
|
|
@ -42,7 +42,6 @@ import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import com.google.android.material.shape.MaterialShapeDrawable
|
import com.google.android.material.shape.MaterialShapeDrawable
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.hideKeyboard
|
|
||||||
import im.vector.app.core.extensions.setTextIfDifferent
|
import im.vector.app.core.extensions.setTextIfDifferent
|
||||||
import im.vector.app.core.extensions.showKeyboard
|
import im.vector.app.core.extensions.showKeyboard
|
||||||
import im.vector.app.core.utils.DimensionConverter
|
import im.vector.app.core.utils.DimensionConverter
|
||||||
|
@ -133,8 +132,6 @@ class RichTextComposerLayout @JvmOverloads constructor(
|
||||||
views.bottomSheetHandle.isVisible = isFullScreen
|
views.bottomSheetHandle.isVisible = isFullScreen
|
||||||
if (isFullScreen) {
|
if (isFullScreen) {
|
||||||
editText.showKeyboard(true)
|
editText.showKeyboard(true)
|
||||||
} else {
|
|
||||||
editText.hideKeyboard()
|
|
||||||
}
|
}
|
||||||
this.isFullScreen = isFullScreen
|
this.isFullScreen = isFullScreen
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import android.os.Parcelable
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
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.core.services.VectorAndroidService
|
||||||
import im.vector.app.features.location.LocationData
|
import im.vector.app.features.location.LocationData
|
||||||
import im.vector.app.features.location.LocationTracker
|
import im.vector.app.features.location.LocationTracker
|
||||||
|
@ -105,7 +106,7 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca
|
||||||
if (foregroundModeStarted) {
|
if (foregroundModeStarted) {
|
||||||
NotificationManagerCompat.from(this).notify(FOREGROUND_SERVICE_NOTIFICATION_ID, notification)
|
NotificationManagerCompat.from(this).notify(FOREGROUND_SERVICE_NOTIFICATION_ID, notification)
|
||||||
} else {
|
} else {
|
||||||
startForeground(FOREGROUND_SERVICE_NOTIFICATION_ID, notification)
|
startForegroundCompat(FOREGROUND_SERVICE_NOTIFICATION_ID, notification)
|
||||||
foregroundModeStarted = true
|
foregroundModeStarted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -118,7 +118,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkQrCodeLoginCapability(homeServerUrl: String) {
|
private suspend fun checkQrCodeLoginCapability(config: HomeServerConnectionConfig) {
|
||||||
if (!vectorFeatures.isQrCodeLoginEnabled()) {
|
if (!vectorFeatures.isQrCodeLoginEnabled()) {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
|
@ -133,16 +133,12 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
viewModelScope.launch {
|
// check if selected server supports MSC3882 first
|
||||||
// check if selected server supports MSC3882 first
|
val canLoginWithQrCode = authenticationService.isQrLoginSupported(config)
|
||||||
homeServerConnectionConfigFactory.create(homeServerUrl)?.let {
|
setState {
|
||||||
val canLoginWithQrCode = authenticationService.isQrLoginSupported(it)
|
copy(
|
||||||
setState {
|
canLoginWithQrCode = canLoginWithQrCode
|
||||||
copy(
|
)
|
||||||
canLoginWithQrCode = canLoginWithQrCode
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -710,7 +706,6 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(OnboardingViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig")))
|
_viewEvents.post(OnboardingViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig")))
|
||||||
} else {
|
} else {
|
||||||
startAuthenticationFlow(action, homeServerConnectionConfig, serverTypeOverride, postAction)
|
startAuthenticationFlow(action, homeServerConnectionConfig, serverTypeOverride, postAction)
|
||||||
checkQrCodeLoginCapability(homeServerConnectionConfig.homeServerUri.toString())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -769,6 +764,8 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(OnboardingViewEvents.OutdatedHomeserver)
|
_viewEvents.post(OnboardingViewEvents.OutdatedHomeserver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkQrCodeLoginCapability(config)
|
||||||
|
|
||||||
when (trigger) {
|
when (trigger) {
|
||||||
is OnboardingAction.HomeServerChange.SelectHomeServer -> {
|
is OnboardingAction.HomeServerChange.SelectHomeServer -> {
|
||||||
onHomeServerSelected(config, serverTypeOverride, authResult)
|
onHomeServerSelected(config, serverTypeOverride, authResult)
|
||||||
|
|
|
@ -268,8 +268,6 @@ class VectorPreferences @Inject constructor(
|
||||||
private const val MEDIA_SAVING_1_MONTH = 2
|
private const val MEDIA_SAVING_1_MONTH = 2
|
||||||
private const val MEDIA_SAVING_FOREVER = 3
|
private const val MEDIA_SAVING_FOREVER = 3
|
||||||
|
|
||||||
private const val SETTINGS_UNKNOWN_DEVICE_DISMISSED_LIST = "SETTINGS_UNKNWON_DEVICE_DISMISSED_LIST"
|
|
||||||
|
|
||||||
private const val TAKE_PHOTO_VIDEO_MODE = "TAKE_PHOTO_VIDEO_MODE"
|
private const val TAKE_PHOTO_VIDEO_MODE = "TAKE_PHOTO_VIDEO_MODE"
|
||||||
|
|
||||||
private const val SETTINGS_LABS_ENABLE_LIVE_LOCATION = "SETTINGS_LABS_ENABLE_LIVE_LOCATION"
|
private const val SETTINGS_LABS_ENABLE_LIVE_LOCATION = "SETTINGS_LABS_ENABLE_LIVE_LOCATION"
|
||||||
|
@ -286,6 +284,9 @@ class VectorPreferences @Inject constructor(
|
||||||
// This key will be used to enable user for displaying live user info or not.
|
// This key will be used to enable user for displaying live user info or not.
|
||||||
const val SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO = "SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO"
|
const val SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO = "SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO"
|
||||||
|
|
||||||
|
const val SETTINGS_UNVERIFIED_SESSIONS_ALERT_LAST_SHOWN_MILLIS = "SETTINGS_UNVERIFIED_SESSIONS_ALERT_LAST_SHOWN_MILLIS_"
|
||||||
|
const val SETTINGS_NEW_LOGIN_ALERT_SHOWN_FOR_DEVICE = "SETTINGS_NEW_LOGIN_ALERT_SHOWN_FOR_DEVICE_"
|
||||||
|
|
||||||
// Possible values for TAKE_PHOTO_VIDEO_MODE
|
// Possible values for TAKE_PHOTO_VIDEO_MODE
|
||||||
const val TAKE_PHOTO_VIDEO_MODE_ALWAYS_ASK = 0
|
const val TAKE_PHOTO_VIDEO_MODE_ALWAYS_ASK = 0
|
||||||
const val TAKE_PHOTO_VIDEO_MODE_PHOTO = 1
|
const val TAKE_PHOTO_VIDEO_MODE_PHOTO = 1
|
||||||
|
@ -573,18 +574,6 @@ class VectorPreferences @Inject constructor(
|
||||||
return defaultPrefs.getBoolean(SETTINGS_PLAY_SHUTTER_SOUND_KEY, true)
|
return defaultPrefs.getBoolean(SETTINGS_PLAY_SHUTTER_SOUND_KEY, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun storeUnknownDeviceDismissedList(deviceIds: List<String>) {
|
|
||||||
defaultPrefs.edit(true) {
|
|
||||||
putStringSet(SETTINGS_UNKNOWN_DEVICE_DISMISSED_LIST, deviceIds.toSet())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getUnknownDeviceDismissedList(): List<String> {
|
|
||||||
return tryOrNull {
|
|
||||||
defaultPrefs.getStringSet(SETTINGS_UNKNOWN_DEVICE_DISMISSED_LIST, null)?.toList()
|
|
||||||
}.orEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the notification ringtone.
|
* Update the notification ringtone.
|
||||||
*
|
*
|
||||||
|
@ -1526,7 +1515,27 @@ class VectorPreferences @Inject constructor(
|
||||||
|
|
||||||
fun setIpAddressVisibilityInDeviceManagerScreens(isVisible: Boolean) {
|
fun setIpAddressVisibilityInDeviceManagerScreens(isVisible: Boolean) {
|
||||||
defaultPrefs.edit {
|
defaultPrefs.edit {
|
||||||
putBoolean(VectorPreferences.SETTINGS_SESSION_MANAGER_SHOW_IP_ADDRESS, isVisible)
|
putBoolean(SETTINGS_SESSION_MANAGER_SHOW_IP_ADDRESS, isVisible)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getUnverifiedSessionsAlertLastShownMillis(deviceId: String): Long {
|
||||||
|
return defaultPrefs.getLong(SETTINGS_UNVERIFIED_SESSIONS_ALERT_LAST_SHOWN_MILLIS + deviceId, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setUnverifiedSessionsAlertLastShownMillis(deviceId: String, lastShownMillis: Long) {
|
||||||
|
defaultPrefs.edit {
|
||||||
|
putLong(SETTINGS_UNVERIFIED_SESSIONS_ALERT_LAST_SHOWN_MILLIS + deviceId, lastShownMillis)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isNewLoginAlertShownForDevice(deviceId: String): Boolean {
|
||||||
|
return defaultPrefs.getBoolean(SETTINGS_NEW_LOGIN_ALERT_SHOWN_FOR_DEVICE + deviceId, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setNewLoginAlertShownForDevice(deviceId: String) {
|
||||||
|
defaultPrefs.edit {
|
||||||
|
putBoolean(SETTINGS_NEW_LOGIN_ALERT_SHOWN_FOR_DEVICE + deviceId, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ import im.vector.app.R
|
||||||
import im.vector.app.core.error.ErrorFormatter
|
import im.vector.app.core.error.ErrorFormatter
|
||||||
import im.vector.app.core.extensions.singletonEntryPoint
|
import im.vector.app.core.extensions.singletonEntryPoint
|
||||||
import im.vector.app.core.platform.VectorBaseActivity
|
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.core.utils.toast
|
||||||
import im.vector.app.features.analytics.AnalyticsTracker
|
import im.vector.app.features.analytics.AnalyticsTracker
|
||||||
import im.vector.app.features.analytics.plan.MobileScreen
|
import im.vector.app.features.analytics.plan.MobileScreen
|
||||||
|
@ -61,6 +63,19 @@ abstract class VectorSettingsBaseFragment : ScPreferenceFragment(), MavericksVie
|
||||||
protected lateinit var session: Session
|
protected lateinit var session: Session
|
||||||
protected lateinit var errorFormatter: ErrorFormatter
|
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
|
* Views
|
||||||
* ========================================================================================== */
|
* ========================================================================================== */
|
||||||
|
@ -149,7 +164,7 @@ abstract class VectorSettingsBaseFragment : ScPreferenceFragment(), MavericksVie
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun displayErrorDialog(throwable: Throwable) {
|
protected fun displayErrorDialog(throwable: Throwable?) {
|
||||||
displayErrorDialog(errorFormatter.toHumanReadable(throwable))
|
displayErrorDialog(errorFormatter.toHumanReadable(throwable))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,9 +85,9 @@ abstract class DeviceItem : VectorEpoxyModel<DeviceItem.Holder>(R.layout.item_de
|
||||||
trusted
|
trusted
|
||||||
)
|
)
|
||||||
|
|
||||||
holder.trustIcon.render(shield)
|
holder.trustIcon.renderDeviceShield(shield)
|
||||||
} else {
|
} else {
|
||||||
holder.trustIcon.render(null)
|
holder.trustIcon.renderDeviceShield(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
val detailedModeLabels = listOf(
|
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.auth.PendingAuthHandler
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
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.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.SignoutSessionsReAuthNeeded
|
||||||
import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsUseCase
|
import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsUseCase
|
||||||
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
|
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
|
||||||
|
@ -48,7 +47,6 @@ class DevicesViewModel @AssistedInject constructor(
|
||||||
private val refreshDevicesOnCryptoDevicesChangeUseCase: RefreshDevicesOnCryptoDevicesChangeUseCase,
|
private val refreshDevicesOnCryptoDevicesChangeUseCase: RefreshDevicesOnCryptoDevicesChangeUseCase,
|
||||||
private val checkIfCurrentSessionCanBeVerifiedUseCase: CheckIfCurrentSessionCanBeVerifiedUseCase,
|
private val checkIfCurrentSessionCanBeVerifiedUseCase: CheckIfCurrentSessionCanBeVerifiedUseCase,
|
||||||
private val signoutSessionsUseCase: SignoutSessionsUseCase,
|
private val signoutSessionsUseCase: SignoutSessionsUseCase,
|
||||||
private val interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase,
|
|
||||||
private val pendingAuthHandler: PendingAuthHandler,
|
private val pendingAuthHandler: PendingAuthHandler,
|
||||||
refreshDevicesUseCase: RefreshDevicesUseCase,
|
refreshDevicesUseCase: RefreshDevicesUseCase,
|
||||||
private val vectorPreferences: VectorPreferences,
|
private val vectorPreferences: VectorPreferences,
|
||||||
|
|
|
@ -53,6 +53,7 @@ import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationVie
|
||||||
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
|
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.settings.devices.v2.signout.BuildConfirmSignoutDialogUseCase
|
||||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
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 org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -99,6 +100,7 @@ class VectorSettingsDevicesFragment :
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
initWaitingView()
|
initWaitingView()
|
||||||
|
initCurrentSessionHeaderView()
|
||||||
initOtherSessionsHeaderView()
|
initOtherSessionsHeaderView()
|
||||||
initOtherSessionsView()
|
initOtherSessionsView()
|
||||||
initSecurityRecommendationsView()
|
initSecurityRecommendationsView()
|
||||||
|
@ -139,6 +141,18 @@ class VectorSettingsDevicesFragment :
|
||||||
views.waitingView.waitingStatusText.isVisible = true
|
views.waitingView.waitingStatusText.isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initCurrentSessionHeaderView() {
|
||||||
|
views.deviceListHeaderCurrentSession.setOnMenuItemClickListener { menuItem ->
|
||||||
|
when (menuItem.itemId) {
|
||||||
|
R.id.currentSessionHeaderSignoutOtherSessions -> {
|
||||||
|
confirmMultiSignoutOtherSessions()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun initOtherSessionsHeaderView() {
|
private fun initOtherSessionsHeaderView() {
|
||||||
views.deviceListHeaderOtherSessions.setOnMenuItemClickListener { menuItem ->
|
views.deviceListHeaderOtherSessions.setOnMenuItemClickListener { menuItem ->
|
||||||
when (menuItem.itemId) {
|
when (menuItem.itemId) {
|
||||||
|
@ -247,7 +261,7 @@ class VectorSettingsDevicesFragment :
|
||||||
val otherDevices = devices?.filter { it.deviceInfo.deviceId != currentDeviceId }
|
val otherDevices = devices?.filter { it.deviceInfo.deviceId != currentDeviceId }
|
||||||
|
|
||||||
renderSecurityRecommendations(state.inactiveSessionsCount, state.unverifiedSessionsCount, isCurrentSessionVerified)
|
renderSecurityRecommendations(state.inactiveSessionsCount, state.unverifiedSessionsCount, isCurrentSessionVerified)
|
||||||
renderCurrentDevice(currentDeviceInfo)
|
renderCurrentSessionView(currentDeviceInfo, hasOtherDevices = otherDevices?.isNotEmpty().orFalse())
|
||||||
renderOtherSessionsView(otherDevices, state.isShowingIpAddress)
|
renderOtherSessionsView(otherDevices, state.isShowingIpAddress)
|
||||||
} else {
|
} else {
|
||||||
hideSecurityRecommendations()
|
hideSecurityRecommendations()
|
||||||
|
@ -310,11 +324,11 @@ class VectorSettingsDevicesFragment :
|
||||||
hideOtherSessionsView()
|
hideOtherSessionsView()
|
||||||
} else {
|
} else {
|
||||||
views.deviceListHeaderOtherSessions.isVisible = true
|
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 multiSignoutItem = views.deviceListHeaderOtherSessions.menu.findItem(R.id.otherSessionsHeaderMultiSignout)
|
||||||
val nbDevices = otherDevices.size
|
val nbDevices = otherDevices.size
|
||||||
multiSignoutItem.title = stringProvider.getQuantityString(R.plurals.device_manager_other_sessions_multi_signout_all, nbDevices, nbDevices)
|
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
|
views.deviceListOtherSessions.isVisible = true
|
||||||
val devices = if (isShowingIpAddress) otherDevices else otherDevices.map { it.copy(deviceInfo = it.deviceInfo.copy(lastSeenIp = null)) }
|
val devices = if (isShowingIpAddress) otherDevices else otherDevices.map { it.copy(deviceInfo = it.deviceInfo.copy(lastSeenIp = null)) }
|
||||||
views.deviceListOtherSessions.render(
|
views.deviceListOtherSessions.render(
|
||||||
|
@ -327,7 +341,7 @@ class VectorSettingsDevicesFragment :
|
||||||
} else {
|
} else {
|
||||||
stringProvider.getString(R.string.device_manager_other_sessions_show_ip_address)
|
stringProvider.getString(R.string.device_manager_other_sessions_show_ip_address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hideOtherSessionsView() {
|
private fun hideOtherSessionsView() {
|
||||||
|
@ -335,9 +349,13 @@ class VectorSettingsDevicesFragment :
|
||||||
views.deviceListOtherSessions.isVisible = false
|
views.deviceListOtherSessions.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderCurrentDevice(currentDeviceInfo: DeviceFullInfo?) {
|
private fun renderCurrentSessionView(currentDeviceInfo: DeviceFullInfo?, hasOtherDevices: Boolean) {
|
||||||
currentDeviceInfo?.let {
|
currentDeviceInfo?.let {
|
||||||
views.deviceListHeaderCurrentSession.isVisible = true
|
views.deviceListHeaderCurrentSession.isVisible = true
|
||||||
|
val colorDestructive = colorProvider.getColorFromAttribute(R.attr.colorError)
|
||||||
|
val signoutOtherSessionsItem = views.deviceListHeaderCurrentSession.menu.findItem(R.id.currentSessionHeaderSignoutOtherSessions)
|
||||||
|
signoutOtherSessionsItem.setTextColor(colorDestructive)
|
||||||
|
signoutOtherSessionsItem.isVisible = hasOtherDevices
|
||||||
views.deviceListCurrentSession.isVisible = true
|
views.deviceListCurrentSession.isVisible = true
|
||||||
val viewState = SessionInfoViewState(
|
val viewState = SessionInfoViewState(
|
||||||
isCurrentSession = true,
|
isCurrentSession = true,
|
||||||
|
|
|
@ -97,7 +97,7 @@ abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.la
|
||||||
} else {
|
} else {
|
||||||
setDeviceTypeIconUseCase.execute(deviceType, holder.otherSessionDeviceTypeImageView, stringProvider)
|
setDeviceTypeIconUseCase.execute(deviceType, holder.otherSessionDeviceTypeImageView, stringProvider)
|
||||||
}
|
}
|
||||||
holder.otherSessionVerificationStatusImageView.render(roomEncryptionTrustLevel)
|
holder.otherSessionVerificationStatusImageView.renderDeviceShield(roomEncryptionTrustLevel)
|
||||||
holder.otherSessionNameTextView.text = sessionName
|
holder.otherSessionNameTextView.text = sessionName
|
||||||
holder.otherSessionDescriptionTextView.text = sessionDescription
|
holder.otherSessionDescriptionTextView.text = sessionDescription
|
||||||
sessionDescriptionColor?.let {
|
sessionDescriptionColor?.let {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue