Merge branch 'develop' into feature/fga/rx_flow_migration
This commit is contained in:
commit
83644846f6
|
@ -0,0 +1 @@
|
||||||
|
Fixing room search needing exact casing for non latin-1 character named rooms
|
|
@ -0,0 +1 @@
|
||||||
|
Do not show shortcuts if a PIN code is set
|
|
@ -19,17 +19,41 @@ package org.matrix.android.sdk.api.query
|
||||||
/**
|
/**
|
||||||
* Basic query language. All these cases are mutually exclusive.
|
* Basic query language. All these cases are mutually exclusive.
|
||||||
*/
|
*/
|
||||||
sealed class QueryStringValue {
|
sealed interface QueryStringValue {
|
||||||
object NoCondition : QueryStringValue()
|
sealed interface ContentQueryStringValue : QueryStringValue {
|
||||||
object IsNull : QueryStringValue()
|
val string: String
|
||||||
object IsNotNull : QueryStringValue()
|
val case: Case
|
||||||
object IsEmpty : QueryStringValue()
|
}
|
||||||
object IsNotEmpty : QueryStringValue()
|
|
||||||
data class Equals(val string: String, val case: Case = Case.SENSITIVE) : QueryStringValue()
|
object NoCondition : QueryStringValue
|
||||||
data class Contains(val string: String, val case: Case = Case.SENSITIVE) : QueryStringValue()
|
object IsNull : QueryStringValue
|
||||||
|
object IsNotNull : QueryStringValue
|
||||||
|
object IsEmpty : QueryStringValue
|
||||||
|
object IsNotEmpty : QueryStringValue
|
||||||
|
|
||||||
|
data class Equals(override val string: String, override val case: Case = Case.SENSITIVE) : ContentQueryStringValue
|
||||||
|
data class Contains(override val string: String, override val case: Case = Case.SENSITIVE) : ContentQueryStringValue
|
||||||
|
|
||||||
enum class Case {
|
enum class Case {
|
||||||
|
/**
|
||||||
|
* Match query sensitive to case
|
||||||
|
*/
|
||||||
SENSITIVE,
|
SENSITIVE,
|
||||||
INSENSITIVE
|
|
||||||
|
/**
|
||||||
|
* Match query insensitive to case, this only works for Latin-1 character sets
|
||||||
|
*/
|
||||||
|
INSENSITIVE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match query with input normalized (case insensitive)
|
||||||
|
* Works around Realms inability to sort or filter by case for non Latin-1 character sets
|
||||||
|
* Expects the target field to contain normalized data
|
||||||
|
*
|
||||||
|
* @see org.matrix.android.sdk.internal.util.Normalizer.normalize
|
||||||
|
*/
|
||||||
|
NORMALIZED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun QueryStringValue.isNormalized() = this is QueryStringValue.ContentQueryStringValue && case == QueryStringValue.Case.NORMALIZED
|
||||||
|
|
|
@ -94,13 +94,15 @@ interface RoomService {
|
||||||
* Get a snapshot list of room summaries.
|
* Get a snapshot list of room summaries.
|
||||||
* @return the immutable list of [RoomSummary]
|
* @return the immutable list of [RoomSummary]
|
||||||
*/
|
*/
|
||||||
fun getRoomSummaries(queryParams: RoomSummaryQueryParams): List<RoomSummary>
|
fun getRoomSummaries(queryParams: RoomSummaryQueryParams,
|
||||||
|
sortOrder: RoomSortOrder = RoomSortOrder.NONE): List<RoomSummary>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a live list of room summaries. This list is refreshed as soon as the data changes.
|
* Get a live list of room summaries. This list is refreshed as soon as the data changes.
|
||||||
* @return the [LiveData] of List[RoomSummary]
|
* @return the [LiveData] of List[RoomSummary]
|
||||||
*/
|
*/
|
||||||
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>>
|
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||||
|
sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): LiveData<List<RoomSummary>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a snapshot list of Breadcrumbs
|
* Get a snapshot list of Breadcrumbs
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.api.session.space
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
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.room.RoomSortOrder
|
||||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
|
import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
|
||||||
|
@ -74,9 +75,11 @@ interface SpaceService {
|
||||||
* Get a live list of space summaries. This list is refreshed as soon as the data changes.
|
* Get a live list of space summaries. This list is refreshed as soon as the data changes.
|
||||||
* @return the [LiveData] of List[SpaceSummary]
|
* @return the [LiveData] of List[SpaceSummary]
|
||||||
*/
|
*/
|
||||||
fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData<List<RoomSummary>>
|
fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams,
|
||||||
|
sortOrder: RoomSortOrder = RoomSortOrder.NONE): LiveData<List<RoomSummary>>
|
||||||
|
|
||||||
fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List<RoomSummary>
|
fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams,
|
||||||
|
sortOrder: RoomSortOrder = RoomSortOrder.NONE): List<RoomSummary>
|
||||||
|
|
||||||
suspend fun joinSpace(spaceIdOrAlias: String,
|
suspend fun joinSpace(spaceIdOrAlias: String,
|
||||||
reason: String? = null,
|
reason: String? = null,
|
||||||
|
|
|
@ -45,11 +45,24 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntityFields
|
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntityFields
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
import org.matrix.android.sdk.internal.query.process
|
import org.matrix.android.sdk.internal.query.process
|
||||||
|
import org.matrix.android.sdk.internal.util.Normalizer
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal object RealmSessionStoreMigration : RealmMigration {
|
internal class RealmSessionStoreMigration @Inject constructor(
|
||||||
|
private val normalizer: Normalizer
|
||||||
|
) : RealmMigration {
|
||||||
|
|
||||||
const val SESSION_STORE_SCHEMA_VERSION = 18L
|
companion object {
|
||||||
|
const val SESSION_STORE_SCHEMA_VERSION = 19L
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces all RealmSessionStoreMigration instances to be equal
|
||||||
|
* Avoids Realm throwing when multiple instances of the migration are set
|
||||||
|
*/
|
||||||
|
override fun equals(other: Any?) = other is RealmSessionStoreMigration
|
||||||
|
override fun hashCode() = 1000
|
||||||
|
|
||||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||||
Timber.v("Migrating Realm Session from $oldVersion to $newVersion")
|
Timber.v("Migrating Realm Session from $oldVersion to $newVersion")
|
||||||
|
@ -72,6 +85,7 @@ internal object RealmSessionStoreMigration : RealmMigration {
|
||||||
if (oldVersion <= 15) migrateTo16(realm)
|
if (oldVersion <= 15) migrateTo16(realm)
|
||||||
if (oldVersion <= 16) migrateTo17(realm)
|
if (oldVersion <= 16) migrateTo17(realm)
|
||||||
if (oldVersion <= 17) migrateTo18(realm)
|
if (oldVersion <= 17) migrateTo18(realm)
|
||||||
|
if (oldVersion <= 18) migrateTo19(realm)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun migrateTo1(realm: DynamicRealm) {
|
private fun migrateTo1(realm: DynamicRealm) {
|
||||||
|
@ -364,4 +378,16 @@ internal object RealmSessionStoreMigration : RealmMigration {
|
||||||
realm.schema.get("RoomMemberSummaryEntity")
|
realm.schema.get("RoomMemberSummaryEntity")
|
||||||
?.addRealmObjectField(RoomMemberSummaryEntityFields.USER_PRESENCE_ENTITY.`$`, userPresenceEntity)
|
?.addRealmObjectField(RoomMemberSummaryEntityFields.USER_PRESENCE_ENTITY.`$`, userPresenceEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun migrateTo19(realm: DynamicRealm) {
|
||||||
|
Timber.d("Step 18 -> 19")
|
||||||
|
realm.schema.get("RoomSummaryEntity")
|
||||||
|
?.addField(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, String::class.java)
|
||||||
|
?.transform {
|
||||||
|
it.getString(RoomSummaryEntityFields.DISPLAY_NAME)?.let { displayName ->
|
||||||
|
val normalised = normalizer.normalize(displayName)
|
||||||
|
it.set(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, normalised)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ private const val REALM_NAME = "disk_store.realm"
|
||||||
*/
|
*/
|
||||||
internal class SessionRealmConfigurationFactory @Inject constructor(
|
internal class SessionRealmConfigurationFactory @Inject constructor(
|
||||||
private val realmKeysUtils: RealmKeysUtils,
|
private val realmKeysUtils: RealmKeysUtils,
|
||||||
|
private val realmSessionStoreMigration: RealmSessionStoreMigration,
|
||||||
@SessionFilesDirectory val directory: File,
|
@SessionFilesDirectory val directory: File,
|
||||||
@SessionId val sessionId: String,
|
@SessionId val sessionId: String,
|
||||||
@UserMd5 val userMd5: String,
|
@UserMd5 val userMd5: String,
|
||||||
|
@ -71,7 +72,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
|
||||||
.allowWritesOnUiThread(true)
|
.allowWritesOnUiThread(true)
|
||||||
.modules(SessionRealmModule())
|
.modules(SessionRealmModule())
|
||||||
.schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION)
|
.schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION)
|
||||||
.migration(RealmSessionStoreMigration)
|
.migration(realmSessionStoreMigration)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
// Try creating a realm instance and if it succeeds we can clear the flag
|
// Try creating a realm instance and if it succeeds we can clear the flag
|
||||||
|
|
|
@ -42,7 +42,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
|
||||||
|
|
||||||
return RoomSummary(
|
return RoomSummary(
|
||||||
roomId = roomSummaryEntity.roomId,
|
roomId = roomSummaryEntity.roomId,
|
||||||
displayName = roomSummaryEntity.displayName ?: "",
|
displayName = roomSummaryEntity.displayName() ?: "",
|
||||||
name = roomSummaryEntity.name ?: "",
|
name = roomSummaryEntity.name ?: "",
|
||||||
topic = roomSummaryEntity.topic ?: "",
|
topic = roomSummaryEntity.topic ?: "",
|
||||||
avatarUrl = roomSummaryEntity.avatarUrl ?: "",
|
avatarUrl = roomSummaryEntity.avatarUrl ?: "",
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
||||||
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
||||||
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
|
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
|
||||||
|
import org.matrix.android.sdk.internal.session.room.membership.RoomName
|
||||||
|
|
||||||
internal open class RoomSummaryEntity(
|
internal open class RoomSummaryEntity(
|
||||||
@PrimaryKey var roomId: String = "",
|
@PrimaryKey var roomId: String = "",
|
||||||
|
@ -36,10 +37,24 @@ internal open class RoomSummaryEntity(
|
||||||
var children: RealmList<SpaceChildSummaryEntity> = RealmList()
|
var children: RealmList<SpaceChildSummaryEntity> = RealmList()
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
var displayName: String? = ""
|
private var displayName: String? = ""
|
||||||
set(value) {
|
|
||||||
if (value != field) field = value
|
fun displayName() = displayName
|
||||||
|
|
||||||
|
fun setDisplayName(roomName: RoomName) {
|
||||||
|
if (roomName.name != displayName) {
|
||||||
|
displayName = roomName.name
|
||||||
|
normalizedDisplayName = roomName.normalizedName
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Workaround for Realm only supporting Latin-1 character sets when sorting
|
||||||
|
* or filtering by case
|
||||||
|
* See https://github.com/realm/realm-core/issues/777
|
||||||
|
*/
|
||||||
|
private var normalizedDisplayName: String? = ""
|
||||||
|
|
||||||
var avatarUrl: String? = ""
|
var avatarUrl: String? = ""
|
||||||
set(value) {
|
set(value) {
|
||||||
if (value != field) field = value
|
if (value != field) field = value
|
||||||
|
@ -284,5 +299,6 @@ internal open class RoomSummaryEntity(
|
||||||
roomEncryptionTrustLevelStr = value?.name
|
roomEncryptionTrustLevelStr = value?.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object
|
companion object
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,24 +20,41 @@ import io.realm.Case
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import timber.log.Timber
|
import org.matrix.android.sdk.api.query.QueryStringValue.ContentQueryStringValue
|
||||||
|
import org.matrix.android.sdk.internal.util.Normalizer
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
fun <T : RealmObject> RealmQuery<T>.process(field: String, queryStringValue: QueryStringValue): RealmQuery<T> {
|
class QueryStringValueProcessor @Inject constructor(
|
||||||
when (queryStringValue) {
|
private val normalizer: Normalizer
|
||||||
is QueryStringValue.NoCondition -> Timber.v("No condition to process")
|
) {
|
||||||
is QueryStringValue.IsNotNull -> isNotNull(field)
|
|
||||||
is QueryStringValue.IsNull -> isNull(field)
|
fun <T : RealmObject> RealmQuery<T>.process(field: String, queryStringValue: QueryStringValue): RealmQuery<T> {
|
||||||
is QueryStringValue.IsEmpty -> isEmpty(field)
|
return when (queryStringValue) {
|
||||||
is QueryStringValue.IsNotEmpty -> isNotEmpty(field)
|
is QueryStringValue.NoCondition -> this
|
||||||
is QueryStringValue.Equals -> equalTo(field, queryStringValue.string, queryStringValue.case.toRealmCase())
|
is QueryStringValue.IsNotNull -> isNotNull(field)
|
||||||
is QueryStringValue.Contains -> contains(field, queryStringValue.string, queryStringValue.case.toRealmCase())
|
is QueryStringValue.IsNull -> isNull(field)
|
||||||
|
is QueryStringValue.IsEmpty -> isEmpty(field)
|
||||||
|
is QueryStringValue.IsNotEmpty -> isNotEmpty(field)
|
||||||
|
is ContentQueryStringValue -> when (queryStringValue) {
|
||||||
|
is QueryStringValue.Equals -> equalTo(field, queryStringValue.toRealmValue(), queryStringValue.case.toRealmCase())
|
||||||
|
is QueryStringValue.Contains -> contains(field, queryStringValue.toRealmValue(), queryStringValue.case.toRealmCase())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ContentQueryStringValue.toRealmValue(): String {
|
||||||
|
return when (case) {
|
||||||
|
QueryStringValue.Case.NORMALIZED -> normalizer.normalize(string)
|
||||||
|
QueryStringValue.Case.SENSITIVE,
|
||||||
|
QueryStringValue.Case.INSENSITIVE -> string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun QueryStringValue.Case.toRealmCase(): Case {
|
private fun QueryStringValue.Case.toRealmCase(): Case {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
QueryStringValue.Case.INSENSITIVE -> Case.INSENSITIVE
|
QueryStringValue.Case.INSENSITIVE -> Case.INSENSITIVE
|
||||||
QueryStringValue.Case.SENSITIVE -> Case.SENSITIVE
|
QueryStringValue.Case.SENSITIVE,
|
||||||
|
QueryStringValue.Case.NORMALIZED -> Case.SENSITIVE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,12 +30,16 @@ import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.GroupSummaryEntityFields
|
import org.matrix.android.sdk.internal.database.model.GroupSummaryEntityFields
|
||||||
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.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
import org.matrix.android.sdk.internal.query.QueryStringValueProcessor
|
||||||
import org.matrix.android.sdk.internal.query.process
|
import org.matrix.android.sdk.internal.query.process
|
||||||
import org.matrix.android.sdk.internal.util.fetchCopyMap
|
import org.matrix.android.sdk.internal.util.fetchCopyMap
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultGroupService @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
|
internal class DefaultGroupService @Inject constructor(
|
||||||
private val groupFactory: GroupFactory) : GroupService {
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
|
private val groupFactory: GroupFactory,
|
||||||
|
private val queryStringValueProcessor: QueryStringValueProcessor,
|
||||||
|
) : GroupService {
|
||||||
|
|
||||||
override fun getGroup(groupId: String): Group? {
|
override fun getGroup(groupId: String): Group? {
|
||||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||||
|
@ -67,8 +71,10 @@ internal class DefaultGroupService @Inject constructor(@SessionDatabase private
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun groupSummariesQuery(realm: Realm, queryParams: GroupSummaryQueryParams): RealmQuery<GroupSummaryEntity> {
|
private fun groupSummariesQuery(realm: Realm, queryParams: GroupSummaryQueryParams): RealmQuery<GroupSummaryEntity> {
|
||||||
return GroupSummaryEntity.where(realm)
|
return with(queryStringValueProcessor) {
|
||||||
.process(GroupSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
|
GroupSummaryEntity.where(realm)
|
||||||
.process(GroupSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
|
.process(GroupSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
|
||||||
|
.process(GroupSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,12 +85,14 @@ internal class DefaultRoomService @Inject constructor(
|
||||||
return roomSummaryDataSource.getRoomSummary(roomIdOrAlias)
|
return roomSummaryDataSource.getRoomSummary(roomIdOrAlias)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRoomSummaries(queryParams: RoomSummaryQueryParams): List<RoomSummary> {
|
override fun getRoomSummaries(queryParams: RoomSummaryQueryParams,
|
||||||
return roomSummaryDataSource.getRoomSummaries(queryParams)
|
sortOrder: RoomSortOrder): List<RoomSummary> {
|
||||||
|
return roomSummaryDataSource.getRoomSummaries(queryParams, sortOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>> {
|
override fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||||
return roomSummaryDataSource.getRoomSummariesLive(queryParams)
|
sortOrder: RoomSortOrder): LiveData<List<RoomSummary>> {
|
||||||
|
return roomSummaryDataSource.getRoomSummariesLive(queryParams, sortOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
|
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
|
import org.matrix.android.sdk.internal.query.QueryStringValueProcessor
|
||||||
import org.matrix.android.sdk.internal.query.process
|
import org.matrix.android.sdk.internal.query.process
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.admin.MembershipAdminTask
|
import org.matrix.android.sdk.internal.session.room.membership.admin.MembershipAdminTask
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.joining.InviteTask
|
import org.matrix.android.sdk.internal.session.room.membership.joining.InviteTask
|
||||||
|
@ -51,7 +52,8 @@ internal class DefaultMembershipService @AssistedInject constructor(
|
||||||
private val leaveRoomTask: LeaveRoomTask,
|
private val leaveRoomTask: LeaveRoomTask,
|
||||||
private val membershipAdminTask: MembershipAdminTask,
|
private val membershipAdminTask: MembershipAdminTask,
|
||||||
@UserId
|
@UserId
|
||||||
private val userId: String
|
private val userId: String,
|
||||||
|
private val queryStringValueProcessor: QueryStringValueProcessor
|
||||||
) : MembershipService {
|
) : MembershipService {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
@ -94,15 +96,17 @@ internal class DefaultMembershipService @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun roomMembersQuery(realm: Realm, queryParams: RoomMemberQueryParams): RealmQuery<RoomMemberSummaryEntity> {
|
private fun roomMembersQuery(realm: Realm, queryParams: RoomMemberQueryParams): RealmQuery<RoomMemberSummaryEntity> {
|
||||||
return RoomMemberHelper(realm, roomId).queryRoomMembersEvent()
|
return with(queryStringValueProcessor) {
|
||||||
.process(RoomMemberSummaryEntityFields.USER_ID, queryParams.userId)
|
RoomMemberHelper(realm, roomId).queryRoomMembersEvent()
|
||||||
.process(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
|
.process(RoomMemberSummaryEntityFields.USER_ID, queryParams.userId)
|
||||||
.process(RoomMemberSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
|
.process(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
|
||||||
.apply {
|
.process(RoomMemberSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
|
||||||
if (queryParams.excludeSelf) {
|
.apply {
|
||||||
notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
|
if (queryParams.excludeSelf) {
|
||||||
|
notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getNumberOfJoinedMembers(): Int {
|
override fun getNumberOfJoinedMembers(): Int {
|
||||||
|
|
|
@ -34,6 +34,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.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.session.displayname.DisplayNameResolver
|
import org.matrix.android.sdk.internal.session.displayname.DisplayNameResolver
|
||||||
|
import org.matrix.android.sdk.internal.util.Normalizer
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,6 +43,7 @@ import javax.inject.Inject
|
||||||
internal class RoomDisplayNameResolver @Inject constructor(
|
internal class RoomDisplayNameResolver @Inject constructor(
|
||||||
matrixConfiguration: MatrixConfiguration,
|
matrixConfiguration: MatrixConfiguration,
|
||||||
private val displayNameResolver: DisplayNameResolver,
|
private val displayNameResolver: DisplayNameResolver,
|
||||||
|
private val normalizer: Normalizer,
|
||||||
@UserId private val userId: String
|
@UserId private val userId: String
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -54,7 +56,7 @@ internal class RoomDisplayNameResolver @Inject constructor(
|
||||||
* @param roomId: the roomId to resolve the name of.
|
* @param roomId: the roomId to resolve the name of.
|
||||||
* @return the room display name
|
* @return the room display name
|
||||||
*/
|
*/
|
||||||
fun resolve(realm: Realm, roomId: String): String {
|
fun resolve(realm: Realm, roomId: String): RoomName {
|
||||||
// this algorithm is the one defined in
|
// this algorithm is the one defined in
|
||||||
// https://github.com/matrix-org/matrix-js-sdk/blob/develop/lib/models/room.js#L617
|
// https://github.com/matrix-org/matrix-js-sdk/blob/develop/lib/models/room.js#L617
|
||||||
// calculateRoomName(room, userId)
|
// calculateRoomName(room, userId)
|
||||||
|
@ -66,12 +68,12 @@ internal class RoomDisplayNameResolver @Inject constructor(
|
||||||
val roomName = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_NAME, stateKey = "")?.root
|
val roomName = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_NAME, stateKey = "")?.root
|
||||||
name = ContentMapper.map(roomName?.content).toModel<RoomNameContent>()?.name
|
name = ContentMapper.map(roomName?.content).toModel<RoomNameContent>()?.name
|
||||||
if (!name.isNullOrEmpty()) {
|
if (!name.isNullOrEmpty()) {
|
||||||
return name
|
return name.toRoomName()
|
||||||
}
|
}
|
||||||
val canonicalAlias = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root
|
val canonicalAlias = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root
|
||||||
name = ContentMapper.map(canonicalAlias?.content).toModel<RoomCanonicalAliasContent>()?.canonicalAlias
|
name = ContentMapper.map(canonicalAlias?.content).toModel<RoomCanonicalAliasContent>()?.canonicalAlias
|
||||||
if (!name.isNullOrEmpty()) {
|
if (!name.isNullOrEmpty()) {
|
||||||
return name
|
return name.toRoomName()
|
||||||
}
|
}
|
||||||
|
|
||||||
val roomMembers = RoomMemberHelper(realm, roomId)
|
val roomMembers = RoomMemberHelper(realm, roomId)
|
||||||
|
@ -152,7 +154,7 @@ internal class RoomDisplayNameResolver @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return name ?: roomId
|
return (name ?: roomId).toRoomName()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** See [org.matrix.android.sdk.api.session.room.sender.SenderInfo.disambiguatedDisplayName] */
|
/** See [org.matrix.android.sdk.api.session.room.sender.SenderInfo.disambiguatedDisplayName] */
|
||||||
|
@ -165,4 +167,8 @@ internal class RoomDisplayNameResolver @Inject constructor(
|
||||||
"${roomMemberSummary.displayName} (${roomMemberSummary.userId})"
|
"${roomMemberSummary.displayName} (${roomMemberSummary.userId})"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun String.toRoomName() = RoomName(this, normalizedName = normalizer.normalize(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal data class RoomName(val name: String, val normalizedName: String)
|
||||||
|
|
|
@ -31,11 +31,15 @@ import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||||
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
|
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
|
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
import org.matrix.android.sdk.internal.query.QueryStringValueProcessor
|
||||||
import org.matrix.android.sdk.internal.query.process
|
import org.matrix.android.sdk.internal.query.process
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class StateEventDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
|
internal class StateEventDataSource @Inject constructor(
|
||||||
private val realmSessionProvider: RealmSessionProvider) {
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
|
private val realmSessionProvider: RealmSessionProvider,
|
||||||
|
private val queryStringValueProcessor: QueryStringValueProcessor
|
||||||
|
) {
|
||||||
|
|
||||||
fun getStateEvent(roomId: String, eventType: String, stateKey: QueryStringValue): Event? {
|
fun getStateEvent(roomId: String, eventType: String, stateKey: QueryStringValue): Event? {
|
||||||
return realmSessionProvider.withRealm { realm ->
|
return realmSessionProvider.withRealm { realm ->
|
||||||
|
@ -78,13 +82,15 @@ internal class StateEventDataSource @Inject constructor(@SessionDatabase private
|
||||||
eventTypes: Set<String>,
|
eventTypes: Set<String>,
|
||||||
stateKey: QueryStringValue
|
stateKey: QueryStringValue
|
||||||
): RealmQuery<CurrentStateEventEntity> {
|
): RealmQuery<CurrentStateEventEntity> {
|
||||||
return realm.where<CurrentStateEventEntity>()
|
return with(queryStringValueProcessor) {
|
||||||
.equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId)
|
realm.where<CurrentStateEventEntity>()
|
||||||
.apply {
|
.equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId)
|
||||||
if (eventTypes.isNotEmpty()) {
|
.apply {
|
||||||
`in`(CurrentStateEventEntityFields.TYPE, eventTypes.toTypedArray())
|
if (eventTypes.isNotEmpty()) {
|
||||||
|
`in`(CurrentStateEventEntityFields.TYPE, eventTypes.toTypedArray())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.process(CurrentStateEventEntityFields.STATE_KEY, stateKey)
|
||||||
.process(CurrentStateEventEntityFields.STATE_KEY, stateKey)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,10 +25,10 @@ import androidx.paging.PagedList
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
import io.realm.Sort
|
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
|
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
|
||||||
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
||||||
|
import org.matrix.android.sdk.api.query.isNormalized
|
||||||
import org.matrix.android.sdk.api.session.room.ResultBoundaries
|
import org.matrix.android.sdk.api.session.room.ResultBoundaries
|
||||||
import org.matrix.android.sdk.api.session.room.RoomSortOrder
|
import org.matrix.android.sdk.api.session.room.RoomSortOrder
|
||||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||||
|
@ -48,12 +48,16 @@ import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.query.findByAlias
|
import org.matrix.android.sdk.internal.database.query.findByAlias
|
||||||
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.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
import org.matrix.android.sdk.internal.query.QueryStringValueProcessor
|
||||||
import org.matrix.android.sdk.internal.query.process
|
import org.matrix.android.sdk.internal.query.process
|
||||||
import org.matrix.android.sdk.internal.util.fetchCopyMap
|
import org.matrix.android.sdk.internal.util.fetchCopyMap
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
|
internal class RoomSummaryDataSource @Inject constructor(
|
||||||
private val roomSummaryMapper: RoomSummaryMapper) {
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
|
private val roomSummaryMapper: RoomSummaryMapper,
|
||||||
|
private val queryStringValueProcessor: QueryStringValueProcessor
|
||||||
|
) {
|
||||||
|
|
||||||
fun getRoomSummary(roomIdOrAlias: String): RoomSummary? {
|
fun getRoomSummary(roomIdOrAlias: String): RoomSummary? {
|
||||||
return monarchy
|
return monarchy
|
||||||
|
@ -80,25 +84,27 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRoomSummaries(queryParams: RoomSummaryQueryParams): List<RoomSummary> {
|
fun getRoomSummaries(queryParams: RoomSummaryQueryParams,
|
||||||
|
sortOrder: RoomSortOrder = RoomSortOrder.NONE): List<RoomSummary> {
|
||||||
return monarchy.fetchAllMappedSync(
|
return monarchy.fetchAllMappedSync(
|
||||||
{ roomSummariesQuery(it, queryParams) },
|
{ roomSummariesQuery(it, queryParams).process(sortOrder) },
|
||||||
{ roomSummaryMapper.map(it) }
|
{ roomSummaryMapper.map(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>> {
|
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||||
|
sortOrder: RoomSortOrder = RoomSortOrder.NONE): LiveData<List<RoomSummary>> {
|
||||||
return monarchy.findAllMappedWithChanges(
|
return monarchy.findAllMappedWithChanges(
|
||||||
{
|
{
|
||||||
roomSummariesQuery(it, queryParams)
|
roomSummariesQuery(it, queryParams).process(sortOrder)
|
||||||
.sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
|
|
||||||
},
|
},
|
||||||
{ roomSummaryMapper.map(it) }
|
{ roomSummaryMapper.map(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData<List<RoomSummary>> {
|
fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams,
|
||||||
return getRoomSummariesLive(queryParams)
|
sortOrder: RoomSortOrder = RoomSortOrder.NONE): LiveData<List<RoomSummary>> {
|
||||||
|
return getRoomSummariesLive(queryParams, sortOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSpaceSummary(roomIdOrAlias: String): RoomSummary? {
|
fun getSpaceSummary(roomIdOrAlias: String): RoomSummary? {
|
||||||
|
@ -122,8 +128,9 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List<RoomSummary> {
|
fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams,
|
||||||
return getRoomSummaries(spaceSummaryQueryParams)
|
sortOrder: RoomSortOrder = RoomSortOrder.NONE): List<RoomSummary> {
|
||||||
|
return getRoomSummaries(spaceSummaryQueryParams, sortOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRootSpaceSummaries(): List<RoomSummary> {
|
fun getRootSpaceSummaries(): List<RoomSummary> {
|
||||||
|
@ -238,12 +245,20 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery<RoomSummaryEntity> {
|
private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery<RoomSummaryEntity> {
|
||||||
val query = RoomSummaryEntity.where(realm)
|
val query = with(queryStringValueProcessor) {
|
||||||
query.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId)
|
RoomSummaryEntity.where(realm)
|
||||||
query.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
|
.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId)
|
||||||
query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias)
|
.let {
|
||||||
query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
|
if (queryParams.displayName.isNormalized()) {
|
||||||
query.equalTo(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, false)
|
it.process(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, queryParams.displayName)
|
||||||
|
} else {
|
||||||
|
it.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias)
|
||||||
|
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
|
||||||
|
.equalTo(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, false)
|
||||||
|
}
|
||||||
|
|
||||||
queryParams.roomCategoryFilter?.let {
|
queryParams.roomCategoryFilter?.let {
|
||||||
when (it) {
|
when (it) {
|
||||||
|
|
|
@ -65,6 +65,7 @@ import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataD
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver
|
import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
||||||
import org.matrix.android.sdk.internal.session.room.relationship.RoomChildRelationInfo
|
import org.matrix.android.sdk.internal.session.room.relationship.RoomChildRelationInfo
|
||||||
|
import org.matrix.android.sdk.internal.util.Normalizer
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
@ -75,7 +76,8 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
private val roomAvatarResolver: RoomAvatarResolver,
|
private val roomAvatarResolver: RoomAvatarResolver,
|
||||||
private val eventDecryptor: EventDecryptor,
|
private val eventDecryptor: EventDecryptor,
|
||||||
private val crossSigningService: DefaultCrossSigningService,
|
private val crossSigningService: DefaultCrossSigningService,
|
||||||
private val roomAccountDataDataSource: RoomAccountDataDataSource) {
|
private val roomAccountDataDataSource: RoomAccountDataDataSource,
|
||||||
|
private val normalizer: Normalizer) {
|
||||||
|
|
||||||
fun update(realm: Realm,
|
fun update(realm: Realm,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
|
@ -136,7 +138,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
// avoid this call if we are sure there are unread events
|
// avoid this call if we are sure there are unread events
|
||||||
!isEventRead(realm.configuration, userId, roomId, latestPreviewableEvent?.eventId)
|
!isEventRead(realm.configuration, userId, roomId, latestPreviewableEvent?.eventId)
|
||||||
|
|
||||||
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId)
|
roomSummaryEntity.setDisplayName(roomDisplayNameResolver.resolve(realm, roomId))
|
||||||
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId)
|
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId)
|
||||||
roomSummaryEntity.name = ContentMapper.map(lastNameEvent?.content).toModel<RoomNameContent>()?.name
|
roomSummaryEntity.name = ContentMapper.map(lastNameEvent?.content).toModel<RoomNameContent>()?.name
|
||||||
roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel<RoomTopicContent>()?.topic
|
roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel<RoomTopicContent>()?.topic
|
||||||
|
|
|
@ -23,6 +23,7 @@ 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.toContent
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.api.session.room.RoomSortOrder
|
||||||
import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||||
|
@ -94,12 +95,14 @@ internal class DefaultSpaceService @Inject constructor(
|
||||||
return spaceGetter.get(spaceId)
|
return spaceGetter.get(spaceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData<List<RoomSummary>> {
|
override fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams,
|
||||||
return roomSummaryDataSource.getSpaceSummariesLive(queryParams)
|
sortOrder: RoomSortOrder): LiveData<List<RoomSummary>> {
|
||||||
|
return roomSummaryDataSource.getSpaceSummariesLive(queryParams, sortOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List<RoomSummary> {
|
override fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams,
|
||||||
return roomSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams)
|
sortOrder: RoomSortOrder): List<RoomSummary> {
|
||||||
|
return roomSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams, sortOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRootSpaceSummaries(): List<RoomSummary> {
|
override fun getRootSpaceSummaries(): List<RoomSummary> {
|
||||||
|
|
|
@ -166,7 +166,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
|
||||||
roomSummaryEntity.directUserId = userId
|
roomSummaryEntity.directUserId = userId
|
||||||
// Also update the avatar and displayname, there is a specific treatment for DMs
|
// Also update the avatar and displayname, there is a specific treatment for DMs
|
||||||
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId)
|
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId)
|
||||||
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId)
|
roomSummaryEntity.setDisplayName(roomDisplayNameResolver.resolve(realm, roomId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -178,7 +178,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
|
||||||
it.directUserId = null
|
it.directUserId = null
|
||||||
// Also update the avatar and displayname, there was a specific treatment for DMs
|
// Also update the avatar and displayname, there was a specific treatment for DMs
|
||||||
it.avatarUrl = roomAvatarResolver.resolve(realm, it.roomId)
|
it.avatarUrl = roomAvatarResolver.resolve(realm, it.roomId)
|
||||||
it.displayName = roomDisplayNameResolver.resolve(realm, it.roomId)
|
it.setDisplayName(roomDisplayNameResolver.resolve(realm, it.roomId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* 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 java.text.Normalizer
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class Normalizer @Inject constructor() {
|
||||||
|
|
||||||
|
fun normalize(input: String): String {
|
||||||
|
return Normalizer.normalize(input.lowercase(), Normalizer.Form.NFD)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 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.database
|
||||||
|
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class RealmSessionStoreMigrationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when creating multiple migration instances then they are equal`() {
|
||||||
|
RealmSessionStoreMigration(normalizer = mockk()) shouldBeEqualTo RealmSessionStoreMigration(normalizer = mockk())
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,56 +24,92 @@ import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
import androidx.lifecycle.asFlow
|
import androidx.lifecycle.asFlow
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.dispatchers.CoroutineDispatchers
|
import im.vector.app.core.dispatchers.CoroutineDispatchers
|
||||||
|
import im.vector.app.features.pin.PinCodeStore
|
||||||
|
import im.vector.app.features.pin.PinCodeStoreListener
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onCompletion
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.onStart
|
||||||
import org.matrix.android.sdk.api.session.room.RoomSortOrder
|
import org.matrix.android.sdk.api.session.room.RoomSortOrder
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ShortcutsHandler @Inject constructor(
|
class ShortcutsHandler @Inject constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val appDispatchers: CoroutineDispatchers,
|
private val appDispatchers: CoroutineDispatchers,
|
||||||
private val shortcutCreator: ShortcutCreator,
|
private val shortcutCreator: ShortcutCreator,
|
||||||
private val activeSessionHolder: ActiveSessionHolder
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
) {
|
private val pinCodeStore: PinCodeStore
|
||||||
|
) : PinCodeStoreListener {
|
||||||
|
|
||||||
|
private val isRequestPinShortcutSupported = ShortcutManagerCompat.isRequestPinShortcutSupported(context)
|
||||||
|
|
||||||
|
// Value will be set correctly if necessary
|
||||||
|
private var hasPinCode = AtomicBoolean(true)
|
||||||
|
|
||||||
fun observeRoomsAndBuildShortcuts(coroutineScope: CoroutineScope): Job {
|
fun observeRoomsAndBuildShortcuts(coroutineScope: CoroutineScope): Job {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
|
||||||
// No op
|
// No op
|
||||||
return Job()
|
return Job()
|
||||||
}
|
}
|
||||||
return activeSessionHolder.getSafeActiveSession()
|
hasPinCode.set(pinCodeStore.getEncodedPin() != null)
|
||||||
?.getPagedRoomSummariesLive(
|
val session = activeSessionHolder.getSafeActiveSession() ?: return Job()
|
||||||
roomSummaryQueryParams {
|
return session.getRoomSummariesLive(
|
||||||
memberships = listOf(Membership.JOIN)
|
roomSummaryQueryParams {
|
||||||
},
|
memberships = listOf(Membership.JOIN)
|
||||||
sortOrder = RoomSortOrder.PRIORITY_AND_ACTIVITY
|
},
|
||||||
)
|
sortOrder = RoomSortOrder.PRIORITY_AND_ACTIVITY
|
||||||
?.asFlow()
|
)
|
||||||
?.onEach { rooms ->
|
.asFlow()
|
||||||
|
.onStart { pinCodeStore.addListener(this@ShortcutsHandler) }
|
||||||
|
.onCompletion { pinCodeStore.removeListener(this@ShortcutsHandler) }
|
||||||
|
.onEach { rooms ->
|
||||||
// Remove dead shortcuts (i.e. deleted rooms)
|
// Remove dead shortcuts (i.e. deleted rooms)
|
||||||
val roomIds = rooms.map { it.roomId }
|
removeDeadShortcut(rooms.map { it.roomId })
|
||||||
val deadShortcutIds = ShortcutManagerCompat.getShortcuts(context, ShortcutManagerCompat.FLAG_MATCH_DYNAMIC)
|
|
||||||
.map { it.id }
|
|
||||||
.filter { !roomIds.contains(it) }
|
|
||||||
ShortcutManagerCompat.removeLongLivedShortcuts(context, deadShortcutIds)
|
|
||||||
|
|
||||||
val shortcuts = rooms.mapIndexed { index, room ->
|
// Create shortcuts
|
||||||
shortcutCreator.create(room, index)
|
createShortcuts(rooms)
|
||||||
}
|
|
||||||
|
|
||||||
shortcuts.forEach { shortcut ->
|
|
||||||
ShortcutManagerCompat.pushDynamicShortcut(context, shortcut)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
?.flowOn(appDispatchers.computation)
|
.flowOn(appDispatchers.computation)
|
||||||
?.launchIn(coroutineScope)
|
.launchIn(coroutineScope)
|
||||||
?: Job()
|
}
|
||||||
|
|
||||||
|
private fun removeDeadShortcut(roomIds: List<String>) {
|
||||||
|
val deadShortcutIds = ShortcutManagerCompat.getShortcuts(context, ShortcutManagerCompat.FLAG_MATCH_DYNAMIC)
|
||||||
|
.map { it.id }
|
||||||
|
.filter { !roomIds.contains(it) }
|
||||||
|
|
||||||
|
if (deadShortcutIds.isNotEmpty()) {
|
||||||
|
Timber.d("Removing shortcut(s) $deadShortcutIds")
|
||||||
|
ShortcutManagerCompat.removeLongLivedShortcuts(context, deadShortcutIds)
|
||||||
|
if (isRequestPinShortcutSupported) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||||
|
context.getSystemService<ShortcutManager>()?.disableShortcuts(deadShortcutIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createShortcuts(rooms: List<RoomSummary>) {
|
||||||
|
if (hasPinCode.get()) {
|
||||||
|
// No shortcut in this case (privacy)
|
||||||
|
ShortcutManagerCompat.removeAllDynamicShortcuts(context)
|
||||||
|
} else {
|
||||||
|
val shortcuts = rooms.mapIndexed { index, room ->
|
||||||
|
shortcutCreator.create(room, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
shortcuts.forEach { shortcut ->
|
||||||
|
ShortcutManagerCompat.pushDynamicShortcut(context, shortcut)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearShortcuts() {
|
fun clearShortcuts() {
|
||||||
|
@ -89,7 +125,7 @@ class ShortcutsHandler @Inject constructor(
|
||||||
ShortcutManagerCompat.removeLongLivedShortcuts(context, shortcuts)
|
ShortcutManagerCompat.removeLongLivedShortcuts(context, shortcuts)
|
||||||
|
|
||||||
// We can only disabled pinned shortcuts with the API, but at least it will prevent the crash
|
// We can only disabled pinned shortcuts with the API, but at least it will prevent the crash
|
||||||
if (ShortcutManagerCompat.isRequestPinShortcutSupported(context)) {
|
if (isRequestPinShortcutSupported) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||||
context.getSystemService<ShortcutManager>()
|
context.getSystemService<ShortcutManager>()
|
||||||
?.let {
|
?.let {
|
||||||
|
@ -98,4 +134,14 @@ class ShortcutsHandler @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPinSetUpChange(isConfigured: Boolean) {
|
||||||
|
hasPinCode.set(isConfigured)
|
||||||
|
if (isConfigured) {
|
||||||
|
// Remove shortcuts immediately
|
||||||
|
ShortcutManagerCompat.removeAllDynamicShortcuts(context)
|
||||||
|
}
|
||||||
|
// Else shortcut will be created next time any room summary is updated, or
|
||||||
|
// next time the app is started which is acceptable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,7 +192,7 @@ class RoomListViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
updatableQuery?.updateQuery {
|
updatableQuery?.updateQuery {
|
||||||
it.copy(
|
it.copy(
|
||||||
displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE)
|
displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.NORMALIZED)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
@ -56,26 +57,40 @@ interface PinCodeStore {
|
||||||
* Will reset the counters
|
* Will reset the counters
|
||||||
*/
|
*/
|
||||||
fun resetCounters()
|
fun resetCounters()
|
||||||
|
|
||||||
|
fun addListener(listener: PinCodeStoreListener)
|
||||||
|
fun removeListener(listener: PinCodeStoreListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
class SharedPrefPinCodeStore @Inject constructor(private val sharedPreferences: SharedPreferences) : PinCodeStore {
|
interface PinCodeStoreListener {
|
||||||
|
fun onPinSetUpChange(isConfigured: Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun storeEncodedPin(encodePin: String) = withContext(Dispatchers.IO) {
|
@Singleton
|
||||||
sharedPreferences.edit {
|
class SharedPrefPinCodeStore @Inject constructor(private val sharedPreferences: SharedPreferences) : PinCodeStore {
|
||||||
putString(ENCODED_PIN_CODE_KEY, encodePin)
|
private val listeners = mutableSetOf<PinCodeStoreListener>()
|
||||||
|
|
||||||
|
override suspend fun storeEncodedPin(encodePin: String) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
sharedPreferences.edit {
|
||||||
|
putString(ENCODED_PIN_CODE_KEY, encodePin)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
listeners.forEach { it.onPinSetUpChange(isConfigured = true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deleteEncodedPin() = withContext(Dispatchers.IO) {
|
override suspend fun deleteEncodedPin() {
|
||||||
// Also reset the counters
|
withContext(Dispatchers.IO) {
|
||||||
resetCounters()
|
// Also reset the counters
|
||||||
sharedPreferences.edit {
|
resetCounters()
|
||||||
remove(ENCODED_PIN_CODE_KEY)
|
sharedPreferences.edit {
|
||||||
|
remove(ENCODED_PIN_CODE_KEY)
|
||||||
|
}
|
||||||
|
awaitPinCodeCallback<Boolean> {
|
||||||
|
PFSecurityManager.getInstance().pinCodeHelper.delete(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
awaitPinCodeCallback<Boolean> {
|
listeners.forEach { it.onPinSetUpChange(isConfigured = false) }
|
||||||
PFSecurityManager.getInstance().pinCodeHelper.delete(it)
|
|
||||||
}
|
|
||||||
return@withContext
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getEncodedPin(): String? {
|
override fun getEncodedPin(): String? {
|
||||||
|
@ -124,6 +139,14 @@ class SharedPrefPinCodeStore @Inject constructor(private val sharedPreferences:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun addListener(listener: PinCodeStoreListener) {
|
||||||
|
listeners.add(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeListener(listener: PinCodeStoreListener) {
|
||||||
|
listeners.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend inline fun <T> awaitPinCodeCallback(crossinline callback: (PFPinCodeHelperCallback<T>) -> Unit) = suspendCoroutine<PFResult<T>> { cont ->
|
private suspend inline fun <T> awaitPinCodeCallback(crossinline callback: (PFPinCodeHelperCallback<T>) -> Unit) = suspendCoroutine<PFResult<T>> { cont ->
|
||||||
callback(PFPinCodeHelperCallback<T> { result -> cont.resume(result) })
|
callback(PFPinCodeHelperCallback<T> { result -> cont.resume(result) })
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue