Merge branch 'develop' into feature/fga/rx_flow_migration

This commit is contained in:
ganfra 2021-10-28 15:27:25 +02:00
commit 83644846f6
24 changed files with 391 additions and 131 deletions

1
changelog.d/3968.bugfix Normal file
View File

@ -0,0 +1 @@
Fixing room search needing exact casing for non latin-1 character named rooms

1
changelog.d/4170.bugfix Normal file
View File

@ -0,0 +1 @@
Do not show shortcuts if a PIN code is set

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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)
}
}
}
} }

View File

@ -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

View File

@ -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 ?: "",

View File

@ -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
} }

View File

@ -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
} }
} }

View File

@ -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)
}
} }
} }

View File

@ -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,

View File

@ -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 {

View File

@ -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)

View File

@ -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) }
} }
} }

View File

@ -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) {

View File

@ -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

View File

@ -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> {

View File

@ -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))
} }
} }

View File

@ -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)
}
}

View File

@ -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())
}
}

View File

@ -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
}
} }

View File

@ -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)
) )
} }
} }

View File

@ -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) })
} }