Merge branch 'develop' into feature/bma/jitsi_in_timeline

This commit is contained in:
Benoit Marty 2021-02-08 17:43:49 +01:00 committed by GitHub
commit fade21de57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 248 additions and 122 deletions

View File

@ -1,4 +1,4 @@
Changes in Element 1.X.X (2020-XX-XX) Changes in Element 1.0.17 (2020-XX-XX)
=================================================== ===================================================
Features ✨: Features ✨:
@ -6,11 +6,15 @@ Features ✨:
Improvements 🙌: Improvements 🙌:
- Create a WidgetItemFactory and use it for better rendering of Jitsi widget change (video conference) - Create a WidgetItemFactory and use it for better rendering of Jitsi widget change (video conference)
- Open image from URL Preview (#2705)
Bugfix 🐛: Bugfix 🐛:
- Bug in WidgetContent.computeURL() (#2767) - Bug in WidgetContent.computeURL() (#2767)
- Duplicate thumbs | Mobile reactions for 👍 and 👎 are not the same as web (#2776) - Duplicate thumbs | Mobile reactions for 👍 and 👎 are not the same as web (#2776)
- Join room by alias other federation error (#2778) - Join room by alias other federation error (#2778)
- HTML unescaping for URL preview (#2766)
- URL preview on reply fallback (#2756)
- RTL: some arrows should be rotated in RTL (#2757)
Translations 🗣: Translations 🗣:
- -
@ -25,7 +29,7 @@ Test:
- -
Other changes: Other changes:
- - Change app name from "Element (Riot.im)" to "Element"
Changes in Element 1.0.16 (2020-02-04) Changes in Element 1.0.16 (2020-02-04)
=================================================== ===================================================
@ -53,6 +57,7 @@ Bugfix 🐛:
- Widgets: Support $matrix_widget_id parameter (#2748) - Widgets: Support $matrix_widget_id parameter (#2748)
- Data for Worker overload (#2721) - Data for Worker overload (#2721)
- Fix multiple tasks - Fix multiple tasks
- Object deletion in database is not complete (#2759)
SDK API changes ⚠️: SDK API changes ⚠️:
- Increase targetSdkVersion to 30 (#2600) - Increase targetSdkVersion to 30 (#2600)

View File

@ -20,7 +20,7 @@ object ContentUtils {
val lines = repliedBody.lines() val lines = repliedBody.lines()
var wellFormed = repliedBody.startsWith(">") var wellFormed = repliedBody.startsWith(">")
var endOfPreviousFound = false var endOfPreviousFound = false
val usefullines = ArrayList<String>() val usefulLines = ArrayList<String>()
lines.forEach { lines.forEach {
if (it == "") { if (it == "") {
endOfPreviousFound = true endOfPreviousFound = true
@ -29,10 +29,10 @@ object ContentUtils {
if (!endOfPreviousFound) { if (!endOfPreviousFound) {
wellFormed = wellFormed && it.startsWith(">") wellFormed = wellFormed && it.startsWith(">")
} else { } else {
usefullines.add(it) usefulLines.add(it)
} }
} }
return usefullines.joinToString("\n").takeIf { wellFormed } ?: repliedBody return usefulLines.joinToString("\n").takeIf { wellFormed } ?: repliedBody
} }
fun extractUsefulTextFromHtmlReply(repliedBody: String): String { fun extractUsefulTextFromHtmlReply(repliedBody: String): String {

View File

@ -21,11 +21,11 @@ import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.util.JsonCanonicalizer import org.matrix.android.sdk.internal.util.JsonCanonicalizer
import timber.log.Timber import timber.log.Timber
fun CryptoDeviceInfo.canonicalSignable(): String { internal fun CryptoDeviceInfo.canonicalSignable(): String {
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary()) return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
} }
fun CryptoCrossSigningKey.canonicalSignable(): String { internal fun CryptoCrossSigningKey.canonicalSignable(): String {
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary()) return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
} }
@ -40,7 +40,7 @@ fun String.fromBase64(): ByteArray {
/** /**
* Decode the base 64. Return null in case of bad format. Should be used when parsing received data from external source * Decode the base 64. Return null in case of bad format. Should be used when parsing received data from external source
*/ */
fun String.fromBase64Safe(): ByteArray? { internal fun String.fromBase64Safe(): ByteArray? {
return try { return try {
Base64.decode(this, Base64.DEFAULT) Base64.decode(this, Base64.DEFAULT)
} catch (throwable: Throwable) { } catch (throwable: Throwable) {

View File

@ -63,7 +63,7 @@ data class CryptoCrossSigningKey(
) )
} }
data class Builder( internal data class Builder(
val userId: String, val userId: String,
val usage: KeyUsage, val usage: KeyUsage,
private var base64Pkey: String? = null, private var base64Pkey: String? = null,
@ -97,7 +97,7 @@ data class CryptoCrossSigningKey(
} }
} }
enum class KeyUsage(val value: String) { internal enum class KeyUsage(val value: String) {
MASTER("master"), MASTER("master"),
SELF_SIGNING("self_signing"), SELF_SIGNING("self_signing"),
USER_SIGNING("user_signing") USER_SIGNING("user_signing")

View File

@ -83,6 +83,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntity import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.createPrimaryKey import org.matrix.android.sdk.internal.crypto.store.db.model.createPrimaryKey
import org.matrix.android.sdk.internal.crypto.store.db.model.deleteOnCascade
import org.matrix.android.sdk.internal.crypto.store.db.query.create import org.matrix.android.sdk.internal.crypto.store.db.query.create
import org.matrix.android.sdk.internal.crypto.store.db.query.delete import org.matrix.android.sdk.internal.crypto.store.db.query.delete
import org.matrix.android.sdk.internal.crypto.store.db.query.get import org.matrix.android.sdk.internal.crypto.store.db.query.get
@ -94,6 +95,7 @@ import org.matrix.android.sdk.internal.di.CryptoDatabase
import org.matrix.android.sdk.internal.di.DeviceId import org.matrix.android.sdk.internal.di.DeviceId
import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.extensions.clearWith
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.olm.OlmAccount import org.matrix.olm.OlmAccount
import org.matrix.olm.OlmException import org.matrix.olm.OlmException
@ -293,7 +295,7 @@ internal class RealmCryptoStore @Inject constructor(
realm.insertOrUpdate(entity) realm.insertOrUpdate(entity)
} }
// Ensure all other devices are deleted // Ensure all other devices are deleted
u.devices.deleteAllFromRealm() u.devices.clearWith { it.deleteOnCascade() }
u.devices.addAll(new) u.devices.addAll(new)
} }
} }
@ -309,7 +311,7 @@ internal class RealmCryptoStore @Inject constructor(
.let { userEntity -> .let { userEntity ->
if (masterKey == null || selfSigningKey == null) { if (masterKey == null || selfSigningKey == null) {
// The user has disabled cross signing? // The user has disabled cross signing?
userEntity.crossSigningInfoEntity?.deleteFromRealm() userEntity.crossSigningInfoEntity?.deleteOnCascade()
userEntity.crossSigningInfoEntity = null userEntity.crossSigningInfoEntity = null
} else { } else {
var shouldResetMyDevicesLocalTrust = false var shouldResetMyDevicesLocalTrust = false
@ -1633,7 +1635,7 @@ internal class RealmCryptoStore @Inject constructor(
} else { } else {
// Just override existing, caller should check and untrust id needed // Just override existing, caller should check and untrust id needed
val existing = CrossSigningInfoEntity.getOrCreate(realm, userId) val existing = CrossSigningInfoEntity.getOrCreate(realm, userId)
existing.crossSigningKeys.deleteAllFromRealm() existing.crossSigningKeys.clearWith { it.deleteOnCascade() }
existing.crossSigningKeys.addAll( existing.crossSigningKeys.addAll(
info.crossSigningKeys.map { info.crossSigningKeys.map {
crossSigningKeysMapper.map(it) crossSigningKeysMapper.map(it)

View File

@ -16,10 +16,11 @@
package org.matrix.android.sdk.internal.crypto.store.db.model package org.matrix.android.sdk.internal.crypto.store.db.model
import org.matrix.android.sdk.internal.crypto.model.KeyUsage
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import org.matrix.android.sdk.internal.crypto.model.KeyUsage
import org.matrix.android.sdk.internal.extensions.clearWith
internal open class CrossSigningInfoEntity( internal open class CrossSigningInfoEntity(
@PrimaryKey @PrimaryKey
@ -56,3 +57,8 @@ internal open class CrossSigningInfoEntity(
info?.let { crossSigningKeys.add(it) } info?.let { crossSigningKeys.add(it) }
} }
} }
internal fun CrossSigningInfoEntity.deleteOnCascade() {
crossSigningKeys.clearWith { it.deleteOnCascade() }
deleteFromRealm()
}

View File

@ -47,3 +47,8 @@ internal open class DeviceInfoEntity(
companion object companion object
} }
internal fun DeviceInfoEntity.deleteOnCascade() {
trustLevelEntity?.deleteFromRealm()
deleteFromRealm()
}

View File

@ -30,3 +30,8 @@ internal open class KeyInfoEntity(
var signatures: String? = null, var signatures: String? = null,
var trustLevelEntity: TrustLevelEntity? = null var trustLevelEntity: TrustLevelEntity? = null
) : RealmObject() ) : RealmObject()
internal fun KeyInfoEntity.deleteOnCascade() {
trustLevelEntity?.deleteFromRealm()
deleteFromRealm()
}

View File

@ -19,13 +19,20 @@ package org.matrix.android.sdk.internal.crypto.store.db.model
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import org.matrix.android.sdk.internal.extensions.clearWith
internal open class UserEntity( internal open class UserEntity(
@PrimaryKey var userId: String? = null, @PrimaryKey var userId: String? = null,
var devices: RealmList<DeviceInfoEntity> = RealmList(), var devices: RealmList<DeviceInfoEntity> = RealmList(),
var crossSigningInfoEntity: CrossSigningInfoEntity? = null, var crossSigningInfoEntity: CrossSigningInfoEntity? = null,
var deviceTrackingStatus: Int = 0) var deviceTrackingStatus: Int = 0
: RealmObject() { ) : RealmObject() {
companion object companion object
} }
internal fun UserEntity.deleteOnCascade() {
devices.clearWith { it.deleteOnCascade() }
crossSigningInfoEntity?.deleteOnCascade()
deleteFromRealm()
}

View File

@ -16,11 +16,12 @@
package org.matrix.android.sdk.internal.crypto.store.db.query package org.matrix.android.sdk.internal.crypto.store.db.query
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
import io.realm.Realm import io.realm.Realm
import io.realm.kotlin.createObject import io.realm.kotlin.createObject
import io.realm.kotlin.where import io.realm.kotlin.where
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.deleteOnCascade
/** /**
* Get or create a user * Get or create a user
@ -39,5 +40,5 @@ internal fun UserEntity.Companion.delete(realm: Realm, userId: String) {
realm.where<UserEntity>() realm.where<UserEntity>()
.equalTo(UserEntityFields.USER_ID, userId) .equalTo(UserEntityFields.USER_ID, userId)
.findFirst() .findFirst()
?.deleteFromRealm() ?.deleteOnCascade()
} }

View File

@ -21,7 +21,7 @@ import org.matrix.olm.OlmPkEncryption
import org.matrix.olm.OlmPkSigning import org.matrix.olm.OlmPkSigning
import org.matrix.olm.OlmUtility import org.matrix.olm.OlmUtility
fun <T> withOlmEncryption(block: (OlmPkEncryption) -> T): T { internal fun <T> withOlmEncryption(block: (OlmPkEncryption) -> T): T {
val olmPkEncryption = OlmPkEncryption() val olmPkEncryption = OlmPkEncryption()
try { try {
return block(olmPkEncryption) return block(olmPkEncryption)
@ -30,7 +30,7 @@ fun <T> withOlmEncryption(block: (OlmPkEncryption) -> T): T {
} }
} }
fun <T> withOlmDecryption(block: (OlmPkDecryption) -> T): T { internal fun <T> withOlmDecryption(block: (OlmPkDecryption) -> T): T {
val olmPkDecryption = OlmPkDecryption() val olmPkDecryption = OlmPkDecryption()
try { try {
return block(olmPkDecryption) return block(olmPkDecryption)
@ -39,7 +39,7 @@ fun <T> withOlmDecryption(block: (OlmPkDecryption) -> T): T {
} }
} }
fun <T> withOlmSigning(block: (OlmPkSigning) -> T): T { internal fun <T> withOlmSigning(block: (OlmPkSigning) -> T): T {
val olmPkSigning = OlmPkSigning() val olmPkSigning = OlmPkSigning()
try { try {
return block(olmPkSigning) return block(olmPkSigning)
@ -48,7 +48,7 @@ fun <T> withOlmSigning(block: (OlmPkSigning) -> T): T {
} }
} }
fun <T> withOlmUtility(block: (OlmUtility) -> T): T { internal fun <T> withOlmUtility(block: (OlmUtility) -> T): T {
val olmUtility = OlmUtility() val olmUtility = OlmUtility()
try { try {
return block(olmUtility) return block(olmUtility)

View File

@ -16,6 +16,10 @@
package org.matrix.android.sdk.internal.database package org.matrix.android.sdk.internal.database
import io.realm.Realm
import io.realm.RealmConfiguration
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.matrix.android.sdk.internal.database.helper.nextDisplayIndex import org.matrix.android.sdk.internal.database.helper.nextDisplayIndex
import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity
import org.matrix.android.sdk.internal.database.model.ChunkEntityFields import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
@ -23,14 +27,11 @@ import org.matrix.android.sdk.internal.database.model.EventEntity
import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
import org.matrix.android.sdk.internal.database.model.deleteOnCascade
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.SessionLifecycleObserver import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.TaskExecutor
import io.realm.Realm
import io.realm.RealmConfiguration
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -56,7 +57,7 @@ internal class DatabaseCleaner @Inject constructor(@SessionDatabase private val
} }
} }
private suspend fun cleanUp(realm: Realm, threshold: Long) { private fun cleanUp(realm: Realm, threshold: Long) {
val numberOfEvents = realm.where(EventEntity::class.java).findAll().size val numberOfEvents = realm.where(EventEntity::class.java).findAll().size
val numberOfTimelineEvents = realm.where(TimelineEventEntity::class.java).findAll().size val numberOfTimelineEvents = realm.where(TimelineEventEntity::class.java).findAll().size
Timber.v("Number of events in db: $numberOfEvents | Number of timeline events in db: $numberOfTimelineEvents") Timber.v("Number of events in db: $numberOfEvents | Number of timeline events in db: $numberOfTimelineEvents")
@ -76,20 +77,7 @@ internal class DatabaseCleaner @Inject constructor(@SessionDatabase private val
chunk.numberOfTimelineEvents = chunk.numberOfTimelineEvents - eventsToRemove.size chunk.numberOfTimelineEvents = chunk.numberOfTimelineEvents - eventsToRemove.size
eventsToRemove.forEach { eventsToRemove.forEach {
val canDeleteRoot = it.root?.stateKey == null val canDeleteRoot = it.root?.stateKey == null
if (canDeleteRoot) { it.deleteOnCascade(canDeleteRoot)
it.root?.deleteFromRealm()
}
it.readReceipts?.readReceipts?.deleteAllFromRealm()
it.readReceipts?.deleteFromRealm()
it.annotations?.apply {
editSummary?.deleteFromRealm()
pollResponseSummary?.deleteFromRealm()
referencesSummaryEntity?.deleteFromRealm()
reactionsSummary.deleteAllFromRealm()
}
it.annotations?.deleteFromRealm()
it.readReceipts?.deleteFromRealm()
it.deleteFromRealm()
} }
// We reset the prevToken so we will need to fetch again. // We reset the prevToken so we will need to fetch again.
chunk.prevToken = null chunk.prevToken = null

View File

@ -38,12 +38,6 @@ import io.realm.Sort
import io.realm.kotlin.createObject import io.realm.kotlin.createObject
import timber.log.Timber import timber.log.Timber
internal fun ChunkEntity.deleteOnCascade() {
assertIsManaged()
this.timelineEvents.deleteAllFromRealm()
this.deleteFromRealm()
}
internal fun ChunkEntity.merge(roomId: String, chunkToMerge: ChunkEntity, direction: PaginationDirection) { internal fun ChunkEntity.merge(roomId: String, chunkToMerge: ChunkEntity, direction: PaginationDirection) {
assertIsManaged() assertIsManaged()
val localRealm = this.realm val localRealm = this.realm

View File

@ -19,12 +19,7 @@ package org.matrix.android.sdk.internal.database.helper
import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity
import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomEntity
internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) { internal fun RoomEntity.addIfNecessary(chunkEntity: ChunkEntity) {
chunks.remove(chunkEntity)
chunkEntity.deleteOnCascade()
}
internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) {
if (!chunks.contains(chunkEntity)) { if (!chunks.contains(chunkEntity)) {
chunks.add(chunkEntity) chunks.add(chunkEntity)
} }

View File

@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.database.helper
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
import org.matrix.android.sdk.internal.extensions.assertIsManaged
import io.realm.Realm import io.realm.Realm
internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long { internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long {
@ -29,11 +28,3 @@ internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long {
currentIdNum.toLong() + 1 currentIdNum.toLong() + 1
} }
} }
internal fun TimelineEventEntity.deleteOnCascade() {
assertIsManaged()
root?.deleteFromRealm()
annotations?.deleteFromRealm()
readReceipts?.deleteFromRealm()
deleteFromRealm()
}

View File

@ -21,6 +21,8 @@ import io.realm.RealmObject
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.annotations.Index import io.realm.annotations.Index
import io.realm.annotations.LinkingObjects import io.realm.annotations.LinkingObjects
import org.matrix.android.sdk.internal.extensions.assertIsManaged
import org.matrix.android.sdk.internal.extensions.clearWith
internal open class ChunkEntity(@Index var prevToken: String? = null, internal open class ChunkEntity(@Index var prevToken: String? = null,
// Because of gaps we can have several chunks with nextToken == null // Because of gaps we can have several chunks with nextToken == null
@ -43,3 +45,12 @@ internal open class ChunkEntity(@Index var prevToken: String? = null,
companion object companion object
} }
internal fun ChunkEntity.deleteOnCascade(deleteStateEvents: Boolean, canDeleteRoot: Boolean) {
assertIsManaged()
if (deleteStateEvents) {
stateEvents.deleteAllFromRealm()
}
timelineEvents.clearWith { it.deleteOnCascade(canDeleteRoot) }
deleteFromRealm()
}

View File

@ -31,3 +31,11 @@ internal open class EventAnnotationsSummaryEntity(
companion object companion object
} }
internal fun EventAnnotationsSummaryEntity.deleteOnCascade() {
reactionsSummary.deleteAllFromRealm()
editSummary?.deleteFromRealm()
referencesSummaryEntity?.deleteFromRealm()
pollResponseSummary?.deleteFromRealm()
deleteFromRealm()
}

View File

@ -40,3 +40,8 @@ internal open class PushRuleEntity(
companion object companion object
} }
internal fun PushRuleEntity.deleteOnCascade() {
conditions?.deleteAllFromRealm()
deleteFromRealm()
}

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.model
import org.matrix.android.sdk.api.pushrules.RuleKind import org.matrix.android.sdk.api.pushrules.RuleKind
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import org.matrix.android.sdk.internal.extensions.clearWith
internal open class PushRulesEntity( internal open class PushRulesEntity(
var scope: String = "", var scope: String = "",
@ -35,3 +36,8 @@ internal open class PushRulesEntity(
companion object companion object
} }
internal fun PushRulesEntity.deleteOnCascade() {
pushRules.clearWith { it.deleteOnCascade() }
deleteFromRealm()
}

View File

@ -15,8 +15,8 @@
*/ */
package org.matrix.android.sdk.internal.database.model package org.matrix.android.sdk.internal.database.model
import org.matrix.android.sdk.api.session.pushers.PusherState
import io.realm.RealmObject import io.realm.RealmObject
import org.matrix.android.sdk.api.session.pushers.PusherState
// TODO // TODO
// at java.lang.Thread.run(Thread.java:764) // at java.lang.Thread.run(Thread.java:764)
@ -54,3 +54,8 @@ internal open class PusherEntity(
companion object companion object
} }
internal fun PusherEntity.deleteOnCascade() {
data?.deleteFromRealm()
deleteFromRealm()
}

View File

@ -34,3 +34,8 @@ internal open class ReadReceiptsSummaryEntity(
companion object companion object
} }
internal fun ReadReceiptsSummaryEntity.deleteOnCascade() {
readReceipts.deleteAllFromRealm()
deleteFromRealm()
}

View File

@ -20,6 +20,7 @@ import io.realm.RealmObject
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.annotations.Index import io.realm.annotations.Index
import io.realm.annotations.LinkingObjects import io.realm.annotations.LinkingObjects
import org.matrix.android.sdk.internal.extensions.assertIsManaged
internal open class TimelineEventEntity(var localId: Long = 0, internal open class TimelineEventEntity(var localId: Long = 0,
@Index var eventId: String = "", @Index var eventId: String = "",
@ -39,3 +40,13 @@ internal open class TimelineEventEntity(var localId: Long = 0,
companion object companion object
} }
internal fun TimelineEventEntity.deleteOnCascade(canDeleteRoot: Boolean) {
assertIsManaged()
if (canDeleteRoot) {
root?.deleteFromRealm()
}
annotations?.deleteOnCascade()
readReceipts?.deleteOnCascade()
deleteFromRealm()
}

View File

@ -16,8 +16,18 @@
package org.matrix.android.sdk.internal.extensions package org.matrix.android.sdk.internal.extensions
import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
internal fun RealmObject.assertIsManaged() { internal fun RealmObject.assertIsManaged() {
check(isManaged) { "${javaClass.simpleName} entity should be managed to use this function" } check(isManaged) { "${javaClass.simpleName} entity should be managed to use this function" }
} }
/**
* Clear a RealmList by deleting all its items calling the provided lambda
*/
internal fun <T> RealmList<T>.clearWith(delete: (T) -> Unit) {
while (!isEmpty()) {
first()?.let { delete.invoke(it) }
}
}

View File

@ -28,6 +28,7 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.task.Task
import org.matrix.android.sdk.internal.util.awaitTransaction import org.matrix.android.sdk.internal.util.awaitTransaction
import org.matrix.android.sdk.internal.util.unescapeHtml
import java.util.Date import java.util.Date
import javax.inject.Inject import javax.inject.Inject
@ -73,9 +74,9 @@ internal class DefaultGetPreviewUrlTask @Inject constructor(
private fun JsonDict.toPreviewUrlData(url: String): PreviewUrlData { private fun JsonDict.toPreviewUrlData(url: String): PreviewUrlData {
return PreviewUrlData( return PreviewUrlData(
url = (get("og:url") as? String) ?: url, url = (get("og:url") as? String) ?: url,
siteName = get("og:site_name") as? String, siteName = (get("og:site_name") as? String)?.unescapeHtml(),
title = get("og:title") as? String, title = (get("og:title") as? String)?.unescapeHtml(),
description = get("og:description") as? String, description = (get("og:description") as? String)?.unescapeHtml(),
mxcUrl = get("og:image") as? String mxcUrl = get("og:image") as? String
) )
} }

View File

@ -21,6 +21,8 @@ import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.MessageType
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.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
import org.matrix.android.sdk.api.session.room.timeline.isReply
import org.matrix.android.sdk.api.util.ContentUtils
import javax.inject.Inject import javax.inject.Inject
internal class UrlsExtractor @Inject constructor() { internal class UrlsExtractor @Inject constructor() {
@ -35,7 +37,14 @@ internal class UrlsExtractor @Inject constructor() {
|| it.msgType == MessageType.MSGTYPE_NOTICE || it.msgType == MessageType.MSGTYPE_NOTICE
|| it.msgType == MessageType.MSGTYPE_EMOTE || it.msgType == MessageType.MSGTYPE_EMOTE
} }
?.body ?.let { messageContent ->
if (event.isReply()) {
// This is a reply, strip the reply fallback
ContentUtils.extractUsefulTextFromReply(messageContent.body)
} else {
messageContent.body
}
}
?.let { urlRegex.findAll(it) } ?.let { urlRegex.findAll(it) }
?.map { it.value } ?.map { it.value }
?.filter { it.startsWith("https://") || it.startsWith("http://") } ?.filter { it.startsWith("https://") || it.startsWith("http://") }

View File

@ -19,6 +19,7 @@ import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.session.pushers.PusherState import org.matrix.android.sdk.api.session.pushers.PusherState
import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.mapper.toEntity
import org.matrix.android.sdk.internal.database.model.PusherEntity import org.matrix.android.sdk.internal.database.model.PusherEntity
import org.matrix.android.sdk.internal.database.model.deleteOnCascade
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
@ -41,7 +42,8 @@ internal class DefaultGetPushersTask @Inject constructor(
monarchy.awaitTransaction { realm -> monarchy.awaitTransaction { realm ->
// clear existings? // clear existings?
realm.where(PusherEntity::class.java) realm.where(PusherEntity::class.java)
.findAll().deleteAllFromRealm() .findAll()
.forEach { it.deleteOnCascade() }
response.pushers?.forEach { jsonPusher -> response.pushers?.forEach { jsonPusher ->
jsonPusher.toEntity().also { jsonPusher.toEntity().also {
it.state = PusherState.REGISTERED it.state = PusherState.REGISTERED

View File

@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.pushrules.RuleSetKey
import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse
import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper
import org.matrix.android.sdk.internal.database.model.PushRulesEntity import org.matrix.android.sdk.internal.database.model.PushRulesEntity
import org.matrix.android.sdk.internal.database.model.deleteOnCascade
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.task.Task
import org.matrix.android.sdk.internal.util.awaitTransaction import org.matrix.android.sdk.internal.util.awaitTransaction
@ -40,7 +41,7 @@ internal class DefaultSavePushRulesTask @Inject constructor(@SessionDatabase pri
// clear current push rules // clear current push rules
realm.where(PushRulesEntity::class.java) realm.where(PushRulesEntity::class.java)
.findAll() .findAll()
.deleteAllFromRealm() .forEach { it.deleteOnCascade() }
// Save only global rules for the moment // Save only global rules for the moment
val globalRules = params.pushRules.global val globalRules = params.pushRules.global

View File

@ -22,16 +22,16 @@ import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.database.helper.addOrUpdate import org.matrix.android.sdk.internal.database.helper.addIfNecessary
import org.matrix.android.sdk.internal.database.helper.addStateEvent import org.matrix.android.sdk.internal.database.helper.addStateEvent
import org.matrix.android.sdk.internal.database.helper.addTimelineEvent import org.matrix.android.sdk.internal.database.helper.addTimelineEvent
import org.matrix.android.sdk.internal.database.helper.deleteOnCascade
import org.matrix.android.sdk.internal.database.helper.merge import org.matrix.android.sdk.internal.database.helper.merge
import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.mapper.toEntity
import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity
import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.EventInsertType
import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomEntity
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.deleteOnCascade
import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
import org.matrix.android.sdk.internal.database.query.create import org.matrix.android.sdk.internal.database.query.create
import org.matrix.android.sdk.internal.database.query.find import org.matrix.android.sdk.internal.database.query.find
@ -172,7 +172,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri
val currentLastForwardChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) val currentLastForwardChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)
if (currentChunk != currentLastForwardChunk) { if (currentChunk != currentLastForwardChunk) {
currentChunk.isLastForward = true currentChunk.isLastForward = true
currentLastForwardChunk?.deleteOnCascade() currentLastForwardChunk?.deleteOnCascade(deleteStateEvents = false, canDeleteRoot = false)
RoomSummaryEntity.where(realm, roomId).findFirst()?.apply { RoomSummaryEntity.where(realm, roomId).findFirst()?.apply {
latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
} }
@ -235,7 +235,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri
} }
} }
chunksToDelete.forEach { chunksToDelete.forEach {
it.deleteOnCascade() it.deleteOnCascade(deleteStateEvents = false, canDeleteRoot = false)
} }
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId) val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
val shouldUpdateSummary = roomSummaryEntity.latestPreviewableEvent == null val shouldUpdateSummary = roomSummaryEntity.latestPreviewableEvent == null
@ -244,7 +244,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri
roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
} }
if (currentChunk.isValid) { if (currentChunk.isValid) {
RoomEntity.where(realm, roomId).findFirst()?.addOrUpdate(currentChunk) RoomEntity.where(realm, roomId).findFirst()?.addIfNecessary(currentChunk)
} }
} }
} }

View File

@ -30,9 +30,8 @@ import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
import org.matrix.android.sdk.internal.database.helper.addOrUpdate import org.matrix.android.sdk.internal.database.helper.addIfNecessary
import org.matrix.android.sdk.internal.database.helper.addTimelineEvent import org.matrix.android.sdk.internal.database.helper.addTimelineEvent
import org.matrix.android.sdk.internal.database.helper.deleteOnCascade
import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.mapper.toEntity
import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity
@ -40,6 +39,7 @@ import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.EventInsertType
import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomEntity
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
import org.matrix.android.sdk.internal.database.model.deleteOnCascade
import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
import org.matrix.android.sdk.internal.database.query.find import org.matrix.android.sdk.internal.database.query.find
import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom
@ -48,6 +48,7 @@ import org.matrix.android.sdk.internal.database.query.getOrNull
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.extensions.clearWith
import org.matrix.android.sdk.internal.session.DefaultInitialSyncProgressService import org.matrix.android.sdk.internal.session.DefaultInitialSyncProgressService
import org.matrix.android.sdk.internal.session.mapWithProgress import org.matrix.android.sdk.internal.session.mapWithProgress
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
@ -175,7 +176,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
syncLocalTimestampMillis, syncLocalTimestampMillis,
isInitialSync isInitialSync
) )
roomEntity.addOrUpdate(chunkEntity) roomEntity.addIfNecessary(chunkEntity)
} }
val hasRoomMember = roomSync.state?.events?.firstOrNull { val hasRoomMember = roomSync.state?.events?.firstOrNull {
it.type == EventType.STATE_ROOM_MEMBER it.type == EventType.STATE_ROOM_MEMBER
@ -263,7 +264,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
val leftMember = RoomMemberSummaryEntity.where(realm, roomId, userId).findFirst() val leftMember = RoomMemberSummaryEntity.where(realm, roomId, userId).findFirst()
val membership = leftMember?.membership ?: Membership.LEAVE val membership = leftMember?.membership ?: Membership.LEAVE
roomEntity.membership = membership roomEntity.membership = membership
roomEntity.chunks.deleteAllFromRealm() roomEntity.chunks.clearWith { it.deleteOnCascade(deleteStateEvents = true, canDeleteRoot = true) }
roomTypingUsersHandler.handle(realm, roomId, null) roomTypingUsersHandler.handle(realm, roomId, null)
roomChangeMembershipStateDataSource.setMembershipFromSync(roomId, Membership.LEAVE) roomChangeMembershipStateDataSource.setMembershipFromSync(roomId, Membership.LEAVE)
roomSummaryUpdater.update(realm, roomId, membership, roomSync.summary, roomSync.unreadNotifications) roomSummaryUpdater.update(realm, roomId, membership, roomSync.summary, roomSync.unreadNotifications)
@ -340,7 +341,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
} }
} }
// Finally delete the local echo // Finally delete the local echo
sendingEventEntity.deleteOnCascade() sendingEventEntity.deleteOnCascade(true)
} else { } else {
Timber.v("Can't find corresponding local echo for tx:$it") Timber.v("Can't find corresponding local echo for tx:$it")
} }

View File

@ -16,8 +16,10 @@
package org.matrix.android.sdk.internal.session.sync package org.matrix.android.sdk.internal.session.sync
import com.squareup.moshi.Moshi
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import io.realm.RealmList
import io.realm.kotlin.where
import org.matrix.android.sdk.api.pushrules.RuleScope import org.matrix.android.sdk.api.pushrules.RuleScope
import org.matrix.android.sdk.api.pushrules.RuleSetKey import org.matrix.android.sdk.api.pushrules.RuleSetKey
import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse
@ -37,6 +39,7 @@ import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.UserAccountDataEntity import org.matrix.android.sdk.internal.database.model.UserAccountDataEntity
import org.matrix.android.sdk.internal.database.model.UserAccountDataEntityFields import org.matrix.android.sdk.internal.database.model.UserAccountDataEntityFields
import org.matrix.android.sdk.internal.database.model.deleteOnCascade
import org.matrix.android.sdk.internal.database.query.getDirectRooms import org.matrix.android.sdk.internal.database.query.getDirectRooms
import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
@ -50,9 +53,6 @@ import org.matrix.android.sdk.internal.session.sync.model.accountdata.IgnoredUse
import org.matrix.android.sdk.internal.session.sync.model.accountdata.UserAccountDataSync import org.matrix.android.sdk.internal.session.sync.model.accountdata.UserAccountDataSync
import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
import io.realm.Realm
import io.realm.RealmList
import io.realm.kotlin.where
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -60,7 +60,6 @@ internal class UserAccountDataSyncHandler @Inject constructor(
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val monarchy: Monarchy,
@UserId private val userId: String, @UserId private val userId: String,
private val directChatsHelper: DirectChatsHelper, private val directChatsHelper: DirectChatsHelper,
private val moshi: Moshi,
private val updateUserAccountDataTask: UpdateUserAccountDataTask) { private val updateUserAccountDataTask: UpdateUserAccountDataTask) {
fun handle(realm: Realm, accountData: UserAccountDataSync?) { fun handle(realm: Realm, accountData: UserAccountDataSync?) {
@ -113,7 +112,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
val pushRules = event.content.toModel<GetPushRulesResponse>() ?: return val pushRules = event.content.toModel<GetPushRulesResponse>() ?: return
realm.where(PushRulesEntity::class.java) realm.where(PushRulesEntity::class.java)
.findAll() .findAll()
.deleteAllFromRealm() .forEach { it.deleteOnCascade() }
// Save only global rules for the moment // Save only global rules for the moment
val globalRules = pushRules.global val globalRules = pushRules.global

View File

@ -0,0 +1,23 @@
/*
* Copyright 2021 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.util
import androidx.core.text.HtmlCompat
internal fun String.unescapeHtml(): String {
return HtmlCompat.fromHtml(this, HtmlCompat.FROM_HTML_MODE_LEGACY).toString()
}

View File

@ -210,7 +210,7 @@ android {
} }
release { release {
resValue "string", "app_name", "Element (Riot.im)" resValue "string", "app_name", "Element"
resValue "bool", "debug_mode", "false" resValue "bool", "debug_mode", "false"
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false" buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"

View File

@ -1687,6 +1687,10 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.DoNotShowPreviewUrlFor(eventId, url)) roomDetailViewModel.handle(RoomDetailAction.DoNotShowPreviewUrlFor(eventId, url))
} }
override fun onPreviewUrlImageClicked(sharedView: View?, mxcUrl: String?, title: String?) {
navigator.openBigImageViewer(requireActivity(), sharedView, mxcUrl, title)
}
private fun onShareActionClicked(action: EventSharedAction.Share) { private fun onShareActionClicked(action: EventSharedAction.Share) {
if (action.messageContent is MessageTextContent) { if (action.messageContent is MessageTextContent) {
shareText(requireContext(), action.messageContent.body) shareText(requireContext(), action.messageContent.body)

View File

@ -87,12 +87,12 @@ import org.matrix.android.sdk.api.session.room.read.ReadService
import org.matrix.android.sdk.api.session.room.send.UserDraft import org.matrix.android.sdk.api.session.room.send.UserDraft
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.getRelationContent
import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent
import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.Widget
import org.matrix.android.sdk.api.session.widgets.model.WidgetType import org.matrix.android.sdk.api.session.widgets.model.WidgetType
import org.matrix.android.sdk.api.util.appendParamToUrl import org.matrix.android.sdk.api.util.appendParamToUrl
import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.internal.util.awaitCallback
import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.rx
@ -754,8 +754,7 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
is SendMode.EDIT -> { is SendMode.EDIT -> {
// is original event a reply? // is original event a reply?
val inReplyTo = state.sendMode.timelineEvent.root.getClearContent().toModel<MessageContent>()?.relatesTo?.inReplyTo?.eventId val inReplyTo = state.sendMode.timelineEvent.getRelationContent()?.inReplyTo?.eventId
?: state.sendMode.timelineEvent.root.content.toModel<EncryptedEventContent>()?.relatesTo?.inReplyTo?.eventId
if (inReplyTo != null) { if (inReplyTo != null) {
// TODO check if same content? // TODO check if same content?
room.getTimeLineEvent(inReplyTo)?.let { room.getTimeLineEvent(inReplyTo)?.let {

View File

@ -130,6 +130,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
interface PreviewUrlCallback { interface PreviewUrlCallback {
fun onPreviewUrlClicked(url: String) fun onPreviewUrlClicked(url: String)
fun onPreviewUrlCloseClicked(eventId: String, url: String) fun onPreviewUrlCloseClicked(eventId: String, url: String)
fun onPreviewUrlImageClicked(sharedView: View?, mxcUrl: String?, title: String?)
} }
// Map eventId to adapter position // Map eventId to adapter position

View File

@ -23,7 +23,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.databinding.UrlPreviewBinding import im.vector.app.databinding.ViewUrlPreviewBinding
import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.ImageContentRenderer
@ -39,7 +39,7 @@ class PreviewUrlView @JvmOverloads constructor(
defStyleAttr: Int = 0 defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr), View.OnClickListener { ) : ConstraintLayout(context, attrs, defStyleAttr), View.OnClickListener {
private lateinit var views: UrlPreviewBinding private lateinit var views: ViewUrlPreviewBinding
var delegate: TimelineEventController.PreviewUrlCallback? = null var delegate: TimelineEventController.PreviewUrlCallback? = null
@ -80,6 +80,19 @@ class PreviewUrlView @JvmOverloads constructor(
} }
} }
private fun onImageClick() {
when (val finalState = state) {
is PreviewUrlUiState.Data -> {
delegate?.onPreviewUrlImageClicked(
sharedView = views.urlPreviewImage,
mxcUrl = finalState.previewUrlData.mxcUrl,
title = finalState.previewUrlData.title
)
}
else -> Unit
}
}
private fun onCloseClick() { private fun onCloseClick() {
when (val finalState = state) { when (val finalState = state) {
is PreviewUrlUiState.Data -> delegate?.onPreviewUrlCloseClicked(finalState.eventId, finalState.url) is PreviewUrlUiState.Data -> delegate?.onPreviewUrlCloseClicked(finalState.eventId, finalState.url)
@ -90,10 +103,11 @@ class PreviewUrlView @JvmOverloads constructor(
// PRIVATE METHODS **************************************************************************************************************************************** // PRIVATE METHODS ****************************************************************************************************************************************
private fun setupView() { private fun setupView() {
inflate(context, R.layout.url_preview, this) inflate(context, R.layout.view_url_preview, this)
views = UrlPreviewBinding.bind(this) views = ViewUrlPreviewBinding.bind(this)
setOnClickListener(this) setOnClickListener(this)
views.urlPreviewImage.setOnClickListener { onImageClick() }
views.urlPreviewClose.setOnClickListener { onCloseClick() } views.urlPreviewClose.setOnClickListener { onCloseClick() }
} }

View File

@ -73,7 +73,6 @@ import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryDat
import org.matrix.android.sdk.api.session.terms.TermsService import org.matrix.android.sdk.api.session.terms.TermsService
import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.Widget
import org.matrix.android.sdk.api.session.widgets.model.WidgetType import org.matrix.android.sdk.api.session.widgets.model.WidgetType
import org.matrix.android.sdk.api.util.MatrixItem
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -256,14 +255,13 @@ class DefaultNavigator @Inject constructor(
context.startActivity(RoomProfileActivity.newIntent(context, roomId, directAccess)) context.startActivity(RoomProfileActivity.newIntent(context, roomId, directAccess))
} }
override fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem) { override fun openBigImageViewer(activity: Activity, sharedElement: View?, mxcUrl: String?, title: String?) {
matrixItem.avatarUrl mxcUrl
?.takeIf { it.isNotBlank() } ?.takeIf { it.isNotBlank() }
?.let { avatarUrl -> ?.let { avatarUrl ->
val intent = BigImageViewerActivity.newIntent(activity, matrixItem.getBestName(), avatarUrl) val intent = BigImageViewerActivity.newIntent(activity, title, avatarUrl)
val options = sharedElement?.let { val options = sharedElement?.let {
ActivityOptionsCompat.makeSceneTransitionAnimation(activity, it, ViewCompat.getTransitionName(it) ActivityOptionsCompat.makeSceneTransitionAnimation(activity, it, ViewCompat.getTransitionName(it) ?: "")
?: "")
} }
activity.startActivity(intent, options?.toBundle()) activity.startActivity(intent, options?.toBundle())
} }

View File

@ -80,7 +80,11 @@ interface Navigator {
fun openRoomProfile(context: Context, roomId: String, directAccess: Int? = null) fun openRoomProfile(context: Context, roomId: String, directAccess: Int? = null)
fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem) fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem) {
openBigImageViewer(activity, sharedElement, matrixItem.avatarUrl, matrixItem.getBestName())
}
fun openBigImageViewer(activity: Activity, sharedElement: View?, mxcUrl: String?, title: String?)
fun openPinCode(context: Context, fun openPinCode(context: Context,
activityResultLauncher: ActivityResultLauncher<Intent>, activityResultLauncher: ActivityResultLauncher<Intent>,

View File

@ -25,9 +25,7 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
@ -52,7 +50,6 @@ import im.vector.app.features.home.room.list.actions.RoomListActionsArgs
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
import im.vector.app.features.media.BigImageViewerActivity
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
@ -289,13 +286,7 @@ class RoomProfileFragment @Inject constructor(
) )
} }
private fun onAvatarClicked(view: View, matrixItem: MatrixItem.RoomItem) = withState(roomProfileViewModel) { private fun onAvatarClicked(view: View, matrixItem: MatrixItem.RoomItem) {
matrixItem.avatarUrl navigator.openBigImageViewer(requireActivity(), view, matrixItem)
?.takeIf { it.isNotEmpty() }
?.let { avatarUrl ->
val intent = BigImageViewerActivity.newIntent(requireContext(), matrixItem.getBestName(), avatarUrl)
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), view, ViewCompat.getTransitionName(view) ?: "")
startActivity(intent, options.toBundle())
}
} }
} }

View File

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="7dp"
android:height="12dp"
android:viewportWidth="7"
android:viewportHeight="12">
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M6,11l-5,-5 5,-5"
android:strokeWidth="2"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"
android:strokeLineJoin="round"
tools:strokeColor="#FFAF0F" />
</vector>

View File

@ -1,14 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="7dp" android:width="7dp"
android:height="12dp" android:height="12dp"
android:viewportWidth="7" android:viewportWidth="7"
android:viewportHeight="12"> android:viewportHeight="12">
<path <path
android:pathData="M1,11l5,-5 -5,-5"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000" android:fillColor="#00000000"
android:fillType="evenOdd" android:fillType="evenOdd"
android:pathData="M1,11l5,-5 -5,-5"
android:strokeWidth="2"
android:strokeColor="#2E2F32" android:strokeColor="#2E2F32"
android:strokeLineCap="round"/> android:strokeLineCap="round"
android:strokeLineJoin="round"
tools:strokeColor="#FFAF0F" />
</vector> </vector>

View File

@ -84,6 +84,7 @@
android:insetRight="0dp" android:insetRight="0dp"
android:insetBottom="0dp" android:insetBottom="0dp"
android:padding="0dp" android:padding="0dp"
android:rotationY="@integer/rtl_mirror_flip"
app:cornerRadius="17dp" app:cornerRadius="17dp"
app:icon="@drawable/ic_qr_code_add" app:icon="@drawable/ic_qr_code_add"
app:iconGravity="textStart" app:iconGravity="textStart"