Merge remote-tracking branch 'origin/develop' into task/eric/space-switching-unit-tests
# Conflicts: # vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
This commit is contained in:
commit
a909779e08
@ -770,7 +770,7 @@ ij_kotlin_align_multiline_extends_list = false
|
|||||||
ij_kotlin_align_multiline_method_parentheses = false
|
ij_kotlin_align_multiline_method_parentheses = false
|
||||||
ij_kotlin_align_multiline_parameters = true
|
ij_kotlin_align_multiline_parameters = true
|
||||||
ij_kotlin_align_multiline_parameters_in_calls = false
|
ij_kotlin_align_multiline_parameters_in_calls = false
|
||||||
ij_kotlin_allow_trailing_comma = false
|
ij_kotlin_allow_trailing_comma = true
|
||||||
ij_kotlin_allow_trailing_comma_on_call_site = false
|
ij_kotlin_allow_trailing_comma_on_call_site = false
|
||||||
ij_kotlin_assignment_wrap = off
|
ij_kotlin_assignment_wrap = off
|
||||||
ij_kotlin_blank_lines_after_class_header = 0
|
ij_kotlin_blank_lines_after_class_header = 0
|
||||||
|
1
changelog.d/6200.bugfix
Normal file
1
changelog.d/6200.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
Fixes room not being in space after upgrade
|
1
changelog.d/6437.feature
Normal file
1
changelog.d/6437.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
[Location sharing] - Delete action on a live message
|
1
changelog.d/6487.feature
Normal file
1
changelog.d/6487.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
[Timeline] - Collapse redacted events
|
1
changelog.d/6537.bugfix
Normal file
1
changelog.d/6537.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
[Location Share] - Wrong room live location status bar visibility in timeline
|
1
changelog.d/6585.bugfix
Normal file
1
changelog.d/6585.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
Fix backup saving several times the same keys
|
1
changelog.d/6587.bugfix
Normal file
1
changelog.d/6587.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
Check user power level before sharing live location
|
1
changelog.d/6596.bugfix
Normal file
1
changelog.d/6596.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
[Location Share] - Live is considered as ended while still active
|
@ -1 +1 @@
|
|||||||
مُحادثة آمنة لا مركزية و VoIP. حافظ على بياناتك آمنة من الأطراف الثالثة.
|
برنامج المراسلة الجماعية - الرسائل المشفرة والدردشة الجماعية ومكالمات الفيديو
|
||||||
|
@ -1 +1 @@
|
|||||||
Element (Riot.im سابقًا)
|
إيليمنت - تطبيق محادثات أمن
|
||||||
|
2
fastlane/metadata/android/cs-CZ/changelogs/40104260.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40104260.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Hlavní změny v této verzi: Podpora UnifiedPush a možnost používat push bez FCM.
|
||||||
|
Úplný seznam změn: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/cs-CZ/changelogs/40104270.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40104270.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Hlavní změny v této verzi: Opravy různých chyb a vylepšení stability.
|
||||||
|
Úplný seznam změn: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/et/changelogs/40104260.txt
Normal file
2
fastlane/metadata/android/et/changelogs/40104260.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Põhilised muutused selles versioonis: võimalus kasutada tõukesõnumite jaoks FCM'i asemel UnifiedPush'i.
|
||||||
|
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/et/changelogs/40104270.txt
Normal file
2
fastlane/metadata/android/et/changelogs/40104270.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Põhilised muutused selles versioonis: erinevate vigade parandused ja stabiilsust edendavad kohendused.
|
||||||
|
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/fa/changelogs/40104260.txt
Normal file
2
fastlane/metadata/android/fa/changelogs/40104260.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
تغییرات عمده در این نگارش: استفاده از UnifiedPush و اجازه به کاربر برای داشتن آگاهیهای ارسالی بدون FCM.
|
||||||
|
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/fa/changelogs/40104270.txt
Normal file
2
fastlane/metadata/android/fa/changelogs/40104270.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
تغییرات عمده در این نگارش: رفع اشکالهای مختلف و بهبودهای پایداری.
|
||||||
|
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/fr-FR/changelogs/40104260.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/40104260.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Principaux changements pour cette version : Utilisation de UnifiedPush qui permet aux utilisateur d’utiliser « push » sans FCM.
|
||||||
|
Intégralité des changements : https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/fr-FR/changelogs/40104270.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/40104270.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Principaux changements pour cette version : Plusieurs corrections de bogues et d’améliorations de stabilité.
|
||||||
|
Intégralité des changements : https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/gl/changelogs/40104160.txt
Normal file
2
fastlane/metadata/android/gl/changelogs/40104160.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Principais cambios nesta versión: melloras na xestión das mensaxes cifradas. Varios arranxos e melloras na estabilidade.
|
||||||
|
Rexistro completo dos cambios: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/gl/changelogs/40104180.txt
Normal file
2
fastlane/metadata/android/gl/changelogs/40104180.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Principais cambios nesta versión: varios arranxos e melloras na estabilidade.
|
||||||
|
Rexistro completo dos cambios: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/gl/changelogs/40104190.txt
Normal file
2
fastlane/metadata/android/gl/changelogs/40104190.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Principais cambios nesta versión: varios arranxos e melloras na estabilidade.
|
||||||
|
Rexistro completo dos cambios: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/gl/changelogs/40104200.txt
Normal file
2
fastlane/metadata/android/gl/changelogs/40104200.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Principais cambios nesta versión: varios arranxos e melloras na estabilidade.
|
||||||
|
Rexistro completo dos cambios: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/gl/changelogs/40104220.txt
Normal file
2
fastlane/metadata/android/gl/changelogs/40104220.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Principais cambios nesta versión: varios arranxos e melloras na estabilidade.
|
||||||
|
Rexistro completo dos cambios: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/gl/changelogs/40104230.txt
Normal file
2
fastlane/metadata/android/gl/changelogs/40104230.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Principais cambios nesta versión: varios arranxos e melloras na estabilidade.
|
||||||
|
Rexistro completo dos cambios: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/gl/changelogs/40104240.txt
Normal file
2
fastlane/metadata/android/gl/changelogs/40104240.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Principais cambios nesta versión: varios arranxos e melloras na estabilidade.
|
||||||
|
Rexistro completo dos cambios: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/gl/changelogs/40104250.txt
Normal file
2
fastlane/metadata/android/gl/changelogs/40104250.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Principais cambios nesta versión: varios arranxos e melloras na estabilidade.
|
||||||
|
Rexistro completo dos cambios: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/gl/changelogs/40104260.txt
Normal file
2
fastlane/metadata/android/gl/changelogs/40104260.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Principais cambios nesta versión: Utiliza UnifiedPush e permite á usuaria obter notificacións sen FCM.
|
||||||
|
Rexistro completo dos cambios: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/gl/changelogs/40104270.txt
Normal file
2
fastlane/metadata/android/gl/changelogs/40104270.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Principais cambios nesta versión: varios arranxos e melloras na estabilidade.
|
||||||
|
Rexistro completo dos cambios: https://github.com/vector-im/element-android/releases
|
42
fastlane/metadata/android/gl/full_description.txt
Normal file
42
fastlane/metadata/android/gl/full_description.txt
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
Element é tanto unha mensaxería segura e unha app de productividade para o traballo en equipo, perfecta para conversas de grupos con traballo remoto. Esta app de chat usa cifrado de extremo-a-extremo para proporcionar video conferencias seguras, compartición de ficheiros e chamadas de voz.
|
||||||
|
|
||||||
|
<b>Características de Element incluídas:</b>
|
||||||
|
- Ferramentas avanzadas para a comunicación en liña
|
||||||
|
- Mensaxes completamente cifradas para permitir a comunicación corporativa, incluso para traballo remoto
|
||||||
|
- Chat descentralizado baseado no sistema de código aberto Matrix
|
||||||
|
- Compartición segura de ficheiros con datos cifrados na xestión de proxectos
|
||||||
|
- Chats de vídeo con Voz sobre IP en compartición de pantalla
|
||||||
|
- Integración doada con outras ferramentas de colaboración en liña, ferramentas de xestión de proxectos, servizos VoIP e outras apps de mensaxería para equipos
|
||||||
|
|
||||||
|
Element é completamente diferente a outras apps de mensaxería e traballo en equipo. Funciona grazas a Matrix, unha rede aberta para mensaxería segura e descentralizada. Permite a hospedaxe na infraestructura propia para proporcionar o maior grao de propiedade e control sobre os teus datos e mensaxes.
|
||||||
|
|
||||||
|
<b>Mensaxería privada e cifrada</b>
|
||||||
|
Element protéxete da publicidade non solicitada, minería de datos e burbullas de contido. Tamén protexe os teus datos, chamadas de vídeo e voz cifradas de extremo-a-extremo así como verificación con sinatura dos dispositivos.
|
||||||
|
|
||||||
|
Element pon baixo o teu control a túa privacidade permitíndoche comunicarte de xeito seguro con calquera a través da rede Matrix, ou en outras ferramentas de colaboración para empresas ao estar integrada en apps como Slack.
|
||||||
|
|
||||||
|
<b>Element na túa infraestructura</b>
|
||||||
|
Para un maior control sobre os teus datos sensibles e comunicacións, podes hospedar Element ou elexir calquera hóspede baseado en Matrix - un estándar para comunicación descentralizado e de código aberto. Element proporciona privacidade e seguridade así como flexibilidade para a integración.
|
||||||
|
|
||||||
|
<b>Os teus datos</b>
|
||||||
|
Ti decides onde gardas os teus datos e mensaxes. Sen o risco da minería de datos ou acceso por terceiras partes.
|
||||||
|
|
||||||
|
Element ponte ao mando de varios xeitos:
|
||||||
|
1. Consigue unha conta gratuíta no servidor público matrix.org hospedado polos desenvolvedores de Matrix, ou elixe entre miles de servidores públicos xestionados por voluntarias
|
||||||
|
2. Hospeda a túa conta na túa propia infraestructura IT
|
||||||
|
3. Crea unha conta nun servidor personalizado simplemente subscribíndote á plataforma de hospedaxe Element Matrix Services
|
||||||
|
|
||||||
|
<b>Mensaxería e Colaboración abertas</b>
|
||||||
|
Podes conversar con calquera na rede Matrix, tanto se usan Element ou outra app Matrix ou incluso unha mensaxería diferente.
|
||||||
|
|
||||||
|
<b>Super segura</b>
|
||||||
|
Cifrado real de extremo-a-extremo (só quen participa na conversa pode descifrar as mensaxes), e verificación con sinatura cruzada dos dispositivos.
|
||||||
|
|
||||||
|
<b>Comunicación e integración completas</b>
|
||||||
|
Mensaxería, chamadas de voz e vídeo, compartición de ficheiros, compartición de pantalla e moitas máis integracións, bots e widgets. Crea salas, comunidades, mantén o contacto e saca adiante o traballo.
|
||||||
|
|
||||||
|
<b>Continúa onde o deixaches</b>
|
||||||
|
Sigue en contacto alá onde estés grazas ao historial sincronizado de mensaxería entre tódolos dispositivos e na web https://app.element.io
|
||||||
|
|
||||||
|
<b>Código aberto</b>
|
||||||
|
Element Android é un proxecto de código aberto, hospedado en GitHub. Informa de fallos e/ou contribúe ao seu desenvolvemento en https://github.com/vector-im/element-android
|
1
fastlane/metadata/android/gl/short_description.txt
Normal file
1
fastlane/metadata/android/gl/short_description.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Mensaxería en grupo - mensaxería cifrada, chat en grupo e videochamadas
|
1
fastlane/metadata/android/gl/title.txt
Normal file
1
fastlane/metadata/android/gl/title.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Element - Mensaxería Segura
|
2
fastlane/metadata/android/id/changelogs/40104260.txt
Normal file
2
fastlane/metadata/android/id/changelogs/40104260.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Perubahan utama dalam versi ini: Dukungan UnifiedPush, memungkinkan pengguna untuk diberitahukan tanpa FCM.
|
||||||
|
Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/id/changelogs/40104270.txt
Normal file
2
fastlane/metadata/android/id/changelogs/40104270.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Perubahan utama dalam versi ini: Banyak perbaikan kutu dan perbaikan stabilitas.
|
||||||
|
Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/it-IT/changelogs/40104260.txt
Normal file
2
fastlane/metadata/android/it-IT/changelogs/40104260.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Modifiche principali in questa versione: utilizza UnifiedPush e consente all'utente di avere notifiche push senza FCM.
|
||||||
|
Cronologia completa: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/it-IT/changelogs/40104270.txt
Normal file
2
fastlane/metadata/android/it-IT/changelogs/40104270.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Modifiche principali in questa versione: varie correzioni di errori e miglioramenti della stabilità.
|
||||||
|
Cronologia completa: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/sk/changelogs/40104260.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40104260.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Hlavné zmeny v tejto verzii: Použitie UnifiedPush a umožňuje používateľovi používať push bez FCM.
|
||||||
|
Úplný zoznam zmien: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/sk/changelogs/40104270.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40104270.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Hlavné zmeny v tejto verzii: Rôzne opravy chýb a vylepšenia stability.
|
||||||
|
Úplný zoznam zmien: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/sv-SE/changelogs/40104260.txt
Normal file
2
fastlane/metadata/android/sv-SE/changelogs/40104260.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Huvudsakliga ändringar i den här versionen: Använd UnifiedPush och tillåt användare att ha push utan FCM.
|
||||||
|
Full ändringslogg: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/sv-SE/changelogs/40104270.txt
Normal file
2
fastlane/metadata/android/sv-SE/changelogs/40104270.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Huvudsakliga ändringar i den här versionen: Diverse buggfixar och stabilitetsförbättringar.
|
||||||
|
Full ändringslogg: https://github.com/vector-im/element-android/releases
|
@ -1,2 +1,2 @@
|
|||||||
Основні зміни в цій версії: поліпшення VoIP (аудіо та відео дзвінки в DM) та виправлення помилок!
|
Основні зміни в цій версії: поліпшення VoIP (аудіо та відеовиклики у ПП) та виправлення помилок!
|
||||||
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.1.0
|
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.1.0
|
||||||
|
2
fastlane/metadata/android/uk/changelogs/40104260.txt
Normal file
2
fastlane/metadata/android/uk/changelogs/40104260.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Основні зміни в цій версії: Застосовано UnifiedPush і дозволено користувачам отримувати push-сповіщення без FCM.
|
||||||
|
Перелік усіх змін: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/uk/changelogs/40104270.txt
Normal file
2
fastlane/metadata/android/uk/changelogs/40104270.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Основні зміни в цій версії: Усунуто різні вади й поліпшено стабільність.
|
||||||
|
Перелік усіх змін: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/zh-TW/changelogs/40104260.txt
Normal file
2
fastlane/metadata/android/zh-TW/changelogs/40104260.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
此版本中的主要變動:使用 UnifiedPush 並允許使用者在沒有 FCM 的情況下推送。
|
||||||
|
完整的變更紀錄:https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/zh-TW/changelogs/40104270.txt
Normal file
2
fastlane/metadata/android/zh-TW/changelogs/40104270.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
此版本中的主要變動:多個臭蟲修復與穩定性改善。
|
||||||
|
完整的變更紀錄:https://github.com/vector-im/element-android/releases
|
@ -199,7 +199,7 @@ dependencies {
|
|||||||
implementation libs.apache.commonsImaging
|
implementation libs.apache.commonsImaging
|
||||||
|
|
||||||
// Phone number https://github.com/google/libphonenumber
|
// Phone number https://github.com/google/libphonenumber
|
||||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.51'
|
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.52'
|
||||||
|
|
||||||
testImplementation libs.tests.junit
|
testImplementation libs.tests.junit
|
||||||
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
||||||
|
@ -24,7 +24,6 @@ import org.junit.Assert.assertNotNull
|
|||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
import org.junit.Ignore
|
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
@ -56,7 +55,6 @@ import java.util.concurrent.CountDownLatch
|
|||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@Ignore
|
|
||||||
class KeysBackupTest : InstrumentedTest {
|
class KeysBackupTest : InstrumentedTest {
|
||||||
|
|
||||||
@get:Rule val rule = RetryTestRule(3)
|
@get:Rule val rule = RetryTestRule(3)
|
||||||
|
@ -202,7 +202,7 @@ data class Event(
|
|||||||
* It will return a decrypted text message or an empty string otherwise.
|
* It will return a decrypted text message or an empty string otherwise.
|
||||||
*/
|
*/
|
||||||
fun getDecryptedTextSummary(): String? {
|
fun getDecryptedTextSummary(): String? {
|
||||||
if (isRedacted()) return "Message Deleted"
|
if (isRedacted()) return "Message removed"
|
||||||
val text = getDecryptedValue() ?: run {
|
val text = getDecryptedValue() ?: run {
|
||||||
if (isPoll()) {
|
if (isPoll()) {
|
||||||
return getPollQuestion() ?: "created a poll."
|
return getPollQuestion() ?: "created a poll."
|
||||||
@ -371,6 +371,8 @@ fun Event.isPoll(): Boolean = getClearType() in EventType.POLL_START || getClear
|
|||||||
|
|
||||||
fun Event.isSticker(): Boolean = getClearType() == EventType.STICKER
|
fun Event.isSticker(): Boolean = getClearType() == EventType.STICKER
|
||||||
|
|
||||||
|
fun Event.isLiveLocation(): Boolean = getClearType() in EventType.STATE_ROOM_BEACON_INFO
|
||||||
|
|
||||||
fun Event.getRelationContent(): RelationDefaultContent? {
|
fun Event.getRelationContent(): RelationDefaultContent? {
|
||||||
return if (isEncrypted()) {
|
return if (isEncrypted()) {
|
||||||
content.toModel<EncryptedEventContent>()?.relatesTo
|
content.toModel<EncryptedEventContent>()?.relatesTo
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.api.session.room.location
|
package org.matrix.android.sdk.api.session.room.location
|
||||||
|
|
||||||
import androidx.annotation.MainThread
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
|
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
@ -59,16 +58,21 @@ interface LocationSharingService {
|
|||||||
*/
|
*/
|
||||||
suspend fun stopLiveLocationShare(): UpdateLiveLocationShareResult
|
suspend fun stopLiveLocationShare(): UpdateLiveLocationShareResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redact (delete) the live associated to the given beacon info event id.
|
||||||
|
* @param beaconInfoEventId event id of the initial beacon info state event
|
||||||
|
* @param reason Optional reason string
|
||||||
|
*/
|
||||||
|
suspend fun redactLiveLocationShare(beaconInfoEventId: String, reason: String?)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a LiveData on the list of current running live location shares.
|
* Returns a LiveData on the list of current running live location shares.
|
||||||
*/
|
*/
|
||||||
@MainThread
|
|
||||||
fun getRunningLiveLocationShareSummaries(): LiveData<List<LiveLocationShareAggregatedSummary>>
|
fun getRunningLiveLocationShareSummaries(): LiveData<List<LiveLocationShareAggregatedSummary>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a LiveData on the live location share summary with the given eventId.
|
* Returns a LiveData on the live location share summary with the given eventId.
|
||||||
* @param beaconInfoEventId event id of the initial beacon info state event
|
* @param beaconInfoEventId event id of the initial beacon info state event
|
||||||
*/
|
*/
|
||||||
@MainThread
|
|
||||||
fun getLiveLocationShareSummary(beaconInfoEventId: String): LiveData<Optional<LiveLocationShareAggregatedSummary>>
|
fun getLiveLocationShareSummary(beaconInfoEventId: String): LiveData<Optional<LiveLocationShareAggregatedSummary>>
|
||||||
}
|
}
|
||||||
|
@ -33,5 +33,7 @@ enum class VersioningState {
|
|||||||
/**
|
/**
|
||||||
* The room has been upgraded, and the new room has been joined.
|
* The room has been upgraded, and the new room has been joined.
|
||||||
*/
|
*/
|
||||||
UPGRADED_ROOM_JOINED,
|
UPGRADED_ROOM_JOINED;
|
||||||
|
|
||||||
|
fun isUpgraded() = this != NONE
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType
|
|||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
import org.matrix.android.sdk.api.session.events.model.getRelationContent
|
import org.matrix.android.sdk.api.session.events.model.getRelationContent
|
||||||
import org.matrix.android.sdk.api.session.events.model.isEdition
|
import org.matrix.android.sdk.api.session.events.model.isEdition
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.isLiveLocation
|
||||||
import org.matrix.android.sdk.api.session.events.model.isPoll
|
import org.matrix.android.sdk.api.session.events.model.isPoll
|
||||||
import org.matrix.android.sdk.api.session.events.model.isReply
|
import org.matrix.android.sdk.api.session.events.model.isReply
|
||||||
import org.matrix.android.sdk.api.session.events.model.isSticker
|
import org.matrix.android.sdk.api.session.events.model.isSticker
|
||||||
@ -165,6 +166,10 @@ fun TimelineEvent.isSticker(): Boolean {
|
|||||||
return root.isSticker()
|
return root.isSticker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun TimelineEvent.isLiveLocation(): Boolean {
|
||||||
|
return root.isLiveLocation()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not the event is a root thread event.
|
* Returns whether or not the event is a root thread event.
|
||||||
*/
|
*/
|
||||||
|
@ -37,9 +37,9 @@ import org.matrix.android.sdk.internal.auth.db.PendingSessionData
|
|||||||
* This class execute the registration request and is responsible to keep the session of interactive authentication.
|
* This class execute the registration request and is responsible to keep the session of interactive authentication.
|
||||||
*/
|
*/
|
||||||
internal class DefaultRegistrationWizard(
|
internal class DefaultRegistrationWizard(
|
||||||
authAPI: AuthAPI,
|
authAPI: AuthAPI,
|
||||||
private val sessionCreator: SessionCreator,
|
private val sessionCreator: SessionCreator,
|
||||||
private val pendingSessionStore: PendingSessionStore
|
private val pendingSessionStore: PendingSessionStore
|
||||||
) : RegistrationWizard {
|
) : RegistrationWizard {
|
||||||
|
|
||||||
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
|
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
|
||||||
@ -74,20 +74,20 @@ internal class DefaultRegistrationWizard(
|
|||||||
initialDeviceDisplayName: String?
|
initialDeviceDisplayName: String?
|
||||||
): RegistrationResult {
|
): RegistrationResult {
|
||||||
val params = RegistrationParams(
|
val params = RegistrationParams(
|
||||||
username = userName,
|
username = userName,
|
||||||
password = password,
|
password = password,
|
||||||
initialDeviceDisplayName = initialDeviceDisplayName
|
initialDeviceDisplayName = initialDeviceDisplayName
|
||||||
)
|
)
|
||||||
return performRegistrationRequest(params, LoginType.PASSWORD)
|
return performRegistrationRequest(params, LoginType.PASSWORD)
|
||||||
.also {
|
.also {
|
||||||
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
|
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
|
||||||
.also { pendingSessionStore.savePendingSessionData(it) }
|
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun performReCaptcha(response: String): RegistrationResult {
|
override suspend fun performReCaptcha(response: String): RegistrationResult {
|
||||||
val safeSession = pendingSessionData.currentSession
|
val safeSession = pendingSessionData.currentSession
|
||||||
?: throw IllegalStateException("developer error, call createAccount() method first")
|
?: throw IllegalStateException("developer error, call createAccount() method first")
|
||||||
|
|
||||||
val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response))
|
val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response))
|
||||||
return performRegistrationRequest(params, LoginType.PASSWORD)
|
return performRegistrationRequest(params, LoginType.PASSWORD)
|
||||||
@ -95,7 +95,7 @@ internal class DefaultRegistrationWizard(
|
|||||||
|
|
||||||
override suspend fun acceptTerms(): RegistrationResult {
|
override suspend fun acceptTerms(): RegistrationResult {
|
||||||
val safeSession = pendingSessionData.currentSession
|
val safeSession = pendingSessionData.currentSession
|
||||||
?: throw IllegalStateException("developer error, call createAccount() method first")
|
?: throw IllegalStateException("developer error, call createAccount() method first")
|
||||||
|
|
||||||
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession))
|
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession))
|
||||||
return performRegistrationRequest(params, LoginType.PASSWORD)
|
return performRegistrationRequest(params, LoginType.PASSWORD)
|
||||||
@ -103,14 +103,14 @@ internal class DefaultRegistrationWizard(
|
|||||||
|
|
||||||
override suspend fun addThreePid(threePid: RegisterThreePid): RegistrationResult {
|
override suspend fun addThreePid(threePid: RegisterThreePid): RegistrationResult {
|
||||||
pendingSessionData = pendingSessionData.copy(currentThreePidData = null)
|
pendingSessionData = pendingSessionData.copy(currentThreePidData = null)
|
||||||
.also { pendingSessionStore.savePendingSessionData(it) }
|
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||||
|
|
||||||
return sendThreePid(threePid)
|
return sendThreePid(threePid)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun sendAgainThreePid(): RegistrationResult {
|
override suspend fun sendAgainThreePid(): RegistrationResult {
|
||||||
val safeCurrentThreePid = pendingSessionData.currentThreePidData?.threePid
|
val safeCurrentThreePid = pendingSessionData.currentThreePidData?.threePid
|
||||||
?: throw IllegalStateException("developer error, call createAccount() method first")
|
?: throw IllegalStateException("developer error, call createAccount() method first")
|
||||||
|
|
||||||
return sendThreePid(safeCurrentThreePid)
|
return sendThreePid(safeCurrentThreePid)
|
||||||
}
|
}
|
||||||
@ -126,7 +126,7 @@ internal class DefaultRegistrationWizard(
|
|||||||
)
|
)
|
||||||
|
|
||||||
pendingSessionData = pendingSessionData.copy(sendAttempt = pendingSessionData.sendAttempt + 1)
|
pendingSessionData = pendingSessionData.copy(sendAttempt = pendingSessionData.sendAttempt + 1)
|
||||||
.also { pendingSessionStore.savePendingSessionData(it) }
|
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||||
|
|
||||||
val params = RegistrationParams(
|
val params = RegistrationParams(
|
||||||
auth = if (threePid is RegisterThreePid.Email) {
|
auth = if (threePid is RegisterThreePid.Email) {
|
||||||
@ -149,7 +149,7 @@ internal class DefaultRegistrationWizard(
|
|||||||
)
|
)
|
||||||
// Store data
|
// Store data
|
||||||
pendingSessionData = pendingSessionData.copy(currentThreePidData = ThreePidData.from(threePid, response, params))
|
pendingSessionData = pendingSessionData.copy(currentThreePidData = ThreePidData.from(threePid, response, params))
|
||||||
.also { pendingSessionStore.savePendingSessionData(it) }
|
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||||
|
|
||||||
// and send the sid a first time
|
// and send the sid a first time
|
||||||
return performRegistrationRequest(params, LoginType.PASSWORD)
|
return performRegistrationRequest(params, LoginType.PASSWORD)
|
||||||
@ -157,7 +157,7 @@ internal class DefaultRegistrationWizard(
|
|||||||
|
|
||||||
override suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult {
|
override suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult {
|
||||||
val safeParam = pendingSessionData.currentThreePidData?.registrationParams
|
val safeParam = pendingSessionData.currentThreePidData?.registrationParams
|
||||||
?: throw IllegalStateException("developer error, no pending three pid")
|
?: throw IllegalStateException("developer error, no pending three pid")
|
||||||
|
|
||||||
return performRegistrationRequest(safeParam, LoginType.PASSWORD, delayMillis)
|
return performRegistrationRequest(safeParam, LoginType.PASSWORD, delayMillis)
|
||||||
}
|
}
|
||||||
@ -168,13 +168,13 @@ internal class DefaultRegistrationWizard(
|
|||||||
|
|
||||||
private suspend fun validateThreePid(code: String): RegistrationResult {
|
private suspend fun validateThreePid(code: String): RegistrationResult {
|
||||||
val registrationParams = pendingSessionData.currentThreePidData?.registrationParams
|
val registrationParams = pendingSessionData.currentThreePidData?.registrationParams
|
||||||
?: throw IllegalStateException("developer error, no pending three pid")
|
?: throw IllegalStateException("developer error, no pending three pid")
|
||||||
val safeCurrentData = pendingSessionData.currentThreePidData ?: throw IllegalStateException("developer error, call createAccount() method first")
|
val safeCurrentData = pendingSessionData.currentThreePidData ?: throw IllegalStateException("developer error, call createAccount() method first")
|
||||||
val url = safeCurrentData.addThreePidRegistrationResponse.submitUrl ?: throw IllegalStateException("Missing url to send the code")
|
val url = safeCurrentData.addThreePidRegistrationResponse.submitUrl ?: throw IllegalStateException("Missing url to send the code")
|
||||||
val validationBody = ValidationCodeBody(
|
val validationBody = ValidationCodeBody(
|
||||||
clientSecret = pendingSessionData.clientSecret,
|
clientSecret = pendingSessionData.clientSecret,
|
||||||
sid = safeCurrentData.addThreePidRegistrationResponse.sid,
|
sid = safeCurrentData.addThreePidRegistrationResponse.sid,
|
||||||
code = code
|
code = code
|
||||||
)
|
)
|
||||||
val validationResponse = validateCodeTask.execute(ValidateCodeTask.Params(url, validationBody))
|
val validationResponse = validateCodeTask.execute(ValidateCodeTask.Params(url, validationBody))
|
||||||
if (validationResponse.isSuccess()) {
|
if (validationResponse.isSuccess()) {
|
||||||
@ -189,7 +189,7 @@ internal class DefaultRegistrationWizard(
|
|||||||
|
|
||||||
override suspend fun dummy(): RegistrationResult {
|
override suspend fun dummy(): RegistrationResult {
|
||||||
val safeSession = pendingSessionData.currentSession
|
val safeSession = pendingSessionData.currentSession
|
||||||
?: throw IllegalStateException("developer error, call createAccount() method first")
|
?: throw IllegalStateException("developer error, call createAccount() method first")
|
||||||
|
|
||||||
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession))
|
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession))
|
||||||
return performRegistrationRequest(params, LoginType.PASSWORD)
|
return performRegistrationRequest(params, LoginType.PASSWORD)
|
||||||
|
@ -21,13 +21,10 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
|
||||||
import org.matrix.android.sdk.api.logger.LoggerTag
|
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
|
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
|
||||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.Timer
|
|
||||||
import java.util.TimerTask
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal data class InboundGroupSessionHolder(
|
internal data class InboundGroupSessionHolder(
|
||||||
@ -57,18 +54,13 @@ internal class InboundGroupSessionStore @Inject constructor(
|
|||||||
if (oldValue != null) {
|
if (oldValue != null) {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
Timber.tag(loggerTag.value).v("## Inbound: entryRemoved ${oldValue.wrapper.roomId}-${oldValue.wrapper.senderKey}")
|
Timber.tag(loggerTag.value).v("## Inbound: entryRemoved ${oldValue.wrapper.roomId}-${oldValue.wrapper.senderKey}")
|
||||||
store.storeInboundGroupSessions(listOf(oldValue).map { it.wrapper })
|
// store.storeInboundGroupSessions(listOf(oldValue).map { it.wrapper })
|
||||||
oldValue.wrapper.session.releaseSession()
|
oldValue.wrapper.session.releaseSession()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val timer = Timer()
|
|
||||||
private var timerTask: TimerTask? = null
|
|
||||||
|
|
||||||
private val dirtySession = mutableListOf<InboundGroupSessionHolder>()
|
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun clear() {
|
fun clear() {
|
||||||
sessionCache.evictAll()
|
sessionCache.evictAll()
|
||||||
@ -90,7 +82,6 @@ internal class InboundGroupSessionStore @Inject constructor(
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
fun replaceGroupSession(old: InboundGroupSessionHolder, new: InboundGroupSessionHolder, sessionId: String, senderKey: String) {
|
fun replaceGroupSession(old: InboundGroupSessionHolder, new: InboundGroupSessionHolder, sessionId: String, senderKey: String) {
|
||||||
Timber.tag(loggerTag.value).v("## Replacing outdated session ${old.wrapper.roomId}-${old.wrapper.senderKey}")
|
Timber.tag(loggerTag.value).v("## Replacing outdated session ${old.wrapper.roomId}-${old.wrapper.senderKey}")
|
||||||
dirtySession.remove(old)
|
|
||||||
store.removeInboundGroupSession(sessionId, senderKey)
|
store.removeInboundGroupSession(sessionId, senderKey)
|
||||||
sessionCache.remove(CacheKey(sessionId, senderKey))
|
sessionCache.remove(CacheKey(sessionId, senderKey))
|
||||||
|
|
||||||
@ -107,33 +98,14 @@ internal class InboundGroupSessionStore @Inject constructor(
|
|||||||
|
|
||||||
private fun internalStoreGroupSession(holder: InboundGroupSessionHolder, sessionId: String, senderKey: String) {
|
private fun internalStoreGroupSession(holder: InboundGroupSessionHolder, sessionId: String, senderKey: String) {
|
||||||
Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession mark as dirty ${holder.wrapper.roomId}-${holder.wrapper.senderKey}")
|
Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession mark as dirty ${holder.wrapper.roomId}-${holder.wrapper.senderKey}")
|
||||||
// We want to batch this a bit for performances
|
|
||||||
dirtySession.add(holder)
|
|
||||||
|
|
||||||
if (sessionCache[CacheKey(sessionId, senderKey)] == null) {
|
if (sessionCache[CacheKey(sessionId, senderKey)] == null) {
|
||||||
// first time seen, put it in memory cache while waiting for batch insert
|
// first time seen, put it in memory cache while waiting for batch insert
|
||||||
// If it's already known, no need to update cache it's already there
|
// If it's already known, no need to update cache it's already there
|
||||||
sessionCache.put(CacheKey(sessionId, senderKey), holder)
|
sessionCache.put(CacheKey(sessionId, senderKey), holder)
|
||||||
}
|
}
|
||||||
|
|
||||||
timerTask?.cancel()
|
|
||||||
timerTask = object : TimerTask() {
|
|
||||||
override fun run() {
|
|
||||||
batchSave()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
timer.schedule(timerTask!!, 300)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun batchSave() {
|
|
||||||
val toSave = mutableListOf<InboundGroupSessionHolder>().apply { addAll(dirtySession) }
|
|
||||||
dirtySession.clear()
|
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession batching save of ${toSave.size}")
|
store.storeInboundGroupSessions(listOf(holder.wrapper))
|
||||||
tryOrNull {
|
|
||||||
store.storeInboundGroupSessions(toSave.map { it.wrapper })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -604,14 +604,16 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
* @param sharedHistory MSC3061, this key is sharable on invite
|
* @param sharedHistory MSC3061, this key is sharable on invite
|
||||||
* @return true if the operation succeeds.
|
* @return true if the operation succeeds.
|
||||||
*/
|
*/
|
||||||
fun addInboundGroupSession(sessionId: String,
|
fun addInboundGroupSession(
|
||||||
sessionKey: String,
|
sessionId: String,
|
||||||
roomId: String,
|
sessionKey: String,
|
||||||
senderKey: String,
|
roomId: String,
|
||||||
forwardingCurve25519KeyChain: List<String>,
|
senderKey: String,
|
||||||
keysClaimed: Map<String, String>,
|
forwardingCurve25519KeyChain: List<String>,
|
||||||
exportFormat: Boolean,
|
keysClaimed: Map<String, String>,
|
||||||
sharedHistory: Boolean): AddSessionResult {
|
exportFormat: Boolean,
|
||||||
|
sharedHistory: Boolean
|
||||||
|
): AddSessionResult {
|
||||||
val candidateSession = tryOrNull("Failed to create inbound session in room $roomId") {
|
val candidateSession = tryOrNull("Failed to create inbound session in room $roomId") {
|
||||||
if (exportFormat) {
|
if (exportFormat) {
|
||||||
OlmInboundGroupSession.importSession(sessionKey)
|
OlmInboundGroupSession.importSession(sessionKey)
|
||||||
@ -701,8 +703,8 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
val senderKey = megolmSessionData.senderKey ?: continue
|
val senderKey = megolmSessionData.senderKey ?: continue
|
||||||
val roomId = megolmSessionData.roomId
|
val roomId = megolmSessionData.roomId
|
||||||
|
|
||||||
val candidateSessionToImport = try {
|
val candidateSessionToImport = try {
|
||||||
MXInboundMegolmSessionWrapper.newFromMegolmData(megolmSessionData, true)
|
MXInboundMegolmSessionWrapper.newFromMegolmData(megolmSessionData, true)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession() : Failed to import session $senderKey/$sessionId")
|
Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession() : Failed to import session $senderKey/$sessionId")
|
||||||
continue
|
continue
|
||||||
@ -806,7 +808,6 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
}
|
}
|
||||||
replayAttackMap[messageIndexKey] = eventId
|
replayAttackMap[messageIndexKey] = eventId
|
||||||
}
|
}
|
||||||
inboundGroupSessionStore.storeInBoundGroupSession(sessionHolder, sessionId, senderKey)
|
|
||||||
val payload = try {
|
val payload = try {
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
|
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
|
||||||
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
|
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
|
||||||
|
@ -38,6 +38,7 @@ internal class MXMegolmDecryptionFactory @Inject constructor(
|
|||||||
outgoingKeyRequestManager,
|
outgoingKeyRequestManager,
|
||||||
cryptoStore,
|
cryptoStore,
|
||||||
matrixConfiguration,
|
matrixConfiguration,
|
||||||
eventsManager)
|
eventsManager
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -250,8 +250,10 @@ internal class MXMegolmEncryption(
|
|||||||
* @param sessionInfo the session info
|
* @param sessionInfo the session info
|
||||||
* @param devicesByUser the devices map
|
* @param devicesByUser the devices map
|
||||||
*/
|
*/
|
||||||
private suspend fun shareUserDevicesKey(sessionInfo: MXOutboundSessionInfo,
|
private suspend fun shareUserDevicesKey(
|
||||||
devicesByUser: Map<String, List<CryptoDeviceInfo>>) {
|
sessionInfo: MXOutboundSessionInfo,
|
||||||
|
devicesByUser: Map<String, List<CryptoDeviceInfo>>
|
||||||
|
) {
|
||||||
val sessionKey = olmDevice.getSessionKey(sessionInfo.sessionId) ?: return Unit.also {
|
val sessionKey = olmDevice.getSessionKey(sessionInfo.sessionId) ?: return Unit.also {
|
||||||
Timber.tag(loggerTag.value).v("shareUserDevicesKey() Failed to share session, failed to export")
|
Timber.tag(loggerTag.value).v("shareUserDevicesKey() Failed to share session, failed to export")
|
||||||
}
|
}
|
||||||
|
@ -1349,6 +1349,8 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||||||
|
|
||||||
// Mark keys as backed up
|
// Mark keys as backed up
|
||||||
cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers)
|
cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers)
|
||||||
|
// we can release the sessions now
|
||||||
|
olmInboundGroupSessionWrappers.onEach { it.session.releaseSession() }
|
||||||
|
|
||||||
if (olmInboundGroupSessionWrappers.size < KEY_BACKUP_SEND_KEYS_MAX_COUNT) {
|
if (olmInboundGroupSessionWrappers.size < KEY_BACKUP_SEND_KEYS_MAX_COUNT) {
|
||||||
Timber.v("backupKeys: All keys have been backed up")
|
Timber.v("backupKeys: All keys have been backed up")
|
||||||
|
@ -763,11 +763,17 @@ internal class RealmCryptoStore @Inject constructor(
|
|||||||
// } ?: false
|
// } ?: false
|
||||||
val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionIdentifier, wrapper.sessionData.senderKey)
|
val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionIdentifier, wrapper.sessionData.senderKey)
|
||||||
|
|
||||||
|
val existing = realm.where<OlmInboundGroupSessionEntity>()
|
||||||
|
.equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
|
||||||
|
.findFirst()
|
||||||
|
|
||||||
val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply {
|
val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply {
|
||||||
primaryKey = key
|
primaryKey = key
|
||||||
store(wrapper)
|
store(wrapper)
|
||||||
|
backedUp = existing?.backedUp ?: false
|
||||||
}
|
}
|
||||||
Timber.i("## CRYPTO | shouldShareHistory: ${wrapper.sessionData.sharedHistory} for $key")
|
|
||||||
|
Timber.v("## CRYPTO | shouldShareHistory: ${wrapper.sessionData.sharedHistory} for $key")
|
||||||
realm.insertOrUpdate(realmOlmInboundGroupSession)
|
realm.insertOrUpdate(realmOlmInboundGroupSession)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,8 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo029
|
|||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo030
|
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo030
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo031
|
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo031
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo032
|
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo032
|
||||||
|
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo033
|
||||||
|
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo034
|
||||||
import org.matrix.android.sdk.internal.util.Normalizer
|
import org.matrix.android.sdk.internal.util.Normalizer
|
||||||
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -57,7 +59,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||||||
private val normalizer: Normalizer
|
private val normalizer: Normalizer
|
||||||
) : MatrixRealmMigration(
|
) : MatrixRealmMigration(
|
||||||
dbName = "Session",
|
dbName = "Session",
|
||||||
schemaVersion = 32L,
|
schemaVersion = 34L,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Forces all RealmSessionStoreMigration instances to be equal.
|
* Forces all RealmSessionStoreMigration instances to be equal.
|
||||||
@ -99,5 +101,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||||||
if (oldVersion < 30) MigrateSessionTo030(realm).perform()
|
if (oldVersion < 30) MigrateSessionTo030(realm).perform()
|
||||||
if (oldVersion < 31) MigrateSessionTo031(realm).perform()
|
if (oldVersion < 31) MigrateSessionTo031(realm).perform()
|
||||||
if (oldVersion < 32) MigrateSessionTo032(realm).perform()
|
if (oldVersion < 32) MigrateSessionTo032(realm).perform()
|
||||||
|
if (oldVersion < 33) MigrateSessionTo033(realm).perform()
|
||||||
|
if (oldVersion < 34) MigrateSessionTo034(realm).perform()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.database.migration
|
||||||
|
|
||||||
|
import io.realm.DynamicRealm
|
||||||
|
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrating to:
|
||||||
|
* Live location sharing aggregated summary: adding new field relatedEventIds.
|
||||||
|
*/
|
||||||
|
internal class MigrateSessionTo033(realm: DynamicRealm) : RealmMigrator(realm, 33) {
|
||||||
|
|
||||||
|
override fun doMigrate(realm: DynamicRealm) {
|
||||||
|
realm.schema.get("LiveLocationShareAggregatedSummaryEntity")
|
||||||
|
?.addRealmListField(LiveLocationShareAggregatedSummaryEntityFields.RELATED_EVENT_IDS.`$`, String::class.java)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.database.migration
|
||||||
|
|
||||||
|
import io.realm.DynamicRealm
|
||||||
|
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrating to:
|
||||||
|
* Live location sharing aggregated summary: adding new field startOfLiveTimestampMillis.
|
||||||
|
*/
|
||||||
|
internal class MigrateSessionTo034(realm: DynamicRealm) : RealmMigrator(realm, 34) {
|
||||||
|
|
||||||
|
override fun doMigrate(realm: DynamicRealm) {
|
||||||
|
realm.schema.get("LiveLocationShareAggregatedSummaryEntity")
|
||||||
|
?.addField(LiveLocationShareAggregatedSummaryEntityFields.START_OF_LIVE_TIMESTAMP_MILLIS, Long::class.java)
|
||||||
|
?.setNullable(LiveLocationShareAggregatedSummaryEntityFields.START_OF_LIVE_TIMESTAMP_MILLIS, true)
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.database.model.livelocation
|
package org.matrix.android.sdk.internal.database.model.livelocation
|
||||||
|
|
||||||
|
import io.realm.RealmList
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
import io.realm.annotations.PrimaryKey
|
import io.realm.annotations.PrimaryKey
|
||||||
|
|
||||||
@ -29,6 +30,11 @@ internal open class LiveLocationShareAggregatedSummaryEntity(
|
|||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
var eventId: String = "",
|
var eventId: String = "",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of event ids used to compute the aggregated summary data.
|
||||||
|
*/
|
||||||
|
var relatedEventIds: RealmList<String> = RealmList(),
|
||||||
|
|
||||||
var roomId: String = "",
|
var roomId: String = "",
|
||||||
|
|
||||||
var userId: String = "",
|
var userId: String = "",
|
||||||
@ -38,6 +44,8 @@ internal open class LiveLocationShareAggregatedSummaryEntity(
|
|||||||
*/
|
*/
|
||||||
var isActive: Boolean? = null,
|
var isActive: Boolean? = null,
|
||||||
|
|
||||||
|
var startOfLiveTimestampMillis: Long? = null,
|
||||||
|
|
||||||
var endOfLiveTimestampMillis: Long? = null,
|
var endOfLiveTimestampMillis: Long? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,6 +23,11 @@ import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEnt
|
|||||||
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntityFields
|
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||||
|
|
||||||
|
internal fun EventAnnotationsSummaryEntity.Companion.where(realm: Realm, eventId: String): RealmQuery<EventAnnotationsSummaryEntity> {
|
||||||
|
return realm.where<EventAnnotationsSummaryEntity>()
|
||||||
|
.equalTo(EventAnnotationsSummaryEntityFields.EVENT_ID, eventId)
|
||||||
|
}
|
||||||
|
|
||||||
internal fun EventAnnotationsSummaryEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery<EventAnnotationsSummaryEntity> {
|
internal fun EventAnnotationsSummaryEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery<EventAnnotationsSummaryEntity> {
|
||||||
return realm.where<EventAnnotationsSummaryEntity>()
|
return realm.where<EventAnnotationsSummaryEntity>()
|
||||||
.equalTo(EventAnnotationsSummaryEntityFields.ROOM_ID, roomId)
|
.equalTo(EventAnnotationsSummaryEntityFields.ROOM_ID, roomId)
|
||||||
@ -44,3 +49,7 @@ internal fun EventAnnotationsSummaryEntity.Companion.getOrCreate(realm: Realm, r
|
|||||||
return EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst()
|
return EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst()
|
||||||
?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId)
|
?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun EventAnnotationsSummaryEntity.Companion.get(realm: Realm, eventId: String): EventAnnotationsSummaryEntity? {
|
||||||
|
return EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
|
||||||
|
}
|
||||||
|
@ -23,6 +23,14 @@ import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEnt
|
|||||||
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
|
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
|
||||||
|
|
||||||
|
internal fun LiveLocationShareAggregatedSummaryEntity.Companion.where(
|
||||||
|
realm: Realm,
|
||||||
|
eventId: String,
|
||||||
|
): RealmQuery<LiveLocationShareAggregatedSummaryEntity> {
|
||||||
|
return realm.where<LiveLocationShareAggregatedSummaryEntity>()
|
||||||
|
.equalTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, eventId)
|
||||||
|
}
|
||||||
|
|
||||||
internal fun LiveLocationShareAggregatedSummaryEntity.Companion.where(
|
internal fun LiveLocationShareAggregatedSummaryEntity.Companion.where(
|
||||||
realm: Realm,
|
realm: Realm,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
@ -72,17 +80,26 @@ internal fun LiveLocationShareAggregatedSummaryEntity.Companion.get(
|
|||||||
return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst()
|
return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun LiveLocationShareAggregatedSummaryEntity.Companion.get(
|
||||||
|
realm: Realm,
|
||||||
|
eventId: String,
|
||||||
|
): LiveLocationShareAggregatedSummaryEntity? {
|
||||||
|
return LiveLocationShareAggregatedSummaryEntity.where(realm, eventId).findFirst()
|
||||||
|
}
|
||||||
|
|
||||||
internal fun LiveLocationShareAggregatedSummaryEntity.Companion.findActiveLiveInRoomForUser(
|
internal fun LiveLocationShareAggregatedSummaryEntity.Companion.findActiveLiveInRoomForUser(
|
||||||
realm: Realm,
|
realm: Realm,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
userId: String,
|
userId: String,
|
||||||
ignoredEventId: String,
|
ignoredEventId: String,
|
||||||
|
startOfLiveTimestampThreshold: Long,
|
||||||
): List<LiveLocationShareAggregatedSummaryEntity> {
|
): List<LiveLocationShareAggregatedSummaryEntity> {
|
||||||
return LiveLocationShareAggregatedSummaryEntity
|
return LiveLocationShareAggregatedSummaryEntity
|
||||||
.whereRoomId(realm, roomId = roomId)
|
.whereRoomId(realm, roomId = roomId)
|
||||||
.equalTo(LiveLocationShareAggregatedSummaryEntityFields.USER_ID, userId)
|
.equalTo(LiveLocationShareAggregatedSummaryEntityFields.USER_ID, userId)
|
||||||
.equalTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true)
|
.equalTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true)
|
||||||
.notEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, ignoredEventId)
|
.notEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, ignoredEventId)
|
||||||
|
.lessThan(LiveLocationShareAggregatedSummaryEntityFields.START_OF_LIVE_TIMESTAMP_MILLIS, startOfLiveTimestampThreshold)
|
||||||
.findAll()
|
.findAll()
|
||||||
.toList()
|
.toList()
|
||||||
}
|
}
|
||||||
|
@ -88,6 +88,7 @@ import org.matrix.android.sdk.internal.session.room.EventRelationsAggregationPro
|
|||||||
import org.matrix.android.sdk.internal.session.room.aggregation.poll.DefaultPollAggregationProcessor
|
import org.matrix.android.sdk.internal.session.room.aggregation.poll.DefaultPollAggregationProcessor
|
||||||
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor
|
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor
|
||||||
import org.matrix.android.sdk.internal.session.room.create.RoomCreateEventProcessor
|
import org.matrix.android.sdk.internal.session.room.create.RoomCreateEventProcessor
|
||||||
|
import org.matrix.android.sdk.internal.session.room.location.LiveLocationShareRedactionEventProcessor
|
||||||
import org.matrix.android.sdk.internal.session.room.prune.RedactionEventProcessor
|
import org.matrix.android.sdk.internal.session.room.prune.RedactionEventProcessor
|
||||||
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
||||||
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessorCoroutine
|
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessorCoroutine
|
||||||
@ -321,6 +322,10 @@ internal abstract class SessionModule {
|
|||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun bindEventRedactionProcessor(processor: RedactionEventProcessor): EventInsertLiveProcessor
|
abstract fun bindEventRedactionProcessor(processor: RedactionEventProcessor): EventInsertLiveProcessor
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoSet
|
||||||
|
abstract fun bindLiveLocationShareRedactionEventProcessor(processor: LiveLocationShareRedactionEventProcessor): EventInsertLiveProcessor
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun bindEventRelationsAggregationProcessor(processor: EventRelationsAggregationProcessor): EventInsertLiveProcessor
|
abstract fun bindEventRelationsAggregationProcessor(processor: EventRelationsAggregationProcessor): EventInsertLiveProcessor
|
||||||
|
@ -58,11 +58,13 @@ import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVi
|
|||||||
import org.matrix.android.sdk.internal.session.room.location.CheckIfExistingActiveLiveTask
|
import org.matrix.android.sdk.internal.session.room.location.CheckIfExistingActiveLiveTask
|
||||||
import org.matrix.android.sdk.internal.session.room.location.DefaultCheckIfExistingActiveLiveTask
|
import org.matrix.android.sdk.internal.session.room.location.DefaultCheckIfExistingActiveLiveTask
|
||||||
import org.matrix.android.sdk.internal.session.room.location.DefaultGetActiveBeaconInfoForUserTask
|
import org.matrix.android.sdk.internal.session.room.location.DefaultGetActiveBeaconInfoForUserTask
|
||||||
|
import org.matrix.android.sdk.internal.session.room.location.DefaultRedactLiveLocationShareTask
|
||||||
import org.matrix.android.sdk.internal.session.room.location.DefaultSendLiveLocationTask
|
import org.matrix.android.sdk.internal.session.room.location.DefaultSendLiveLocationTask
|
||||||
import org.matrix.android.sdk.internal.session.room.location.DefaultSendStaticLocationTask
|
import org.matrix.android.sdk.internal.session.room.location.DefaultSendStaticLocationTask
|
||||||
import org.matrix.android.sdk.internal.session.room.location.DefaultStartLiveLocationShareTask
|
import org.matrix.android.sdk.internal.session.room.location.DefaultStartLiveLocationShareTask
|
||||||
import org.matrix.android.sdk.internal.session.room.location.DefaultStopLiveLocationShareTask
|
import org.matrix.android.sdk.internal.session.room.location.DefaultStopLiveLocationShareTask
|
||||||
import org.matrix.android.sdk.internal.session.room.location.GetActiveBeaconInfoForUserTask
|
import org.matrix.android.sdk.internal.session.room.location.GetActiveBeaconInfoForUserTask
|
||||||
|
import org.matrix.android.sdk.internal.session.room.location.RedactLiveLocationShareTask
|
||||||
import org.matrix.android.sdk.internal.session.room.location.SendLiveLocationTask
|
import org.matrix.android.sdk.internal.session.room.location.SendLiveLocationTask
|
||||||
import org.matrix.android.sdk.internal.session.room.location.SendStaticLocationTask
|
import org.matrix.android.sdk.internal.session.room.location.SendStaticLocationTask
|
||||||
import org.matrix.android.sdk.internal.session.room.location.StartLiveLocationShareTask
|
import org.matrix.android.sdk.internal.session.room.location.StartLiveLocationShareTask
|
||||||
@ -339,4 +341,7 @@ internal abstract class RoomModule {
|
|||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindCheckIfExistingActiveLiveTask(task: DefaultCheckIfExistingActiveLiveTask): CheckIfExistingActiveLiveTask
|
abstract fun bindCheckIfExistingActiveLiveTask(task: DefaultCheckIfExistingActiveLiveTask): CheckIfExistingActiveLiveTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindRedactLiveLocationShareTask(task: DefaultRedactLiveLocationShareTask): RedactLiveLocationShareTask
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
|
|||||||
|
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmList
|
||||||
import org.matrix.android.sdk.api.extensions.orTrue
|
import org.matrix.android.sdk.api.extensions.orTrue
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
@ -73,16 +74,22 @@ internal class LiveLocationAggregationProcessor @Inject constructor(
|
|||||||
eventId = targetEventId
|
eventId = targetEventId
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (!isLive && !event.eventId.isNullOrEmpty()) {
|
||||||
|
// in this case, the received event is a new state event related to the previous one
|
||||||
|
addRelatedEventId(event.eventId, aggregatedSummary)
|
||||||
|
}
|
||||||
|
|
||||||
// remote event can stay with isLive == true while the local summary is no more active
|
// remote event can stay with isLive == true while the local summary is no more active
|
||||||
val isActive = aggregatedSummary.isActive.orTrue() && isLive
|
val isActive = aggregatedSummary.isActive.orTrue() && isLive
|
||||||
val endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) }
|
val endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) }
|
||||||
Timber.d("updating summary of id=$targetEventId with isActive=$isActive and endTimestamp=$endOfLiveTimestampMillis")
|
Timber.d("updating summary of id=$targetEventId with isActive=$isActive and endTimestamp=$endOfLiveTimestampMillis")
|
||||||
|
|
||||||
|
aggregatedSummary.startOfLiveTimestampMillis = content.getBestTimestampMillis()
|
||||||
aggregatedSummary.endOfLiveTimestampMillis = endOfLiveTimestampMillis
|
aggregatedSummary.endOfLiveTimestampMillis = endOfLiveTimestampMillis
|
||||||
aggregatedSummary.isActive = isActive
|
aggregatedSummary.isActive = isActive
|
||||||
aggregatedSummary.userId = event.senderId
|
aggregatedSummary.userId = event.senderId
|
||||||
|
|
||||||
deactivateAllPreviousBeacons(realm, roomId, event.senderId, targetEventId)
|
deactivateAllPreviousBeacons(realm, roomId, event.senderId, targetEventId, content.getBestTimestampMillis() ?: 0)
|
||||||
|
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
scheduleDeactivationAfterTimeout(targetEventId, roomId, endOfLiveTimestampMillis)
|
scheduleDeactivationAfterTimeout(targetEventId, roomId, endOfLiveTimestampMillis)
|
||||||
@ -144,6 +151,11 @@ internal class LiveLocationAggregationProcessor @Inject constructor(
|
|||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
eventId = relatedEventId
|
eventId = relatedEventId
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (!event.eventId.isNullOrEmpty()) {
|
||||||
|
addRelatedEventId(event.eventId, aggregatedSummary)
|
||||||
|
}
|
||||||
|
|
||||||
val updatedLocationTimestamp = content.getBestTimestampMillis() ?: 0
|
val updatedLocationTimestamp = content.getBestTimestampMillis() ?: 0
|
||||||
val currentLocationTimestamp = ContentMapper
|
val currentLocationTimestamp = ContentMapper
|
||||||
.map(aggregatedSummary.lastLocationContent)
|
.map(aggregatedSummary.lastLocationContent)
|
||||||
@ -160,13 +172,31 @@ internal class LiveLocationAggregationProcessor @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deactivateAllPreviousBeacons(realm: Realm, roomId: String, userId: String, currentEventId: String) {
|
private fun addRelatedEventId(
|
||||||
|
eventId: String,
|
||||||
|
aggregatedSummary: LiveLocationShareAggregatedSummaryEntity
|
||||||
|
) {
|
||||||
|
Timber.d("adding related event id $eventId to summary of id ${aggregatedSummary.eventId}")
|
||||||
|
val updatedEventIds = aggregatedSummary.relatedEventIds.toMutableList().also {
|
||||||
|
it.add(eventId)
|
||||||
|
}
|
||||||
|
aggregatedSummary.relatedEventIds = RealmList(*updatedEventIds.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deactivateAllPreviousBeacons(
|
||||||
|
realm: Realm,
|
||||||
|
roomId: String,
|
||||||
|
userId: String,
|
||||||
|
currentEventId: String,
|
||||||
|
currentEventTimestamp: Long
|
||||||
|
) {
|
||||||
LiveLocationShareAggregatedSummaryEntity
|
LiveLocationShareAggregatedSummaryEntity
|
||||||
.findActiveLiveInRoomForUser(
|
.findActiveLiveInRoomForUser(
|
||||||
realm = realm,
|
realm = realm,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
userId = userId,
|
userId = userId,
|
||||||
ignoredEventId = currentEventId
|
ignoredEventId = currentEventId,
|
||||||
|
startOfLiveTimestampThreshold = currentEventTimestamp
|
||||||
)
|
)
|
||||||
.forEach { it.isActive = false }
|
.forEach { it.isActive = false }
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ internal class DefaultLocationSharingService @AssistedInject constructor(
|
|||||||
private val startLiveLocationShareTask: StartLiveLocationShareTask,
|
private val startLiveLocationShareTask: StartLiveLocationShareTask,
|
||||||
private val stopLiveLocationShareTask: StopLiveLocationShareTask,
|
private val stopLiveLocationShareTask: StopLiveLocationShareTask,
|
||||||
private val checkIfExistingActiveLiveTask: CheckIfExistingActiveLiveTask,
|
private val checkIfExistingActiveLiveTask: CheckIfExistingActiveLiveTask,
|
||||||
|
private val redactLiveLocationShareTask: RedactLiveLocationShareTask,
|
||||||
private val liveLocationShareAggregatedSummaryMapper: LiveLocationShareAggregatedSummaryMapper,
|
private val liveLocationShareAggregatedSummaryMapper: LiveLocationShareAggregatedSummaryMapper,
|
||||||
) : LocationSharingService {
|
) : LocationSharingService {
|
||||||
|
|
||||||
@ -102,6 +103,15 @@ internal class DefaultLocationSharingService @AssistedInject constructor(
|
|||||||
return stopLiveLocationShareTask.execute(params)
|
return stopLiveLocationShareTask.execute(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun redactLiveLocationShare(beaconInfoEventId: String, reason: String?) {
|
||||||
|
val params = RedactLiveLocationShareTask.Params(
|
||||||
|
roomId = roomId,
|
||||||
|
beaconInfoEventId = beaconInfoEventId,
|
||||||
|
reason = reason
|
||||||
|
)
|
||||||
|
return redactLiveLocationShareTask.execute(params)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getRunningLiveLocationShareSummaries(): LiveData<List<LiveLocationShareAggregatedSummary>> {
|
override fun getRunningLiveLocationShareSummaries(): LiveData<List<LiveLocationShareAggregatedSummary>> {
|
||||||
return monarchy.findAllMappedWithChanges(
|
return monarchy.findAllMappedWithChanges(
|
||||||
{ LiveLocationShareAggregatedSummaryEntity.findRunningLiveInRoom(it, roomId = roomId) },
|
{ LiveLocationShareAggregatedSummaryEntity.findRunningLiveInRoom(it, roomId = roomId) },
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.room.location
|
||||||
|
|
||||||
|
import io.realm.Realm
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
||||||
|
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.query.get
|
||||||
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
|
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens to the database for the insertion of any redaction event.
|
||||||
|
* Delete specifically the aggregated summary related to a redacted live location share event.
|
||||||
|
*/
|
||||||
|
internal class LiveLocationShareRedactionEventProcessor @Inject constructor() : EventInsertLiveProcessor {
|
||||||
|
|
||||||
|
override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
|
||||||
|
return eventType == EventType.REDACTION && insertType != EventInsertType.LOCAL_ECHO
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun process(realm: Realm, event: Event) {
|
||||||
|
if (event.redacts.isNullOrBlank() || LocalEcho.isLocalEchoId(event.eventId.orEmpty())) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val redactedEvent = EventEntity.where(realm, eventId = event.redacts).findFirst()
|
||||||
|
?: return
|
||||||
|
|
||||||
|
if (redactedEvent.type in EventType.STATE_ROOM_BEACON_INFO) {
|
||||||
|
val liveSummary = LiveLocationShareAggregatedSummaryEntity.get(realm, eventId = redactedEvent.eventId)
|
||||||
|
|
||||||
|
if (liveSummary != null) {
|
||||||
|
Timber.d("deleting live summary with id: ${liveSummary.eventId}")
|
||||||
|
liveSummary.deleteFromRealm()
|
||||||
|
val annotationsSummary = EventAnnotationsSummaryEntity.get(realm, eventId = redactedEvent.eventId)
|
||||||
|
if (annotationsSummary != null) {
|
||||||
|
Timber.d("deleting annotation summary with id: ${annotationsSummary.eventId}")
|
||||||
|
annotationsSummary.deleteFromRealm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.room.location
|
||||||
|
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import org.matrix.android.sdk.internal.database.awaitTransaction
|
||||||
|
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.query.get
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
||||||
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface RedactLiveLocationShareTask : Task<RedactLiveLocationShareTask.Params, Unit> {
|
||||||
|
data class Params(
|
||||||
|
val roomId: String,
|
||||||
|
val beaconInfoEventId: String,
|
||||||
|
val reason: String?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultRedactLiveLocationShareTask @Inject constructor(
|
||||||
|
@SessionDatabase private val realmConfiguration: RealmConfiguration,
|
||||||
|
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||||
|
private val eventSenderProcessor: EventSenderProcessor,
|
||||||
|
) : RedactLiveLocationShareTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: RedactLiveLocationShareTask.Params) {
|
||||||
|
val relatedEventIds = getRelatedEventIdsOfLive(params.beaconInfoEventId)
|
||||||
|
Timber.d("beacon with id ${params.beaconInfoEventId} has related event ids: ${relatedEventIds.joinToString(", ")}")
|
||||||
|
|
||||||
|
postRedactionWithLocalEcho(
|
||||||
|
eventId = params.beaconInfoEventId,
|
||||||
|
roomId = params.roomId,
|
||||||
|
reason = params.reason
|
||||||
|
)
|
||||||
|
relatedEventIds.forEach { eventId ->
|
||||||
|
postRedactionWithLocalEcho(
|
||||||
|
eventId = eventId,
|
||||||
|
roomId = params.roomId,
|
||||||
|
reason = params.reason
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getRelatedEventIdsOfLive(beaconInfoEventId: String): List<String> {
|
||||||
|
return awaitTransaction(realmConfiguration) { realm ->
|
||||||
|
val aggregatedSummaryEntity = LiveLocationShareAggregatedSummaryEntity.get(
|
||||||
|
realm = realm,
|
||||||
|
eventId = beaconInfoEventId
|
||||||
|
)
|
||||||
|
aggregatedSummaryEntity?.relatedEventIds?.toList() ?: emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun postRedactionWithLocalEcho(eventId: String, roomId: String, reason: String?) {
|
||||||
|
Timber.d("posting redaction for event of id $eventId")
|
||||||
|
val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, eventId, reason)
|
||||||
|
localEchoEventFactory.createLocalEcho(redactionEcho)
|
||||||
|
eventSenderProcessor.postRedaction(redactionEcho, reason)
|
||||||
|
}
|
||||||
|
}
|
@ -74,6 +74,8 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr
|
|||||||
when (typeToPrune) {
|
when (typeToPrune) {
|
||||||
EventType.ENCRYPTED,
|
EventType.ENCRYPTED,
|
||||||
EventType.MESSAGE,
|
EventType.MESSAGE,
|
||||||
|
in EventType.STATE_ROOM_BEACON_INFO,
|
||||||
|
in EventType.BEACON_LOCATION_DATA,
|
||||||
in EventType.POLL_START -> {
|
in EventType.POLL_START -> {
|
||||||
Timber.d("REDACTION for message ${eventToPrune.eventId}")
|
Timber.d("REDACTION for message ${eventToPrune.eventId}")
|
||||||
val unsignedData = EventMapper.map(eventToPrune).unsignedData
|
val unsignedData = EventMapper.map(eventToPrune).unsignedData
|
||||||
|
@ -22,7 +22,7 @@ import timber.log.Timber
|
|||||||
/**
|
/**
|
||||||
* Throws in debug, only log in production.
|
* Throws in debug, only log in production.
|
||||||
* As this method does not always throw, next statement should be a return.
|
* As this method does not always throw, next statement should be a return.
|
||||||
*/
|
*/
|
||||||
internal fun fatalError(message: String) {
|
internal fun fatalError(message: String) {
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
error(message)
|
error(message)
|
||||||
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
|
|||||||
|
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.amshove.kluent.shouldContain
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.UnsignedData
|
import org.matrix.android.sdk.api.session.events.model.UnsignedData
|
||||||
@ -35,6 +36,7 @@ import org.matrix.android.sdk.test.fakes.FakeWorkManagerProvider
|
|||||||
import org.matrix.android.sdk.test.fakes.givenEqualTo
|
import org.matrix.android.sdk.test.fakes.givenEqualTo
|
||||||
import org.matrix.android.sdk.test.fakes.givenFindAll
|
import org.matrix.android.sdk.test.fakes.givenFindAll
|
||||||
import org.matrix.android.sdk.test.fakes.givenFindFirst
|
import org.matrix.android.sdk.test.fakes.givenFindFirst
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenLessThan
|
||||||
import org.matrix.android.sdk.test.fakes.givenNotEqualTo
|
import org.matrix.android.sdk.test.fakes.givenNotEqualTo
|
||||||
|
|
||||||
private const val A_SESSION_ID = "session_id"
|
private const val A_SESSION_ID = "session_id"
|
||||||
@ -182,6 +184,7 @@ internal class LiveLocationAggregationProcessorTest {
|
|||||||
aggregatedEntity.roomId shouldBeEqualTo A_ROOM_ID
|
aggregatedEntity.roomId shouldBeEqualTo A_ROOM_ID
|
||||||
aggregatedEntity.userId shouldBeEqualTo A_SENDER_ID
|
aggregatedEntity.userId shouldBeEqualTo A_SENDER_ID
|
||||||
aggregatedEntity.isActive shouldBeEqualTo true
|
aggregatedEntity.isActive shouldBeEqualTo true
|
||||||
|
aggregatedEntity.startOfLiveTimestampMillis shouldBeEqualTo A_TIMESTAMP
|
||||||
aggregatedEntity.endOfLiveTimestampMillis shouldBeEqualTo A_TIMESTAMP + A_TIMEOUT_MILLIS
|
aggregatedEntity.endOfLiveTimestampMillis shouldBeEqualTo A_TIMESTAMP + A_TIMEOUT_MILLIS
|
||||||
aggregatedEntity.lastLocationContent shouldBeEqualTo null
|
aggregatedEntity.lastLocationContent shouldBeEqualTo null
|
||||||
previousEntities.forEach { entity ->
|
previousEntities.forEach { entity ->
|
||||||
@ -199,9 +202,10 @@ internal class LiveLocationAggregationProcessorTest {
|
|||||||
age = 123,
|
age = 123,
|
||||||
replacesState = AN_EVENT_ID
|
replacesState = AN_EVENT_ID
|
||||||
)
|
)
|
||||||
|
val stateEventId = "state-event-id"
|
||||||
val event = Event(
|
val event = Event(
|
||||||
senderId = A_SENDER_ID,
|
senderId = A_SENDER_ID,
|
||||||
eventId = "",
|
eventId = stateEventId,
|
||||||
unsignedData = unsignedData
|
unsignedData = unsignedData
|
||||||
)
|
)
|
||||||
val beaconInfo = MessageBeaconInfoContent(
|
val beaconInfo = MessageBeaconInfoContent(
|
||||||
@ -237,6 +241,7 @@ internal class LiveLocationAggregationProcessorTest {
|
|||||||
aggregatedEntity.roomId shouldBeEqualTo A_ROOM_ID
|
aggregatedEntity.roomId shouldBeEqualTo A_ROOM_ID
|
||||||
aggregatedEntity.userId shouldBeEqualTo A_SENDER_ID
|
aggregatedEntity.userId shouldBeEqualTo A_SENDER_ID
|
||||||
aggregatedEntity.isActive shouldBeEqualTo false
|
aggregatedEntity.isActive shouldBeEqualTo false
|
||||||
|
aggregatedEntity.relatedEventIds shouldContain stateEventId
|
||||||
aggregatedEntity.endOfLiveTimestampMillis shouldBeEqualTo A_TIMESTAMP + A_TIMEOUT_MILLIS
|
aggregatedEntity.endOfLiveTimestampMillis shouldBeEqualTo A_TIMESTAMP + A_TIMEOUT_MILLIS
|
||||||
aggregatedEntity.lastLocationContent shouldBeEqualTo null
|
aggregatedEntity.lastLocationContent shouldBeEqualTo null
|
||||||
previousEntities.forEach { entity ->
|
previousEntities.forEach { entity ->
|
||||||
@ -324,7 +329,7 @@ internal class LiveLocationAggregationProcessorTest {
|
|||||||
val lastBeaconLocationContent = MessageBeaconLocationDataContent(
|
val lastBeaconLocationContent = MessageBeaconLocationDataContent(
|
||||||
unstableTimestampMillis = A_TIMESTAMP
|
unstableTimestampMillis = A_TIMESTAMP
|
||||||
)
|
)
|
||||||
givenLastSummaryQueryReturns(
|
val aggregatedEntity = givenLastSummaryQueryReturns(
|
||||||
eventId = AN_EVENT_ID,
|
eventId = AN_EVENT_ID,
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
beaconLocationContent = lastBeaconLocationContent
|
beaconLocationContent = lastBeaconLocationContent
|
||||||
@ -340,6 +345,7 @@ internal class LiveLocationAggregationProcessorTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
result shouldBeEqualTo false
|
result shouldBeEqualTo false
|
||||||
|
aggregatedEntity.relatedEventIds shouldContain AN_EVENT_ID
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -353,7 +359,7 @@ internal class LiveLocationAggregationProcessorTest {
|
|||||||
val lastBeaconLocationContent = MessageBeaconLocationDataContent(
|
val lastBeaconLocationContent = MessageBeaconLocationDataContent(
|
||||||
unstableTimestampMillis = A_TIMESTAMP - 60_000
|
unstableTimestampMillis = A_TIMESTAMP - 60_000
|
||||||
)
|
)
|
||||||
val entity = givenLastSummaryQueryReturns(
|
val aggregatedEntity = givenLastSummaryQueryReturns(
|
||||||
eventId = AN_EVENT_ID,
|
eventId = AN_EVENT_ID,
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
beaconLocationContent = lastBeaconLocationContent
|
beaconLocationContent = lastBeaconLocationContent
|
||||||
@ -369,7 +375,8 @@ internal class LiveLocationAggregationProcessorTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
result shouldBeEqualTo true
|
result shouldBeEqualTo true
|
||||||
val savedLocationData = ContentMapper.map(entity.lastLocationContent).toModel<MessageBeaconLocationDataContent>()
|
aggregatedEntity.relatedEventIds shouldContain AN_EVENT_ID
|
||||||
|
val savedLocationData = ContentMapper.map(aggregatedEntity.lastLocationContent).toModel<MessageBeaconLocationDataContent>()
|
||||||
savedLocationData?.getBestTimestampMillis() shouldBeEqualTo A_TIMESTAMP
|
savedLocationData?.getBestTimestampMillis() shouldBeEqualTo A_TIMESTAMP
|
||||||
savedLocationData?.getBestLocationInfo()?.geoUri shouldBeEqualTo A_GEO_URI
|
savedLocationData?.getBestLocationInfo()?.geoUri shouldBeEqualTo A_GEO_URI
|
||||||
}
|
}
|
||||||
@ -399,6 +406,7 @@ internal class LiveLocationAggregationProcessorTest {
|
|||||||
.givenNotEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, AN_EVENT_ID)
|
.givenNotEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, AN_EVENT_ID)
|
||||||
.givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.USER_ID, A_SENDER_ID)
|
.givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.USER_ID, A_SENDER_ID)
|
||||||
.givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true)
|
.givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true)
|
||||||
|
.givenLessThan(LiveLocationShareAggregatedSummaryEntityFields.START_OF_LIVE_TIMESTAMP_MILLIS, A_TIMESTAMP)
|
||||||
.givenFindAll(summaryList)
|
.givenFindAll(summaryList)
|
||||||
return summaryList
|
return summaryList
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,10 @@ import androidx.lifecycle.Transformations
|
|||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.coVerify
|
import io.mockk.coVerify
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
|
import io.mockk.just
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.mockkStatic
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.runs
|
||||||
import io.mockk.slot
|
import io.mockk.slot
|
||||||
import io.mockk.unmockkAll
|
import io.mockk.unmockkAll
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
@ -52,6 +54,7 @@ private const val A_LONGITUDE = 40.0
|
|||||||
private const val AN_UNCERTAINTY = 5.0
|
private const val AN_UNCERTAINTY = 5.0
|
||||||
private const val A_TIMEOUT = 15_000L
|
private const val A_TIMEOUT = 15_000L
|
||||||
private const val A_DESCRIPTION = "description"
|
private const val A_DESCRIPTION = "description"
|
||||||
|
private const val A_REASON = "reason"
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
internal class DefaultLocationSharingServiceTest {
|
internal class DefaultLocationSharingServiceTest {
|
||||||
@ -62,6 +65,7 @@ internal class DefaultLocationSharingServiceTest {
|
|||||||
private val startLiveLocationShareTask = mockk<StartLiveLocationShareTask>()
|
private val startLiveLocationShareTask = mockk<StartLiveLocationShareTask>()
|
||||||
private val stopLiveLocationShareTask = mockk<StopLiveLocationShareTask>()
|
private val stopLiveLocationShareTask = mockk<StopLiveLocationShareTask>()
|
||||||
private val checkIfExistingActiveLiveTask = mockk<CheckIfExistingActiveLiveTask>()
|
private val checkIfExistingActiveLiveTask = mockk<CheckIfExistingActiveLiveTask>()
|
||||||
|
private val redactLiveLocationShareTask = mockk<RedactLiveLocationShareTask>()
|
||||||
private val fakeLiveLocationShareAggregatedSummaryMapper = mockk<LiveLocationShareAggregatedSummaryMapper>()
|
private val fakeLiveLocationShareAggregatedSummaryMapper = mockk<LiveLocationShareAggregatedSummaryMapper>()
|
||||||
|
|
||||||
private val defaultLocationSharingService = DefaultLocationSharingService(
|
private val defaultLocationSharingService = DefaultLocationSharingService(
|
||||||
@ -72,6 +76,7 @@ internal class DefaultLocationSharingServiceTest {
|
|||||||
startLiveLocationShareTask = startLiveLocationShareTask,
|
startLiveLocationShareTask = startLiveLocationShareTask,
|
||||||
stopLiveLocationShareTask = stopLiveLocationShareTask,
|
stopLiveLocationShareTask = stopLiveLocationShareTask,
|
||||||
checkIfExistingActiveLiveTask = checkIfExistingActiveLiveTask,
|
checkIfExistingActiveLiveTask = checkIfExistingActiveLiveTask,
|
||||||
|
redactLiveLocationShareTask = redactLiveLocationShareTask,
|
||||||
liveLocationShareAggregatedSummaryMapper = fakeLiveLocationShareAggregatedSummaryMapper
|
liveLocationShareAggregatedSummaryMapper = fakeLiveLocationShareAggregatedSummaryMapper
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -209,6 +214,20 @@ internal class DefaultLocationSharingServiceTest {
|
|||||||
coVerify { stopLiveLocationShareTask.execute(expectedParams) }
|
coVerify { stopLiveLocationShareTask.execute(expectedParams) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `live location share can be redacted`() = runTest {
|
||||||
|
coEvery { redactLiveLocationShareTask.execute(any()) } just runs
|
||||||
|
|
||||||
|
defaultLocationSharingService.redactLiveLocationShare(beaconInfoEventId = AN_EVENT_ID, reason = A_REASON)
|
||||||
|
|
||||||
|
val expectedParams = RedactLiveLocationShareTask.Params(
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
beaconInfoEventId = AN_EVENT_ID,
|
||||||
|
reason = A_REASON
|
||||||
|
)
|
||||||
|
coVerify { redactLiveLocationShareTask.execute(expectedParams) }
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `livedata of live summaries is correctly computed`() {
|
fun `livedata of live summaries is correctly computed`() {
|
||||||
val entity = LiveLocationShareAggregatedSummaryEntity()
|
val entity = LiveLocationShareAggregatedSummaryEntity()
|
||||||
|
@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.room.location
|
||||||
|
|
||||||
|
import io.mockk.unmockkAll
|
||||||
|
import io.realm.RealmList
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
|
||||||
|
import org.matrix.android.sdk.test.fakes.FakeEventSenderProcessor
|
||||||
|
import org.matrix.android.sdk.test.fakes.FakeLocalEchoEventFactory
|
||||||
|
import org.matrix.android.sdk.test.fakes.FakeRealm
|
||||||
|
import org.matrix.android.sdk.test.fakes.FakeRealmConfiguration
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenEqualTo
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenFindFirst
|
||||||
|
|
||||||
|
private const val A_ROOM_ID = "room-id"
|
||||||
|
private const val AN_EVENT_ID = "event-id"
|
||||||
|
private const val AN_EVENT_ID_1 = "event-id-1"
|
||||||
|
private const val AN_EVENT_ID_2 = "event-id-2"
|
||||||
|
private const val AN_EVENT_ID_3 = "event-id-3"
|
||||||
|
private const val A_REASON = "reason"
|
||||||
|
|
||||||
|
@ExperimentalCoroutinesApi
|
||||||
|
class DefaultRedactLiveLocationShareTaskTest {
|
||||||
|
|
||||||
|
private val fakeRealmConfiguration = FakeRealmConfiguration()
|
||||||
|
private val fakeLocalEchoEventFactory = FakeLocalEchoEventFactory()
|
||||||
|
private val fakeEventSenderProcessor = FakeEventSenderProcessor()
|
||||||
|
private val fakeRealm = FakeRealm()
|
||||||
|
|
||||||
|
private val defaultRedactLiveLocationShareTask = DefaultRedactLiveLocationShareTask(
|
||||||
|
realmConfiguration = fakeRealmConfiguration.instance,
|
||||||
|
localEchoEventFactory = fakeLocalEchoEventFactory.instance,
|
||||||
|
eventSenderProcessor = fakeEventSenderProcessor
|
||||||
|
)
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given parameters when redacting then post redact events and related and creates redact local echos`() = runTest {
|
||||||
|
val params = createParams()
|
||||||
|
val relatedEventIds = listOf(AN_EVENT_ID_1, AN_EVENT_ID_2, AN_EVENT_ID_3)
|
||||||
|
val aggregatedSummaryEntity = createSummary(relatedEventIds)
|
||||||
|
givenSummaryForId(AN_EVENT_ID, aggregatedSummaryEntity)
|
||||||
|
fakeRealmConfiguration.givenAwaitTransaction<List<String>>(fakeRealm.instance)
|
||||||
|
val redactEvents = givenCreateRedactEventWithLocalEcho(relatedEventIds + AN_EVENT_ID)
|
||||||
|
givenPostRedaction(redactEvents)
|
||||||
|
|
||||||
|
defaultRedactLiveLocationShareTask.execute(params)
|
||||||
|
|
||||||
|
verifyCreateRedactEventForEventIds(relatedEventIds + AN_EVENT_ID)
|
||||||
|
verifyCreateLocalEchoForEvents(redactEvents)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createParams() = RedactLiveLocationShareTask.Params(
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
beaconInfoEventId = AN_EVENT_ID,
|
||||||
|
reason = A_REASON
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun createSummary(relatedEventIds: List<String>): LiveLocationShareAggregatedSummaryEntity {
|
||||||
|
return LiveLocationShareAggregatedSummaryEntity(
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
relatedEventIds = RealmList(*relatedEventIds.toTypedArray()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenSummaryForId(eventId: String, aggregatedSummaryEntity: LiveLocationShareAggregatedSummaryEntity) {
|
||||||
|
fakeRealm.givenWhere<LiveLocationShareAggregatedSummaryEntity>()
|
||||||
|
.givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, eventId)
|
||||||
|
.givenFindFirst(aggregatedSummaryEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenCreateRedactEventWithLocalEcho(eventIds: List<String>): List<Event> {
|
||||||
|
return eventIds.map { eventId ->
|
||||||
|
fakeLocalEchoEventFactory.givenCreateRedactEvent(
|
||||||
|
eventId = eventId,
|
||||||
|
withLocalEcho = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenPostRedaction(redactEvents: List<Event>) {
|
||||||
|
redactEvents.forEach {
|
||||||
|
fakeEventSenderProcessor.givenPostRedaction(event = it, reason = A_REASON)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun verifyCreateRedactEventForEventIds(eventIds: List<String>) {
|
||||||
|
eventIds.forEach { eventId ->
|
||||||
|
fakeLocalEchoEventFactory.verifyCreateRedactEvent(
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
eventId = eventId,
|
||||||
|
reason = A_REASON
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun verifyCreateLocalEchoForEvents(events: List<Event>) {
|
||||||
|
events.forEach { redactionEvent ->
|
||||||
|
fakeLocalEchoEventFactory.verifyCreateLocalEcho(redactionEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.room.location
|
||||||
|
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.amshove.kluent.shouldBe
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
||||||
|
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
|
||||||
|
import org.matrix.android.sdk.test.fakes.FakeRealm
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenDelete
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenEqualTo
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenFindFirst
|
||||||
|
|
||||||
|
private const val AN_EVENT_ID = "event-id"
|
||||||
|
private const val A_REDACTED_EVENT_ID = "redacted-event-id"
|
||||||
|
|
||||||
|
@ExperimentalCoroutinesApi
|
||||||
|
class LiveLocationShareRedactionEventProcessorTest {
|
||||||
|
|
||||||
|
private val liveLocationShareRedactionEventProcessor = LiveLocationShareRedactionEventProcessor()
|
||||||
|
private val fakeRealm = FakeRealm()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given an event when checking if it should be processed then only event of type REDACTED is processed`() {
|
||||||
|
val eventId = AN_EVENT_ID
|
||||||
|
val eventType = EventType.REDACTION
|
||||||
|
val insertType = EventInsertType.INCREMENTAL_SYNC
|
||||||
|
|
||||||
|
val result = liveLocationShareRedactionEventProcessor.shouldProcess(
|
||||||
|
eventId = eventId,
|
||||||
|
eventType = eventType,
|
||||||
|
insertType = insertType
|
||||||
|
)
|
||||||
|
|
||||||
|
result shouldBe true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given an event when checking if it should be processed then local echo is not processed`() {
|
||||||
|
val eventId = AN_EVENT_ID
|
||||||
|
val eventType = EventType.REDACTION
|
||||||
|
val insertType = EventInsertType.LOCAL_ECHO
|
||||||
|
|
||||||
|
val result = liveLocationShareRedactionEventProcessor.shouldProcess(
|
||||||
|
eventId = eventId,
|
||||||
|
eventType = eventType,
|
||||||
|
insertType = insertType
|
||||||
|
)
|
||||||
|
|
||||||
|
result shouldBe false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a redacted live location share event when processing it then related summaries are deleted from database`() = runTest {
|
||||||
|
val event = Event(eventId = AN_EVENT_ID, redacts = A_REDACTED_EVENT_ID)
|
||||||
|
val redactedEventEntity = EventEntity(eventId = A_REDACTED_EVENT_ID, type = EventType.STATE_ROOM_BEACON_INFO.first())
|
||||||
|
fakeRealm.givenWhere<EventEntity>()
|
||||||
|
.givenEqualTo(EventEntityFields.EVENT_ID, A_REDACTED_EVENT_ID)
|
||||||
|
.givenFindFirst(redactedEventEntity)
|
||||||
|
val liveSummary = mockk<LiveLocationShareAggregatedSummaryEntity>()
|
||||||
|
every { liveSummary.eventId } returns A_REDACTED_EVENT_ID
|
||||||
|
liveSummary.givenDelete()
|
||||||
|
fakeRealm.givenWhere<LiveLocationShareAggregatedSummaryEntity>()
|
||||||
|
.givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, A_REDACTED_EVENT_ID)
|
||||||
|
.givenFindFirst(liveSummary)
|
||||||
|
val annotationsSummary = mockk<EventAnnotationsSummaryEntity>()
|
||||||
|
every { annotationsSummary.eventId } returns A_REDACTED_EVENT_ID
|
||||||
|
annotationsSummary.givenDelete()
|
||||||
|
fakeRealm.givenWhere<EventAnnotationsSummaryEntity>()
|
||||||
|
.givenEqualTo(EventAnnotationsSummaryEntityFields.EVENT_ID, A_REDACTED_EVENT_ID)
|
||||||
|
.givenFindFirst(annotationsSummary)
|
||||||
|
|
||||||
|
liveLocationShareRedactionEventProcessor.process(fakeRealm.instance, event = event)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
liveSummary.deleteFromRealm()
|
||||||
|
annotationsSummary.deleteFromRealm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -27,4 +27,8 @@ internal class FakeEventSenderProcessor : EventSenderProcessor by mockk() {
|
|||||||
fun givenPostEventReturns(event: Event, cancelable: Cancelable) {
|
fun givenPostEventReturns(event: Event, cancelable: Cancelable) {
|
||||||
every { postEvent(event) } returns cancelable
|
every { postEvent(event) } returns cancelable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun givenPostRedaction(event: Event, reason: String?) {
|
||||||
|
every { postRedaction(event, reason) } returns mockk()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,24 +46,6 @@ internal class FakeLocalEchoEventFactory {
|
|||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
fun givenCreateLiveLocationEvent(withLocalEcho: Boolean): Event {
|
|
||||||
val event = Event()
|
|
||||||
every {
|
|
||||||
instance.createLiveLocationEvent(
|
|
||||||
beaconInfoEventId = any(),
|
|
||||||
roomId = any(),
|
|
||||||
latitude = any(),
|
|
||||||
longitude = any(),
|
|
||||||
uncertainty = any()
|
|
||||||
)
|
|
||||||
} returns event
|
|
||||||
|
|
||||||
if (withLocalEcho) {
|
|
||||||
every { instance.createLocalEcho(event) } just runs
|
|
||||||
}
|
|
||||||
return event
|
|
||||||
}
|
|
||||||
|
|
||||||
fun verifyCreateStaticLocationEvent(
|
fun verifyCreateStaticLocationEvent(
|
||||||
roomId: String,
|
roomId: String,
|
||||||
latitude: Double,
|
latitude: Double,
|
||||||
@ -82,6 +64,24 @@ internal class FakeLocalEchoEventFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun givenCreateLiveLocationEvent(withLocalEcho: Boolean): Event {
|
||||||
|
val event = Event()
|
||||||
|
every {
|
||||||
|
instance.createLiveLocationEvent(
|
||||||
|
beaconInfoEventId = any(),
|
||||||
|
roomId = any(),
|
||||||
|
latitude = any(),
|
||||||
|
longitude = any(),
|
||||||
|
uncertainty = any()
|
||||||
|
)
|
||||||
|
} returns event
|
||||||
|
|
||||||
|
if (withLocalEcho) {
|
||||||
|
every { instance.createLocalEcho(event) } just runs
|
||||||
|
}
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
fun verifyCreateLiveLocationEvent(
|
fun verifyCreateLiveLocationEvent(
|
||||||
roomId: String,
|
roomId: String,
|
||||||
beaconInfoEventId: String,
|
beaconInfoEventId: String,
|
||||||
@ -100,6 +100,36 @@ internal class FakeLocalEchoEventFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun givenCreateRedactEvent(eventId: String, withLocalEcho: Boolean): Event {
|
||||||
|
val event = Event()
|
||||||
|
every {
|
||||||
|
instance.createRedactEvent(
|
||||||
|
roomId = any(),
|
||||||
|
eventId = eventId,
|
||||||
|
reason = any()
|
||||||
|
)
|
||||||
|
} returns event
|
||||||
|
|
||||||
|
if (withLocalEcho) {
|
||||||
|
every { instance.createLocalEcho(event) } just runs
|
||||||
|
}
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyCreateRedactEvent(
|
||||||
|
roomId: String,
|
||||||
|
eventId: String,
|
||||||
|
reason: String?
|
||||||
|
) {
|
||||||
|
verify {
|
||||||
|
instance.createRedactEvent(
|
||||||
|
roomId = roomId,
|
||||||
|
eventId = eventId,
|
||||||
|
reason = reason
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun verifyCreateLocalEcho(event: Event) {
|
fun verifyCreateLocalEcho(event: Event) {
|
||||||
verify { instance.createLocalEcho(event) }
|
verify { instance.createLocalEcho(event) }
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,13 @@ package org.matrix.android.sdk.test.fakes
|
|||||||
|
|
||||||
import io.mockk.MockKVerificationScope
|
import io.mockk.MockKVerificationScope
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
|
import io.mockk.just
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
import io.mockk.runs
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmModel
|
import io.realm.RealmModel
|
||||||
|
import io.realm.RealmObject
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
import io.realm.RealmResults
|
import io.realm.RealmResults
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
@ -97,3 +100,18 @@ inline fun <reified T : RealmModel> RealmQuery<T>.givenIsNotNull(
|
|||||||
every { isNotNull(fieldName) } returns this
|
every { isNotNull(fieldName) } returns this
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : RealmModel> RealmQuery<T>.givenLessThan(
|
||||||
|
fieldName: String,
|
||||||
|
value: Long
|
||||||
|
): RealmQuery<T> {
|
||||||
|
every { lessThan(fieldName, value) } returns this
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should be called on a mocked RealmObject and not on a real RealmObject so that the underlying final method is mocked.
|
||||||
|
*/
|
||||||
|
fun RealmObject.givenDelete() {
|
||||||
|
every { deleteFromRealm() } just runs
|
||||||
|
}
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.test.fakes
|
||||||
|
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.slot
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import org.matrix.android.sdk.internal.database.awaitTransaction
|
||||||
|
|
||||||
|
internal class FakeRealmConfiguration {
|
||||||
|
|
||||||
|
init {
|
||||||
|
mockkStatic("org.matrix.android.sdk.internal.database.AsyncTransactionKt")
|
||||||
|
}
|
||||||
|
|
||||||
|
val instance = mockk<RealmConfiguration>()
|
||||||
|
|
||||||
|
fun <T> givenAwaitTransaction(realm: Realm) {
|
||||||
|
val transaction = slot<suspend (Realm) -> T>()
|
||||||
|
coEvery { awaitTransaction(instance, capture(transaction)) } coAnswers {
|
||||||
|
secondArg<suspend (Realm) -> T>().invoke(realm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -437,7 +437,7 @@ dependencies {
|
|||||||
implementation 'com.facebook.stetho:stetho:1.6.0'
|
implementation 'com.facebook.stetho:stetho:1.6.0'
|
||||||
|
|
||||||
// Phone number https://github.com/google/libphonenumber
|
// Phone number https://github.com/google/libphonenumber
|
||||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.51'
|
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.52'
|
||||||
|
|
||||||
// FlowBinding
|
// FlowBinding
|
||||||
implementation libs.github.flowBinding
|
implementation libs.github.flowBinding
|
||||||
|
@ -21,11 +21,11 @@ import im.vector.app.features.onboarding.AuthenticationDescription
|
|||||||
|
|
||||||
fun AuthenticationDescription.AuthenticationType.toAnalyticsType() = when (this) {
|
fun AuthenticationDescription.AuthenticationType.toAnalyticsType() = when (this) {
|
||||||
AuthenticationDescription.AuthenticationType.Password -> Signup.AuthenticationType.Password
|
AuthenticationDescription.AuthenticationType.Password -> Signup.AuthenticationType.Password
|
||||||
AuthenticationDescription.AuthenticationType.Apple -> Signup.AuthenticationType.Apple
|
AuthenticationDescription.AuthenticationType.Apple -> Signup.AuthenticationType.Apple
|
||||||
AuthenticationDescription.AuthenticationType.Facebook -> Signup.AuthenticationType.Facebook
|
AuthenticationDescription.AuthenticationType.Facebook -> Signup.AuthenticationType.Facebook
|
||||||
AuthenticationDescription.AuthenticationType.GitHub -> Signup.AuthenticationType.GitHub
|
AuthenticationDescription.AuthenticationType.GitHub -> Signup.AuthenticationType.GitHub
|
||||||
AuthenticationDescription.AuthenticationType.GitLab -> Signup.AuthenticationType.GitLab
|
AuthenticationDescription.AuthenticationType.GitLab -> Signup.AuthenticationType.GitLab
|
||||||
AuthenticationDescription.AuthenticationType.Google -> Signup.AuthenticationType.Google
|
AuthenticationDescription.AuthenticationType.Google -> Signup.AuthenticationType.Google
|
||||||
AuthenticationDescription.AuthenticationType.SSO -> Signup.AuthenticationType.SSO
|
AuthenticationDescription.AuthenticationType.SSO -> Signup.AuthenticationType.SSO
|
||||||
AuthenticationDescription.AuthenticationType.Other -> Signup.AuthenticationType.Other
|
AuthenticationDescription.AuthenticationType.Other -> Signup.AuthenticationType.Other
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ class AvatarRenderer @Inject constructor(
|
|||||||
render(
|
render(
|
||||||
GlideApp.with(imageView),
|
GlideApp.with(imageView),
|
||||||
matrixItem,
|
matrixItem,
|
||||||
DrawableImageViewTarget(imageView)
|
DrawableImageViewTarget(imageView),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +103,7 @@ class AvatarRenderer @Inject constructor(
|
|||||||
render(
|
render(
|
||||||
glideRequests,
|
glideRequests,
|
||||||
matrixItem,
|
matrixItem,
|
||||||
DrawableImageViewTarget(imageView)
|
DrawableImageViewTarget(imageView),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ class AvatarRenderer @Inject constructor(
|
|||||||
val matrixItem = MatrixItem.UserItem(
|
val matrixItem = MatrixItem.UserItem(
|
||||||
// Need an id starting with @
|
// Need an id starting with @
|
||||||
id = "@${mappedContact.displayName}",
|
id = "@${mappedContact.displayName}",
|
||||||
displayName = mappedContact.displayName
|
displayName = mappedContact.displayName,
|
||||||
)
|
)
|
||||||
|
|
||||||
val placeholder = getPlaceholderDrawable(matrixItem)
|
val placeholder = getPlaceholderDrawable(matrixItem)
|
||||||
@ -140,7 +140,7 @@ class AvatarRenderer @Inject constructor(
|
|||||||
val matrixItem = MatrixItem.UserItem(
|
val matrixItem = MatrixItem.UserItem(
|
||||||
// Need an id starting with @
|
// Need an id starting with @
|
||||||
id = profileInfo.matrixId,
|
id = profileInfo.matrixId,
|
||||||
displayName = profileInfo.displayName
|
displayName = profileInfo.displayName,
|
||||||
)
|
)
|
||||||
|
|
||||||
val placeholder = getPlaceholderDrawable(matrixItem)
|
val placeholder = getPlaceholderDrawable(matrixItem)
|
||||||
@ -215,7 +215,7 @@ class AvatarRenderer @Inject constructor(
|
|||||||
.bold()
|
.bold()
|
||||||
.endConfig()
|
.endConfig()
|
||||||
.buildRect(matrixItem.firstLetterOfDisplayName(), avatarColor)
|
.buildRect(matrixItem.firstLetterOfDisplayName(), avatarColor)
|
||||||
.toBitmap(width = iconSize, height = iconSize)
|
.toBitmap(width = iconSize, height = iconSize),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,7 +231,7 @@ class AvatarRenderer @Inject constructor(
|
|||||||
addPlaceholder: Boolean
|
addPlaceholder: Boolean
|
||||||
) {
|
) {
|
||||||
val transformations = mutableListOf<Transformation<Bitmap>>(
|
val transformations = mutableListOf<Transformation<Bitmap>>(
|
||||||
BlurTransformation(20, sampling)
|
BlurTransformation(20, sampling),
|
||||||
)
|
)
|
||||||
if (colorFilter != null) {
|
if (colorFilter != null) {
|
||||||
transformations.add(ColorFilterTransformation(colorFilter))
|
transformations.add(ColorFilterTransformation(colorFilter))
|
||||||
|
@ -219,7 +219,7 @@ class HomeActivity :
|
|||||||
is HomeActivitySharedAction.ShowSpaceSettings -> showSpaceSettings(sharedAction.spaceId)
|
is HomeActivitySharedAction.ShowSpaceSettings -> showSpaceSettings(sharedAction.spaceId)
|
||||||
is HomeActivitySharedAction.OpenSpaceInvite -> openSpaceInvite(sharedAction.spaceId)
|
is HomeActivitySharedAction.OpenSpaceInvite -> openSpaceInvite(sharedAction.spaceId)
|
||||||
HomeActivitySharedAction.SendSpaceFeedBack -> bugReporter.openBugReportScreen(this, ReportType.SPACE_BETA_FEEDBACK)
|
HomeActivitySharedAction.SendSpaceFeedBack -> bugReporter.openBugReportScreen(this, ReportType.SPACE_BETA_FEEDBACK)
|
||||||
HomeActivitySharedAction.OnCloseSpace -> onCloseSpace()
|
HomeActivitySharedAction.OnCloseSpace -> onCloseSpace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.launchIn(lifecycleScope)
|
.launchIn(lifecycleScope)
|
||||||
|
@ -84,6 +84,4 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
|
|||||||
data class StartChatEffect(val type: ChatEffect) : RoomDetailViewEvents()
|
data class StartChatEffect(val type: ChatEffect) : RoomDetailViewEvents()
|
||||||
object StopChatEffects : RoomDetailViewEvents()
|
object StopChatEffects : RoomDetailViewEvents()
|
||||||
object RoomReplacementStarted : RoomDetailViewEvents()
|
object RoomReplacementStarted : RoomDetailViewEvents()
|
||||||
|
|
||||||
data class ChangeLocationIndicator(val isVisible: Boolean) : RoomDetailViewEvents()
|
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,8 @@ data class RoomDetailViewState(
|
|||||||
val switchToParentSpace: Boolean = false,
|
val switchToParentSpace: Boolean = false,
|
||||||
val rootThreadEventId: String? = null,
|
val rootThreadEventId: String? = null,
|
||||||
val threadNotificationBadgeState: ThreadNotificationBadgeState = ThreadNotificationBadgeState(),
|
val threadNotificationBadgeState: ThreadNotificationBadgeState = ThreadNotificationBadgeState(),
|
||||||
val typingUsers: List<SenderInfo>? = null
|
val typingUsers: List<SenderInfo>? = null,
|
||||||
|
val isSharingLiveLocation: Boolean = false,
|
||||||
) : MavericksState {
|
) : MavericksState {
|
||||||
|
|
||||||
constructor(args: TimelineArgs) : this(
|
constructor(args: TimelineArgs) : this(
|
||||||
|
@ -498,7 +498,6 @@ class TimelineFragment @Inject constructor(
|
|||||||
RoomDetailViewEvents.StopChatEffects -> handleStopChatEffects()
|
RoomDetailViewEvents.StopChatEffects -> handleStopChatEffects()
|
||||||
is RoomDetailViewEvents.DisplayAndAcceptCall -> acceptIncomingCall(it)
|
is RoomDetailViewEvents.DisplayAndAcceptCall -> acceptIncomingCall(it)
|
||||||
RoomDetailViewEvents.RoomReplacementStarted -> handleRoomReplacement()
|
RoomDetailViewEvents.RoomReplacementStarted -> handleRoomReplacement()
|
||||||
is RoomDetailViewEvents.ChangeLocationIndicator -> handleChangeLocationIndicator(it)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -663,10 +662,6 @@ class TimelineFragment @Inject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeLocationIndicator(event: RoomDetailViewEvents.ChangeLocationIndicator) {
|
|
||||||
views.locationLiveStatusIndicator.isVisible = event.isVisible
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun displayErrorMessage(error: RoomDetailViewEvents.Failure) {
|
private fun displayErrorMessage(error: RoomDetailViewEvents.Failure) {
|
||||||
if (error.showInDialog) displayErrorDialog(error.throwable) else showErrorInSnackbar(error.throwable)
|
if (error.showInDialog) displayErrorDialog(error.throwable) else showErrorInSnackbar(error.throwable)
|
||||||
}
|
}
|
||||||
@ -1686,6 +1681,11 @@ class TimelineFragment @Inject constructor(
|
|||||||
} else if (mainState.asyncInviter.complete) {
|
} else if (mainState.asyncInviter.complete) {
|
||||||
vectorBaseActivity.finish()
|
vectorBaseActivity.finish()
|
||||||
}
|
}
|
||||||
|
updateLiveLocationIndicator(mainState.isSharingLiveLocation)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateLiveLocationIndicator(isSharingLiveLocation: Boolean) {
|
||||||
|
views.locationLiveStatusIndicator.isVisible = isSharingLiveLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun FragmentTimelineBinding.hideComposerViews() {
|
private fun FragmentTimelineBinding.hideComposerViews() {
|
||||||
@ -1706,7 +1706,7 @@ class TimelineFragment @Inject constructor(
|
|||||||
|
|
||||||
private fun renderToolbar(roomSummary: RoomSummary?) {
|
private fun renderToolbar(roomSummary: RoomSummary?) {
|
||||||
when {
|
when {
|
||||||
isLocalRoom() -> {
|
isLocalRoom() -> {
|
||||||
views.includeRoomToolbar.roomToolbarContentView.isVisible = false
|
views.includeRoomToolbar.roomToolbarContentView.isVisible = false
|
||||||
views.includeThreadToolbar.roomToolbarThreadConstraintLayout.isVisible = false
|
views.includeThreadToolbar.roomToolbarThreadConstraintLayout.isVisible = false
|
||||||
setupToolbar(views.roomToolbar)
|
setupToolbar(views.roomToolbar)
|
||||||
@ -1724,7 +1724,7 @@ class TimelineFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
views.includeThreadToolbar.roomToolbarThreadTitleTextView.text = resources.getText(R.string.thread_timeline_title)
|
views.includeThreadToolbar.roomToolbarThreadTitleTextView.text = resources.getText(R.string.thread_timeline_title)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
views.includeRoomToolbar.roomToolbarContentView.isVisible = true
|
views.includeRoomToolbar.roomToolbarContentView.isVisible = true
|
||||||
views.includeThreadToolbar.roomToolbarThreadConstraintLayout.isVisible = false
|
views.includeThreadToolbar.roomToolbarThreadConstraintLayout.isVisible = false
|
||||||
if (roomSummary == null) {
|
if (roomSummary == null) {
|
||||||
|
@ -48,6 +48,7 @@ import im.vector.app.features.call.webrtc.WebRtcCallManager
|
|||||||
import im.vector.app.features.createdirect.DirectRoomHelper
|
import im.vector.app.features.createdirect.DirectRoomHelper
|
||||||
import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy
|
import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy
|
||||||
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider
|
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider
|
||||||
|
import im.vector.app.features.home.room.detail.location.RedactLiveLocationShareEventUseCase
|
||||||
import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandler
|
import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandler
|
||||||
import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory
|
import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory
|
||||||
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
|
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
|
||||||
@ -105,6 +106,7 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
|||||||
import org.matrix.android.sdk.api.session.room.read.ReadService
|
import org.matrix.android.sdk.api.session.room.read.ReadService
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.isLiveLocation
|
||||||
import org.matrix.android.sdk.api.session.sync.SyncRequestState
|
import org.matrix.android.sdk.api.session.sync.SyncRequestState
|
||||||
import org.matrix.android.sdk.api.session.threads.ThreadNotificationBadgeState
|
import org.matrix.android.sdk.api.session.threads.ThreadNotificationBadgeState
|
||||||
import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
|
import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
|
||||||
@ -135,6 +137,7 @@ class TimelineViewModel @AssistedInject constructor(
|
|||||||
private val notificationDrawerManager: NotificationDrawerManager,
|
private val notificationDrawerManager: NotificationDrawerManager,
|
||||||
private val locationSharingServiceConnection: LocationSharingServiceConnection,
|
private val locationSharingServiceConnection: LocationSharingServiceConnection,
|
||||||
private val stopLiveLocationShareUseCase: StopLiveLocationShareUseCase,
|
private val stopLiveLocationShareUseCase: StopLiveLocationShareUseCase,
|
||||||
|
private val redactLiveLocationShareEventUseCase: RedactLiveLocationShareEventUseCase,
|
||||||
timelineFactory: TimelineFactory,
|
timelineFactory: TimelineFactory,
|
||||||
spaceStateHandler: SpaceStateHandler,
|
spaceStateHandler: SpaceStateHandler,
|
||||||
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
|
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
|
||||||
@ -770,7 +773,13 @@ class TimelineViewModel @AssistedInject constructor(
|
|||||||
|
|
||||||
private fun handleRedactEvent(action: RoomDetailAction.RedactAction) {
|
private fun handleRedactEvent(action: RoomDetailAction.RedactAction) {
|
||||||
val event = room.getTimelineEvent(action.targetEventId) ?: return
|
val event = room.getTimelineEvent(action.targetEventId) ?: return
|
||||||
room.sendService().redactEvent(event.root, action.reason)
|
if (event.isLiveLocation()) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
redactLiveLocationShareEventUseCase.execute(event.root, room, action.reason)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
room.sendService().redactEvent(event.root, action.reason)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUndoReact(action: RoomDetailAction.UndoReaction) {
|
private fun handleUndoReact(action: RoomDetailAction.UndoReaction) {
|
||||||
@ -1294,12 +1303,12 @@ class TimelineViewModel @AssistedInject constructor(
|
|||||||
_viewEvents.post(RoomDetailViewEvents.OnNewTimelineEvents(eventIds))
|
_viewEvents.post(RoomDetailViewEvents.OnNewTimelineEvents(eventIds))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLocationServiceRunning() {
|
override fun onLocationServiceRunning(roomIds: Set<String>) {
|
||||||
_viewEvents.post(RoomDetailViewEvents.ChangeLocationIndicator(isVisible = true))
|
setState { copy(isSharingLiveLocation = roomId in roomIds) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLocationServiceStopped() {
|
override fun onLocationServiceStopped() {
|
||||||
_viewEvents.post(RoomDetailViewEvents.ChangeLocationIndicator(isVisible = false))
|
setState { copy(isSharingLiveLocation = false) }
|
||||||
// Bind again in case user decides to share live location without leaving the room
|
// Bind again in case user decides to share live location without leaving the room
|
||||||
locationSharingServiceConnection.bind(this)
|
locationSharingServiceConnection.bind(this)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home.room.detail.location
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RedactLiveLocationShareEventUseCase @Inject constructor() {
|
||||||
|
|
||||||
|
suspend fun execute(event: Event, room: Room, reason: String?) {
|
||||||
|
event.eventId
|
||||||
|
?.takeUnless { it.isEmpty() }
|
||||||
|
?.let { room.locationSharingService().redactLiveLocationShare(it, reason) }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home.room.detail.timeline.action
|
||||||
|
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class CheckIfCanRedactEventUseCase @Inject constructor(
|
||||||
|
private val activeSessionHolder: ActiveSessionHolder
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun execute(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean {
|
||||||
|
// Only some event types are supported for the moment
|
||||||
|
val canRedactEventTypes = listOf(EventType.MESSAGE, EventType.STICKER) +
|
||||||
|
EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO
|
||||||
|
|
||||||
|
return event.root.getClearType() in canRedactEventTypes &&
|
||||||
|
// Message sent by the current user can always be redacted, else check permission for messages sent by other users
|
||||||
|
(event.root.senderId == activeSessionHolder.getActiveSession().myUserId || actionPermissions.canRedact)
|
||||||
|
}
|
||||||
|
}
|
@ -82,6 +82,7 @@ class MessageActionsViewModel @AssistedInject constructor(
|
|||||||
private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
|
private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
|
||||||
private val vectorPreferences: VectorPreferences,
|
private val vectorPreferences: VectorPreferences,
|
||||||
private val checkIfCanReplyEventUseCase: CheckIfCanReplyEventUseCase,
|
private val checkIfCanReplyEventUseCase: CheckIfCanReplyEventUseCase,
|
||||||
|
private val checkIfCanRedactEventUseCase: CheckIfCanRedactEventUseCase,
|
||||||
) : VectorViewModel<MessageActionState, MessageActionsAction, EmptyViewEvents>(initialState) {
|
) : VectorViewModel<MessageActionState, MessageActionsAction, EmptyViewEvents>(initialState) {
|
||||||
|
|
||||||
private val informationData = initialState.informationData
|
private val informationData = initialState.informationData
|
||||||
@ -518,12 +519,7 @@ class MessageActionsViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun canRedact(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean {
|
private fun canRedact(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean {
|
||||||
// Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment
|
return checkIfCanRedactEventUseCase.execute(event, actionPermissions)
|
||||||
if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START) return false
|
|
||||||
// Message sent by the current user can always be redacted
|
|
||||||
if (event.root.senderId == session.myUserId) return true
|
|
||||||
// Check permission for messages sent by other users
|
|
||||||
return actionPermissions.canRedact
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun canRetry(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean {
|
private fun canRetry(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean {
|
||||||
|
@ -63,10 +63,10 @@ class EncryptionItemFactory @Inject constructor(
|
|||||||
isDirect && RoomLocalEcho.isLocalEchoId(event.root.roomId.orEmpty()) -> {
|
isDirect && RoomLocalEcho.isLocalEchoId(event.root.roomId.orEmpty()) -> {
|
||||||
R.string.direct_room_encryption_enabled_tile_description_future
|
R.string.direct_room_encryption_enabled_tile_description_future
|
||||||
}
|
}
|
||||||
isDirect -> {
|
isDirect -> {
|
||||||
R.string.direct_room_encryption_enabled_tile_description
|
R.string.direct_room_encryption_enabled_tile_description
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
R.string.encryption_enabled_tile_description
|
R.string.encryption_enabled_tile_description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,6 @@ import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
|||||||
import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider
|
import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.MergedTimelineEventVisibilityStateChangedListener
|
import im.vector.app.features.home.room.detail.timeline.helper.MergedTimelineEventVisibilityStateChangedListener
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper
|
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.canBeMerged
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.isRoomConfiguration
|
import im.vector.app.features.home.room.detail.timeline.helper.isRoomConfiguration
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem
|
import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationItem
|
import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationItem
|
||||||
@ -35,6 +34,7 @@ import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovement
|
|||||||
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
|
import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
@ -53,6 +53,7 @@ class MergedHeaderItemFactory @Inject constructor(
|
|||||||
private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper
|
private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
private val mergeableEventTypes = listOf(EventType.STATE_ROOM_MEMBER, EventType.STATE_ROOM_SERVER_ACL)
|
||||||
private val collapsedEventIds = linkedSetOf<Long>()
|
private val collapsedEventIds = linkedSetOf<Long>()
|
||||||
private val mergeItemCollapseStates = HashMap<Long, Boolean>()
|
private val mergeItemCollapseStates = HashMap<Long, Boolean>()
|
||||||
|
|
||||||
@ -78,19 +79,65 @@ class MergedHeaderItemFactory @Inject constructor(
|
|||||||
callback: TimelineEventController.Callback?,
|
callback: TimelineEventController.Callback?,
|
||||||
requestModelBuild: () -> Unit
|
requestModelBuild: () -> Unit
|
||||||
): BasedMergedItem<*>? {
|
): BasedMergedItem<*>? {
|
||||||
return if (nextEvent?.root?.getClearType() == EventType.STATE_ROOM_CREATE &&
|
return when {
|
||||||
event.isRoomConfiguration(nextEvent.root.getClearContent()?.toModel<RoomCreateContent>()?.creator)) {
|
isStartOfRoomCreationSummary(event, nextEvent) ->
|
||||||
// It's the first item before room.create
|
buildRoomCreationMergedSummary(currentPosition, items, partialState, event, eventIdToHighlight, requestModelBuild, callback)
|
||||||
// Collapse all room configuration events
|
isStartOfSameTypeEventsSummary(event, nextEvent, addDaySeparator) ->
|
||||||
buildRoomCreationMergedSummary(currentPosition, items, partialState, event, eventIdToHighlight, requestModelBuild, callback)
|
buildSameTypeEventsMergedSummary(currentPosition, items, partialState, event, eventIdToHighlight, requestModelBuild, callback)
|
||||||
} else if (!event.canBeMerged() || (nextEvent?.root?.getClearType() == event.root.getClearType() && !addDaySeparator)) {
|
isStartOfRedactedEventsSummary(event, items, currentPosition, addDaySeparator) ->
|
||||||
null
|
buildRedactedEventsMergedSummary(currentPosition, items, partialState, event, eventIdToHighlight, requestModelBuild, callback)
|
||||||
} else {
|
else -> null
|
||||||
buildMembershipEventsMergedSummary(currentPosition, items, partialState, event, eventIdToHighlight, requestModelBuild, callback)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildMembershipEventsMergedSummary(
|
/**
|
||||||
|
* @param event the main timeline event
|
||||||
|
* @param nextEvent is an older event than event
|
||||||
|
*/
|
||||||
|
private fun isStartOfRoomCreationSummary(
|
||||||
|
event: TimelineEvent,
|
||||||
|
nextEvent: TimelineEvent?,
|
||||||
|
): Boolean {
|
||||||
|
// It's the first item before room.create
|
||||||
|
// Collapse all room configuration events
|
||||||
|
return nextEvent?.root?.getClearType() == EventType.STATE_ROOM_CREATE &&
|
||||||
|
event.isRoomConfiguration(nextEvent.root.getClearContent()?.toModel<RoomCreateContent>()?.creator)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param event the main timeline event
|
||||||
|
* @param nextEvent is an older event than event
|
||||||
|
* @param addDaySeparator true to add a day separator
|
||||||
|
*/
|
||||||
|
private fun isStartOfSameTypeEventsSummary(
|
||||||
|
event: TimelineEvent,
|
||||||
|
nextEvent: TimelineEvent?,
|
||||||
|
addDaySeparator: Boolean,
|
||||||
|
): Boolean {
|
||||||
|
return event.root.getClearType() in mergeableEventTypes &&
|
||||||
|
(nextEvent?.root?.getClearType() != event.root.getClearType() || addDaySeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param event the main timeline event
|
||||||
|
* @param items all known items, sorted from newer event to oldest event
|
||||||
|
* @param currentPosition the current position
|
||||||
|
* @param addDaySeparator true to add a day separator
|
||||||
|
*/
|
||||||
|
private fun isStartOfRedactedEventsSummary(
|
||||||
|
event: TimelineEvent,
|
||||||
|
items: List<TimelineEvent>,
|
||||||
|
currentPosition: Int,
|
||||||
|
addDaySeparator: Boolean,
|
||||||
|
): Boolean {
|
||||||
|
val nextNonRedactionEvent = items
|
||||||
|
.subList(fromIndex = currentPosition + 1, toIndex = items.size)
|
||||||
|
.find { it.root.getClearType() != EventType.REDACTION }
|
||||||
|
return event.root.isRedacted() &&
|
||||||
|
(!nextNonRedactionEvent?.root?.isRedacted().orFalse() || addDaySeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildSameTypeEventsMergedSummary(
|
||||||
currentPosition: Int,
|
currentPosition: Int,
|
||||||
items: List<TimelineEvent>,
|
items: List<TimelineEvent>,
|
||||||
partialState: TimelineEventController.PartialState,
|
partialState: TimelineEventController.PartialState,
|
||||||
@ -102,11 +149,42 @@ class MergedHeaderItemFactory @Inject constructor(
|
|||||||
val mergedEvents = timelineEventVisibilityHelper.prevSameTypeEvents(
|
val mergedEvents = timelineEventVisibilityHelper.prevSameTypeEvents(
|
||||||
items,
|
items,
|
||||||
currentPosition,
|
currentPosition,
|
||||||
2,
|
MIN_NUMBER_OF_MERGED_EVENTS,
|
||||||
eventIdToHighlight,
|
eventIdToHighlight,
|
||||||
partialState.rootThreadEventId,
|
partialState.rootThreadEventId,
|
||||||
partialState.isFromThreadTimeline()
|
partialState.isFromThreadTimeline()
|
||||||
)
|
)
|
||||||
|
return buildSimilarEventsMergedSummary(mergedEvents, partialState, event, eventIdToHighlight, requestModelBuild, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildRedactedEventsMergedSummary(
|
||||||
|
currentPosition: Int,
|
||||||
|
items: List<TimelineEvent>,
|
||||||
|
partialState: TimelineEventController.PartialState,
|
||||||
|
event: TimelineEvent,
|
||||||
|
eventIdToHighlight: String?,
|
||||||
|
requestModelBuild: () -> Unit,
|
||||||
|
callback: TimelineEventController.Callback?
|
||||||
|
): MergedSimilarEventsItem_? {
|
||||||
|
val mergedEvents = timelineEventVisibilityHelper.prevRedactedEvents(
|
||||||
|
items,
|
||||||
|
currentPosition,
|
||||||
|
MIN_NUMBER_OF_MERGED_EVENTS,
|
||||||
|
eventIdToHighlight,
|
||||||
|
partialState.rootThreadEventId,
|
||||||
|
partialState.isFromThreadTimeline()
|
||||||
|
)
|
||||||
|
return buildSimilarEventsMergedSummary(mergedEvents, partialState, event, eventIdToHighlight, requestModelBuild, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildSimilarEventsMergedSummary(
|
||||||
|
mergedEvents: List<TimelineEvent>,
|
||||||
|
partialState: TimelineEventController.PartialState,
|
||||||
|
event: TimelineEvent,
|
||||||
|
eventIdToHighlight: String?,
|
||||||
|
requestModelBuild: () -> Unit,
|
||||||
|
callback: TimelineEventController.Callback?
|
||||||
|
): MergedSimilarEventsItem_? {
|
||||||
return if (mergedEvents.isEmpty()) {
|
return if (mergedEvents.isEmpty()) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
@ -127,7 +205,7 @@ class MergedHeaderItemFactory @Inject constructor(
|
|||||||
)
|
)
|
||||||
mergedData.add(data)
|
mergedData.add(data)
|
||||||
}
|
}
|
||||||
val mergedEventIds = mergedEvents.map { it.localId }
|
val mergedEventIds = mergedEvents.map { it.localId }.toSet()
|
||||||
// We try to find if one of the item id were used as mergeItemCollapseStates key
|
// We try to find if one of the item id were used as mergeItemCollapseStates key
|
||||||
// => handle case where paginating from mergeable events and we get more
|
// => handle case where paginating from mergeable events and we get more
|
||||||
val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull()
|
val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull()
|
||||||
@ -140,12 +218,7 @@ class MergedHeaderItemFactory @Inject constructor(
|
|||||||
collapsedEventIds.removeAll(mergedEventIds)
|
collapsedEventIds.removeAll(mergedEventIds)
|
||||||
}
|
}
|
||||||
val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() }
|
val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() }
|
||||||
val summaryTitleResId = when (event.root.getClearType()) {
|
getSummaryTitleResId(event.root)?.let { summaryTitle ->
|
||||||
EventType.STATE_ROOM_MEMBER -> R.plurals.membership_changes
|
|
||||||
EventType.STATE_ROOM_SERVER_ACL -> R.plurals.notice_room_server_acl_changes
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
summaryTitleResId?.let { summaryTitle ->
|
|
||||||
val attributes = MergedSimilarEventsItem.Attributes(
|
val attributes = MergedSimilarEventsItem.Attributes(
|
||||||
summaryTitleResId = summaryTitle,
|
summaryTitleResId = summaryTitle,
|
||||||
isCollapsed = isCollapsed,
|
isCollapsed = isCollapsed,
|
||||||
@ -168,6 +241,16 @@ class MergedHeaderItemFactory @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getSummaryTitleResId(event: Event): Int? {
|
||||||
|
val type = event.getClearType()
|
||||||
|
return when {
|
||||||
|
type == EventType.STATE_ROOM_MEMBER -> R.plurals.membership_changes
|
||||||
|
type == EventType.STATE_ROOM_SERVER_ACL -> R.plurals.notice_room_server_acl_changes
|
||||||
|
event.isRedacted() -> R.plurals.room_removed_messages
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun buildRoomCreationMergedSummary(
|
private fun buildRoomCreationMergedSummary(
|
||||||
currentPosition: Int,
|
currentPosition: Int,
|
||||||
items: List<TimelineEvent>,
|
items: List<TimelineEvent>,
|
||||||
@ -191,7 +274,7 @@ class MergedHeaderItemFactory @Inject constructor(
|
|||||||
tmpPos--
|
tmpPos--
|
||||||
prevEvent = items.getOrNull(tmpPos)
|
prevEvent = items.getOrNull(tmpPos)
|
||||||
}
|
}
|
||||||
return if (mergedEvents.size > 2) {
|
return if (mergedEvents.size > MIN_NUMBER_OF_MERGED_EVENTS) {
|
||||||
var highlighted = false
|
var highlighted = false
|
||||||
val mergedData = ArrayList<BasedMergedItem.Data>(mergedEvents.size)
|
val mergedData = ArrayList<BasedMergedItem.Data>(mergedEvents.size)
|
||||||
mergedEvents.reversed()
|
mergedEvents.reversed()
|
||||||
@ -264,4 +347,8 @@ class MergedHeaderItemFactory @Inject constructor(
|
|||||||
fun isCollapsed(localId: Long): Boolean {
|
fun isCollapsed(localId: Long): Boolean {
|
||||||
return collapsedEventIds.contains(localId)
|
return collapsedEventIds.contains(localId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val MIN_NUMBER_OF_MERGED_EVENTS = 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,8 +113,14 @@ class TimelineItemFactory @Inject constructor(
|
|||||||
EventType.CALL_NEGOTIATE,
|
EventType.CALL_NEGOTIATE,
|
||||||
EventType.REACTION,
|
EventType.REACTION,
|
||||||
in EventType.POLL_RESPONSE,
|
in EventType.POLL_RESPONSE,
|
||||||
in EventType.POLL_END,
|
in EventType.POLL_END -> noticeItemFactory.create(params)
|
||||||
in EventType.BEACON_LOCATION_DATA -> noticeItemFactory.create(params)
|
in EventType.BEACON_LOCATION_DATA -> {
|
||||||
|
if (event.root.isRedacted()) {
|
||||||
|
messageItemFactory.create(params)
|
||||||
|
} else {
|
||||||
|
noticeItemFactory.create(params)
|
||||||
|
}
|
||||||
|
}
|
||||||
// Calls
|
// Calls
|
||||||
EventType.CALL_INVITE,
|
EventType.CALL_INVITE,
|
||||||
EventType.CALL_HANGUP,
|
EventType.CALL_HANGUP,
|
||||||
|
@ -51,12 +51,7 @@ object TimelineDisplayableEvents {
|
|||||||
EventType.STATE_ROOM_JOIN_RULES,
|
EventType.STATE_ROOM_JOIN_RULES,
|
||||||
EventType.KEY_VERIFICATION_DONE,
|
EventType.KEY_VERIFICATION_DONE,
|
||||||
EventType.KEY_VERIFICATION_CANCEL,
|
EventType.KEY_VERIFICATION_CANCEL,
|
||||||
) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO
|
) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO + EventType.BEACON_LOCATION_DATA
|
||||||
}
|
|
||||||
|
|
||||||
fun TimelineEvent.canBeMerged(): Boolean {
|
|
||||||
return root.getClearType() == EventType.STATE_ROOM_MEMBER ||
|
|
||||||
root.getClearType() == EventType.STATE_ROOM_SERVER_ACL
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TimelineEvent.isRoomConfiguration(roomCreatorUserId: String?): Boolean {
|
fun TimelineEvent.isRoomConfiguration(roomCreatorUserId: String?): Boolean {
|
||||||
|
@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail.timeline.helper
|
|||||||
|
|
||||||
import im.vector.app.core.extensions.localDateTime
|
import im.vector.app.core.extensions.localDateTime
|
||||||
import im.vector.app.core.resources.UserPreferencesProvider
|
import im.vector.app.core.resources.UserPreferencesProvider
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
import org.matrix.android.sdk.api.session.events.model.getRelationContent
|
import org.matrix.android.sdk.api.session.events.model.getRelationContent
|
||||||
@ -30,25 +31,38 @@ import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho
|
|||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class TimelineEventVisibilityHelper @Inject constructor(private val userPreferencesProvider: UserPreferencesProvider) {
|
class TimelineEventVisibilityHelper @Inject constructor(
|
||||||
|
private val userPreferencesProvider: UserPreferencesProvider,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private interface PredicateToStopSearch {
|
||||||
|
/**
|
||||||
|
* Indicate whether a search on events should stop by comparing 2 given successive events.
|
||||||
|
* @param oldEvent the oldest event between the 2 events to compare
|
||||||
|
* @param newEvent the more recent event between the 2 events to compare
|
||||||
|
*/
|
||||||
|
fun shouldStopSearch(oldEvent: Event, newEvent: Event): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param timelineEvents the events to search in
|
* @param timelineEvents the events to search in, sorted from oldest event to newer event
|
||||||
* @param index the index to start computing (inclusive)
|
* @param index the index to start computing (inclusive)
|
||||||
* @param minSize the minimum number of same type events to have sequentially, otherwise will return an empty list
|
* @param minSize the minimum number of same type events to have sequentially, otherwise will return an empty list
|
||||||
* @param eventIdToHighlight used to compute visibility
|
* @param eventIdToHighlight used to compute visibility
|
||||||
* @param rootThreadEventId the root thread event id if in a thread timeline
|
* @param rootThreadEventId the root thread event id if in a thread timeline
|
||||||
* @param isFromThreadTimeline true if the timeline is a thread
|
* @param isFromThreadTimeline true if the timeline is a thread
|
||||||
|
* @param predicateToStop events are taken until this condition is met
|
||||||
*
|
*
|
||||||
* @return a list of timeline events which have sequentially the same type following the next direction.
|
* @return a list of timeline events which meet sequentially the same criteria following the next direction.
|
||||||
*/
|
*/
|
||||||
private fun nextSameTypeEvents(
|
private fun nextEventsUntil(
|
||||||
timelineEvents: List<TimelineEvent>,
|
timelineEvents: List<TimelineEvent>,
|
||||||
index: Int,
|
index: Int,
|
||||||
minSize: Int,
|
minSize: Int,
|
||||||
eventIdToHighlight: String?,
|
eventIdToHighlight: String?,
|
||||||
rootThreadEventId: String?,
|
rootThreadEventId: String?,
|
||||||
isFromThreadTimeline: Boolean
|
isFromThreadTimeline: Boolean,
|
||||||
|
predicateToStop: PredicateToStopSearch
|
||||||
): List<TimelineEvent> {
|
): List<TimelineEvent> {
|
||||||
if (index >= timelineEvents.size - 1) {
|
if (index >= timelineEvents.size - 1) {
|
||||||
return emptyList()
|
return emptyList()
|
||||||
@ -65,13 +79,15 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen
|
|||||||
} else {
|
} else {
|
||||||
nextSubList.subList(0, indexOfNextDay)
|
nextSubList.subList(0, indexOfNextDay)
|
||||||
}
|
}
|
||||||
val indexOfFirstDifferentEventType = nextSameDayEvents.indexOfFirst { it.root.getClearType() != timelineEvent.root.getClearType() }
|
val indexOfFirstDifferentEvent = nextSameDayEvents.indexOfFirst {
|
||||||
val sameTypeEvents = if (indexOfFirstDifferentEventType == -1) {
|
predicateToStop.shouldStopSearch(oldEvent = timelineEvent.root, newEvent = it.root)
|
||||||
|
}
|
||||||
|
val similarEvents = if (indexOfFirstDifferentEvent == -1) {
|
||||||
nextSameDayEvents
|
nextSameDayEvents
|
||||||
} else {
|
} else {
|
||||||
nextSameDayEvents.subList(0, indexOfFirstDifferentEventType)
|
nextSameDayEvents.subList(0, indexOfFirstDifferentEvent)
|
||||||
}
|
}
|
||||||
val filteredSameTypeEvents = sameTypeEvents.filter {
|
val filteredSimilarEvents = similarEvents.filter {
|
||||||
shouldShowEvent(
|
shouldShowEvent(
|
||||||
timelineEvent = it,
|
timelineEvent = it,
|
||||||
highlightedEventId = eventIdToHighlight,
|
highlightedEventId = eventIdToHighlight,
|
||||||
@ -79,14 +95,11 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen
|
|||||||
rootThreadEventId = rootThreadEventId
|
rootThreadEventId = rootThreadEventId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (filteredSameTypeEvents.size < minSize) {
|
return if (filteredSimilarEvents.size < minSize) emptyList() else filteredSimilarEvents
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
return filteredSameTypeEvents
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param timelineEvents the events to search in
|
* @param timelineEvents the events to search in, sorted from newer event to oldest event
|
||||||
* @param index the index to start computing (inclusive)
|
* @param index the index to start computing (inclusive)
|
||||||
* @param minSize the minimum number of same type events to have sequentially, otherwise will return an empty list
|
* @param minSize the minimum number of same type events to have sequentially, otherwise will return an empty list
|
||||||
* @param eventIdToHighlight used to compute visibility
|
* @param eventIdToHighlight used to compute visibility
|
||||||
@ -107,7 +120,44 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen
|
|||||||
return prevSub
|
return prevSub
|
||||||
.reversed()
|
.reversed()
|
||||||
.let {
|
.let {
|
||||||
nextSameTypeEvents(it, 0, minSize, eventIdToHighlight, rootThreadEventId, isFromThreadTimeline)
|
nextEventsUntil(it, 0, minSize, eventIdToHighlight, rootThreadEventId, isFromThreadTimeline, object : PredicateToStopSearch {
|
||||||
|
override fun shouldStopSearch(oldEvent: Event, newEvent: Event): Boolean {
|
||||||
|
return oldEvent.getClearType() != newEvent.getClearType()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param timelineEvents the events to search in, sorted from newer event to oldest event
|
||||||
|
* @param index the index to start computing (inclusive)
|
||||||
|
* @param minSize the minimum number of same type events to have sequentially, otherwise will return an empty list
|
||||||
|
* @param eventIdToHighlight used to compute visibility
|
||||||
|
* @param rootThreadEventId the root thread eventId
|
||||||
|
* @param isFromThreadTimeline true if the timeline is a thread
|
||||||
|
*
|
||||||
|
* @return a list of timeline events which are all redacted following the prev direction.
|
||||||
|
*/
|
||||||
|
fun prevRedactedEvents(
|
||||||
|
timelineEvents: List<TimelineEvent>,
|
||||||
|
index: Int,
|
||||||
|
minSize: Int,
|
||||||
|
eventIdToHighlight: String?,
|
||||||
|
rootThreadEventId: String?,
|
||||||
|
isFromThreadTimeline: Boolean
|
||||||
|
): List<TimelineEvent> {
|
||||||
|
val prevSub = timelineEvents
|
||||||
|
.subList(0, index + 1)
|
||||||
|
// Ensure to not take the REDACTION events into account
|
||||||
|
.filter { it.root.getClearType() != EventType.REDACTION }
|
||||||
|
return prevSub
|
||||||
|
.reversed()
|
||||||
|
.let {
|
||||||
|
nextEventsUntil(it, 0, minSize, eventIdToHighlight, rootThreadEventId, isFromThreadTimeline, object : PredicateToStopSearch {
|
||||||
|
override fun shouldStopSearch(oldEvent: Event, newEvent: Event): Boolean {
|
||||||
|
return oldEvent.isRedacted() && !newEvent.isRedacted()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,6 +241,10 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen
|
|||||||
} else root.eventId != rootThreadEventId
|
} else root.eventId != rootThreadEventId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (root.getClearType() in EventType.BEACON_LOCATION_DATA) {
|
||||||
|
return !root.isRedacted()
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,8 +84,6 @@ class UpgradeRoomViewModelTask @Inject constructor(
|
|||||||
// autoJoin = currentInfo.autoJoin ?: false,
|
// autoJoin = currentInfo.autoJoin ?: false,
|
||||||
suggested = currentInfo.suggested
|
suggested = currentInfo.suggested
|
||||||
)
|
)
|
||||||
|
|
||||||
parentSpace.removeChildren(params.roomId)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
|
@ -85,27 +85,27 @@ class EventHtmlRenderer @Inject constructor(
|
|||||||
} else {
|
} else {
|
||||||
builder
|
builder
|
||||||
}
|
}
|
||||||
.usePlugin(
|
.usePlugin(
|
||||||
MarkwonInlineParserPlugin.create(
|
MarkwonInlineParserPlugin.create(
|
||||||
/* Configuring the Markwon inline formatting processor.
|
/* Configuring the Markwon inline formatting processor.
|
||||||
* Default settings are all Markdown features. Turn those off, only using the
|
* Default settings are all Markdown features. Turn those off, only using the
|
||||||
* inline HTML processor and HTML entities processor.
|
* inline HTML processor and HTML entities processor.
|
||||||
*/
|
*/
|
||||||
MarkwonInlineParser.factoryBuilderNoDefaults()
|
MarkwonInlineParser.factoryBuilderNoDefaults()
|
||||||
.addInlineProcessor(HtmlInlineProcessor()) // use inline HTML processor
|
.addInlineProcessor(HtmlInlineProcessor()) // use inline HTML processor
|
||||||
.addInlineProcessor(EntityInlineProcessor()) // use HTML entities processor
|
.addInlineProcessor(EntityInlineProcessor()) // use HTML entities processor
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
.usePlugin(object : AbstractMarkwonPlugin() {
|
||||||
.usePlugin(object : AbstractMarkwonPlugin() {
|
override fun configureParser(builder: Parser.Builder) {
|
||||||
override fun configureParser(builder: Parser.Builder) {
|
/* Configuring the Markwon block formatting processor.
|
||||||
/* Configuring the Markwon block formatting processor.
|
* Default settings are all Markdown blocks. Turn those off.
|
||||||
* Default settings are all Markdown blocks. Turn those off.
|
*/
|
||||||
*/
|
builder.enabledBlockTypes(kotlin.collections.emptySet())
|
||||||
builder.enabledBlockTypes(kotlin.collections.emptySet())
|
}
|
||||||
}
|
})
|
||||||
})
|
.textSetter(PrecomputedFutureTextSetterCompat.create())
|
||||||
.textSetter(PrecomputedFutureTextSetterCompat.create())
|
.build()
|
||||||
.build()
|
|
||||||
|
|
||||||
val plugins: List<MarkwonPlugin> = markwon.plugins
|
val plugins: List<MarkwonPlugin> = markwon.plugins
|
||||||
|
|
||||||
|
@ -23,5 +23,6 @@ sealed class LocationSharingAction : VectorViewModelAction {
|
|||||||
data class PinnedLocationSharing(val locationData: LocationData?) : LocationSharingAction()
|
data class PinnedLocationSharing(val locationData: LocationData?) : LocationSharingAction()
|
||||||
data class LocationTargetChange(val locationData: LocationData) : LocationSharingAction()
|
data class LocationTargetChange(val locationData: LocationData) : LocationSharingAction()
|
||||||
object ZoomToUserLocation : LocationSharingAction()
|
object ZoomToUserLocation : LocationSharingAction()
|
||||||
|
object LiveLocationSharingRequested : LocationSharingAction()
|
||||||
data class StartLiveLocationSharing(val durationMillis: Long) : LocationSharingAction()
|
data class StartLiveLocationSharing(val durationMillis: Long) : LocationSharingAction()
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,12 @@ import im.vector.app.core.di.ActiveSessionHolder
|
|||||||
import im.vector.app.core.services.VectorAndroidService
|
import im.vector.app.core.services.VectorAndroidService
|
||||||
import im.vector.app.features.location.live.GetLiveLocationShareSummaryUseCase
|
import im.vector.app.features.location.live.GetLiveLocationShareSummaryUseCase
|
||||||
import im.vector.app.features.notifications.NotificationUtils
|
import im.vector.app.features.notifications.NotificationUtils
|
||||||
|
import im.vector.app.features.redaction.CheckIfEventIsRedactedUseCase
|
||||||
import im.vector.app.features.session.coroutineScope
|
import im.vector.app.features.session.coroutineScope
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
@ -55,6 +58,7 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca
|
|||||||
@Inject lateinit var locationTracker: LocationTracker
|
@Inject lateinit var locationTracker: LocationTracker
|
||||||
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
|
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
|
||||||
@Inject lateinit var getLiveLocationShareSummaryUseCase: GetLiveLocationShareSummaryUseCase
|
@Inject lateinit var getLiveLocationShareSummaryUseCase: GetLiveLocationShareSummaryUseCase
|
||||||
|
@Inject lateinit var checkIfEventIsRedactedUseCase: CheckIfEventIsRedactedUseCase
|
||||||
|
|
||||||
private val binder = LocalBinder()
|
private val binder = LocalBinder()
|
||||||
|
|
||||||
@ -66,6 +70,9 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca
|
|||||||
private val jobs = mutableListOf<Job>()
|
private val jobs = mutableListOf<Job>()
|
||||||
private var startInProgress = false
|
private var startInProgress = false
|
||||||
|
|
||||||
|
private val _roomIdsOfActiveLives = MutableSharedFlow<Set<String>>(replay = 1)
|
||||||
|
val roomIdsOfActiveLives = _roomIdsOfActiveLives.asSharedFlow()
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
Timber.i("onCreate")
|
Timber.i("onCreate")
|
||||||
@ -193,24 +200,30 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca
|
|||||||
private fun addRoomArgs(beaconEventId: String, roomArgs: RoomArgs) {
|
private fun addRoomArgs(beaconEventId: String, roomArgs: RoomArgs) {
|
||||||
Timber.i("adding roomArgs for beaconEventId: $beaconEventId")
|
Timber.i("adding roomArgs for beaconEventId: $beaconEventId")
|
||||||
roomArgsMap[beaconEventId] = roomArgs
|
roomArgsMap[beaconEventId] = roomArgs
|
||||||
|
launchWithActiveSession { _roomIdsOfActiveLives.emit(getRoomIdsOfActiveLives()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeRoomArgs(beaconEventId: String) {
|
private fun removeRoomArgs(beaconEventId: String) {
|
||||||
Timber.i("removing roomArgs for beaconEventId: $beaconEventId")
|
Timber.i("removing roomArgs for beaconEventId: $beaconEventId")
|
||||||
roomArgsMap.remove(beaconEventId)
|
roomArgsMap.remove(beaconEventId)
|
||||||
|
launchWithActiveSession { _roomIdsOfActiveLives.emit(getRoomIdsOfActiveLives()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun listenForLiveSummaryChanges(roomId: String, beaconEventId: String) {
|
private fun listenForLiveSummaryChanges(roomId: String, beaconEventId: String) {
|
||||||
launchWithActiveSession { session ->
|
launchWithActiveSession { session ->
|
||||||
val job = getLiveLocationShareSummaryUseCase.execute(roomId, beaconEventId)
|
val job = getLiveLocationShareSummaryUseCase.execute(roomId, beaconEventId)
|
||||||
.distinctUntilChangedBy { it.isActive }
|
.distinctUntilChangedBy { it?.isActive }
|
||||||
.filter { it.isActive == false }
|
.filter { it?.isActive == false || (it == null && isLiveRedacted(roomId, beaconEventId)) }
|
||||||
.onEach { stopSharingLocation(beaconEventId) }
|
.onEach { stopSharingLocation(beaconEventId) }
|
||||||
.launchIn(session.coroutineScope)
|
.launchIn(session.coroutineScope)
|
||||||
jobs.add(job)
|
jobs.add(job)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun isLiveRedacted(roomId: String, beaconEventId: String): Boolean {
|
||||||
|
return checkIfEventIsRedactedUseCase.execute(roomId = roomId, eventId = beaconEventId)
|
||||||
|
}
|
||||||
|
|
||||||
private fun launchWithActiveSession(block: suspend CoroutineScope.(Session) -> Unit) =
|
private fun launchWithActiveSession(block: suspend CoroutineScope.(Session) -> Unit) =
|
||||||
activeSessionHolder
|
activeSessionHolder
|
||||||
.getSafeActiveSession()
|
.getSafeActiveSession()
|
||||||
@ -220,6 +233,10 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getRoomIdsOfActiveLives(): Set<String> {
|
||||||
|
return roomArgsMap.map { it.value.roomId }.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onBind(intent: Intent?): IBinder {
|
override fun onBind(intent: Intent?): IBinder {
|
||||||
return binder
|
return binder
|
||||||
}
|
}
|
||||||
|
@ -104,6 +104,9 @@ class LocationSharingFragment @Inject constructor(
|
|||||||
LocationSharingViewEvents.LocationNotAvailableError -> handleLocationNotAvailableError()
|
LocationSharingViewEvents.LocationNotAvailableError -> handleLocationNotAvailableError()
|
||||||
is LocationSharingViewEvents.ZoomToUserLocation -> handleZoomToUserLocationEvent(it)
|
is LocationSharingViewEvents.ZoomToUserLocation -> handleZoomToUserLocationEvent(it)
|
||||||
is LocationSharingViewEvents.StartLiveLocationService -> handleStartLiveLocationService(it)
|
is LocationSharingViewEvents.StartLiveLocationService -> handleStartLiveLocationService(it)
|
||||||
|
LocationSharingViewEvents.ChooseLiveLocationDuration -> handleChooseLiveLocationDuration()
|
||||||
|
LocationSharingViewEvents.ShowLabsFlagPromotion -> handleShowLabsFlagPromotion()
|
||||||
|
LocationSharingViewEvents.LiveLocationSharingNotEnoughPermission -> handleLiveLocationSharingNotEnoughPermission()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -168,6 +171,14 @@ class LocationSharingFragment @Inject constructor(
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleLiveLocationSharingNotEnoughPermission() {
|
||||||
|
MaterialAlertDialogBuilder(requireActivity())
|
||||||
|
.setTitle(R.string.live_location_not_enough_permission_dialog_title)
|
||||||
|
.setMessage(R.string.live_location_not_enough_permission_dialog_description)
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
private fun initLocateButton() {
|
private fun initLocateButton() {
|
||||||
views.mapView.locateButton.setOnClickListener {
|
views.mapView.locateButton.setOnClickListener {
|
||||||
viewModel.handle(LocationSharingAction.ZoomToUserLocation)
|
viewModel.handle(LocationSharingAction.ZoomToUserLocation)
|
||||||
@ -201,7 +212,7 @@ class LocationSharingFragment @Inject constructor(
|
|||||||
viewModel.handle(LocationSharingAction.CurrentUserLocationSharing)
|
viewModel.handle(LocationSharingAction.CurrentUserLocationSharing)
|
||||||
}
|
}
|
||||||
views.shareLocationOptionsPicker.optionUserLive.debouncedClicks {
|
views.shareLocationOptionsPicker.optionUserLive.debouncedClicks {
|
||||||
tryStartLiveLocationSharing()
|
viewModel.handle(LocationSharingAction.LiveLocationSharingRequested)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,13 +223,13 @@ class LocationSharingFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tryStartLiveLocationSharing() {
|
private fun handleChooseLiveLocationDuration() {
|
||||||
if (vectorPreferences.labsEnableLiveLocation()) {
|
startLiveLocationSharing()
|
||||||
startLiveLocationSharing()
|
}
|
||||||
} else {
|
|
||||||
LiveLocationLabsFlagPromotionBottomSheet.newInstance()
|
private fun handleShowLabsFlagPromotion() {
|
||||||
.show(requireActivity().supportFragmentManager, "DISPLAY_LIVE_LOCATION_LABS_FLAG_PROMOTION")
|
LiveLocationLabsFlagPromotionBottomSheet.newInstance()
|
||||||
}
|
.show(requireActivity().supportFragmentManager, "DISPLAY_LIVE_LOCATION_LABS_FLAG_PROMOTION")
|
||||||
}
|
}
|
||||||
|
|
||||||
private val foregroundLocationResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
private val foregroundLocationResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user