mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-22 17:35:54 +03:00
Merge pull request #3092 from vector-im/feature/bca/paged_room_list
Room List performance PR (use Live PagedList via Monarchy)
This commit is contained in:
commit
3109d111a4
37 changed files with 1233 additions and 608 deletions
|
@ -14,6 +14,7 @@ Improvements 🙌:
|
||||||
- Update reactions to Unicode 13.1 (#2998)
|
- Update reactions to Unicode 13.1 (#2998)
|
||||||
- Be more robust when parsing some enums
|
- Be more robust when parsing some enums
|
||||||
- Improve timeline filtering (dissociate membership and profile events, display hidden events when highlighted, fix hidden item/read receipts behavior)
|
- Improve timeline filtering (dissociate membership and profile events, display hidden events when highlighted, fix hidden item/read receipts behavior)
|
||||||
|
- Room list improvements (paging)
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
- Fix bad theme change for the MainActivity
|
- Fix bad theme change for the MainActivity
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* 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.api.query
|
||||||
|
|
||||||
|
enum class RoomCategoryFilter {
|
||||||
|
ONLY_DM,
|
||||||
|
ONLY_ROOMS,
|
||||||
|
ONLY_WITH_NOTIFICATIONS,
|
||||||
|
ALL
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.api.query
|
||||||
|
|
||||||
|
data class RoomTagQueryFilter(
|
||||||
|
val isFavorite: Boolean?,
|
||||||
|
val isLowPriority: Boolean?,
|
||||||
|
val isServerNotice: Boolean?
|
||||||
|
)
|
|
@ -17,6 +17,7 @@
|
||||||
package org.matrix.android.sdk.api.session.room
|
package org.matrix.android.sdk.api.session.room
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.paging.PagedList
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
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.members.ChangeMembershipState
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
|
@ -24,6 +25,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||||
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.api.session.room.model.create.CreateRoomParams
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||||
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
|
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
|
||||||
|
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
|
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
|
||||||
|
@ -178,4 +180,29 @@ interface RoomService {
|
||||||
* This call will try to gather some information on this room, but it could fail and get nothing more
|
* This call will try to gather some information on this room, but it could fail and get nothing more
|
||||||
*/
|
*/
|
||||||
fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback<PeekResult>)
|
fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback<PeekResult>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO Doc
|
||||||
|
*/
|
||||||
|
fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||||
|
pagedListConfig: PagedList.Config = defaultPagedListConfig): LiveData<PagedList<RoomSummary>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO Doc
|
||||||
|
*/
|
||||||
|
fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||||
|
pagedListConfig: PagedList.Config = defaultPagedListConfig): UpdatableFilterLivePageResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO Doc
|
||||||
|
*/
|
||||||
|
fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount
|
||||||
|
|
||||||
|
private val defaultPagedListConfig
|
||||||
|
get() = PagedList.Config.Builder()
|
||||||
|
.setPageSize(10)
|
||||||
|
.setInitialLoadSizeHint(20)
|
||||||
|
.setEnablePlaceholders(false)
|
||||||
|
.setPrefetchDistance(10)
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
package org.matrix.android.sdk.api.session.room
|
package org.matrix.android.sdk.api.session.room
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
|
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
||||||
|
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
|
|
||||||
fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): RoomSummaryQueryParams {
|
fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): RoomSummaryQueryParams {
|
||||||
|
@ -31,7 +33,9 @@ data class RoomSummaryQueryParams(
|
||||||
val roomId: QueryStringValue,
|
val roomId: QueryStringValue,
|
||||||
val displayName: QueryStringValue,
|
val displayName: QueryStringValue,
|
||||||
val canonicalAlias: QueryStringValue,
|
val canonicalAlias: QueryStringValue,
|
||||||
val memberships: List<Membership>
|
val memberships: List<Membership>,
|
||||||
|
val roomCategoryFilter: RoomCategoryFilter?,
|
||||||
|
val roomTagQueryFilter: RoomTagQueryFilter?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
class Builder {
|
class Builder {
|
||||||
|
@ -40,12 +44,16 @@ data class RoomSummaryQueryParams(
|
||||||
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
|
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
|
||||||
var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition
|
var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition
|
||||||
var memberships: List<Membership> = Membership.all()
|
var memberships: List<Membership> = Membership.all()
|
||||||
|
var roomCategoryFilter: RoomCategoryFilter? = RoomCategoryFilter.ALL
|
||||||
|
var roomTagQueryFilter: RoomTagQueryFilter? = null
|
||||||
|
|
||||||
fun build() = RoomSummaryQueryParams(
|
fun build() = RoomSummaryQueryParams(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
displayName = displayName,
|
displayName = displayName,
|
||||||
canonicalAlias = canonicalAlias,
|
canonicalAlias = canonicalAlias,
|
||||||
memberships = memberships
|
memberships = memberships,
|
||||||
|
roomCategoryFilter = roomCategoryFilter,
|
||||||
|
roomTagQueryFilter = roomTagQueryFilter
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright (c) 2021 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,12 +14,14 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.home
|
package org.matrix.android.sdk.api.session.room
|
||||||
|
|
||||||
import im.vector.app.core.utils.BehaviorDataSource
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.paging.PagedList
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
interface UpdatableFilterLivePageResult {
|
||||||
class HomeRoomListDataSource @Inject constructor() : BehaviorDataSource<List<RoomSummary>>()
|
val livePagedList: LiveData<PagedList<RoomSummary>>
|
||||||
|
|
||||||
|
fun updateQuery(queryParams: RoomSummaryQueryParams)
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* 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.api.session.room.summary
|
||||||
|
|
||||||
|
data class RoomAggregateNotificationCount(
|
||||||
|
val notificationCount: Int,
|
||||||
|
val highlightCount: Int
|
||||||
|
) {
|
||||||
|
val totalCount = notificationCount + highlightCount
|
||||||
|
val isHighlight = highlightCount > 0
|
||||||
|
}
|
|
@ -17,22 +17,27 @@
|
||||||
package org.matrix.android.sdk.internal.database
|
package org.matrix.android.sdk.internal.database
|
||||||
|
|
||||||
import io.realm.DynamicRealm
|
import io.realm.DynamicRealm
|
||||||
|
import io.realm.FieldAttribute
|
||||||
import io.realm.RealmMigration
|
import io.realm.RealmMigration
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
||||||
import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields
|
import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.model.EditionOfEventFields
|
import org.matrix.android.sdk.internal.database.model.EditionOfEventFields
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
|
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
|
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
|
import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomEntityFields
|
import org.matrix.android.sdk.internal.database.model.RoomEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
|
import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val SESSION_STORE_SCHEMA_VERSION = 8L
|
const val SESSION_STORE_SCHEMA_VERSION = 9L
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||||
|
@ -46,6 +51,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||||
if (oldVersion <= 5) migrateTo6(realm)
|
if (oldVersion <= 5) migrateTo6(realm)
|
||||||
if (oldVersion <= 6) migrateTo7(realm)
|
if (oldVersion <= 6) migrateTo7(realm)
|
||||||
if (oldVersion <= 7) migrateTo8(realm)
|
if (oldVersion <= 7) migrateTo8(realm)
|
||||||
|
if (oldVersion <= 8) migrateTo9(realm)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun migrateTo1(realm: DynamicRealm) {
|
private fun migrateTo1(realm: DynamicRealm) {
|
||||||
|
@ -149,4 +155,43 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||||
?.removeField("sourceLocalEchoEvents")
|
?.removeField("sourceLocalEchoEvents")
|
||||||
?.addRealmListField(EditAggregatedSummaryEntityFields.EDITIONS.`$`, editionOfEventSchema)
|
?.addRealmListField(EditAggregatedSummaryEntityFields.EDITIONS.`$`, editionOfEventSchema)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun migrateTo9(realm: DynamicRealm) {
|
||||||
|
Timber.d("Step 8 -> 9")
|
||||||
|
|
||||||
|
realm.schema.get("RoomSummaryEntity")
|
||||||
|
?.addField(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Long::class.java, FieldAttribute.INDEXED)
|
||||||
|
?.setNullable(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, true)
|
||||||
|
?.addIndex(RoomSummaryEntityFields.MEMBERSHIP_STR)
|
||||||
|
?.addIndex(RoomSummaryEntityFields.IS_DIRECT)
|
||||||
|
?.addIndex(RoomSummaryEntityFields.VERSIONING_STATE_STR)
|
||||||
|
|
||||||
|
?.addField(RoomSummaryEntityFields.IS_FAVOURITE, Boolean::class.java)
|
||||||
|
?.addIndex(RoomSummaryEntityFields.IS_FAVOURITE)
|
||||||
|
?.addField(RoomSummaryEntityFields.IS_LOW_PRIORITY, Boolean::class.java)
|
||||||
|
?.addIndex(RoomSummaryEntityFields.IS_LOW_PRIORITY)
|
||||||
|
?.addField(RoomSummaryEntityFields.IS_SERVER_NOTICE, Boolean::class.java)
|
||||||
|
?.addIndex(RoomSummaryEntityFields.IS_SERVER_NOTICE)
|
||||||
|
|
||||||
|
?.transform { obj ->
|
||||||
|
|
||||||
|
val isFavorite = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any {
|
||||||
|
it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_FAVOURITE
|
||||||
|
}
|
||||||
|
obj.setBoolean(RoomSummaryEntityFields.IS_FAVOURITE, isFavorite)
|
||||||
|
|
||||||
|
val isLowPriority = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any {
|
||||||
|
it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_LOW_PRIORITY
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.setBoolean(RoomSummaryEntityFields.IS_LOW_PRIORITY, isLowPriority)
|
||||||
|
|
||||||
|
// XXX migrate last message origin server ts
|
||||||
|
obj.getObject(RoomSummaryEntityFields.LATEST_PREVIEWABLE_EVENT.`$`)
|
||||||
|
?.getObject(TimelineEventEntityFields.ROOT.`$`)
|
||||||
|
?.getLong(EventEntityFields.ORIGIN_SERVER_TS)?.let {
|
||||||
|
obj.setLong(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
|
||||||
private val typingUsersTracker: DefaultTypingUsersTracker) {
|
private val typingUsersTracker: DefaultTypingUsersTracker) {
|
||||||
|
|
||||||
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
|
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
|
||||||
val tags = roomSummaryEntity.tags.map {
|
val tags = roomSummaryEntity.tags().map {
|
||||||
RoomTag(it.tagName, it.tagOrder)
|
RoomTag(it.tagName, it.tagOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,61 +16,217 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.database.model
|
package org.matrix.android.sdk.internal.database.model
|
||||||
|
|
||||||
|
import io.realm.RealmList
|
||||||
|
import io.realm.RealmObject
|
||||||
|
import io.realm.annotations.Index
|
||||||
|
import io.realm.annotations.PrimaryKey
|
||||||
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
||||||
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.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
||||||
import io.realm.RealmList
|
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
||||||
import io.realm.RealmObject
|
|
||||||
import io.realm.annotations.PrimaryKey
|
|
||||||
|
|
||||||
internal open class RoomSummaryEntity(
|
internal open class RoomSummaryEntity(
|
||||||
@PrimaryKey var roomId: String = "",
|
@PrimaryKey var roomId: String = ""
|
||||||
var displayName: String? = "",
|
|
||||||
var avatarUrl: String? = "",
|
|
||||||
var name: String? = "",
|
|
||||||
var topic: String? = "",
|
|
||||||
var latestPreviewableEvent: TimelineEventEntity? = null,
|
|
||||||
var heroes: RealmList<String> = RealmList(),
|
|
||||||
var joinedMembersCount: Int? = 0,
|
|
||||||
var invitedMembersCount: Int? = 0,
|
|
||||||
var isDirect: Boolean = false,
|
|
||||||
var directUserId: String? = null,
|
|
||||||
var otherMemberIds: RealmList<String> = RealmList(),
|
|
||||||
var notificationCount: Int = 0,
|
|
||||||
var highlightCount: Int = 0,
|
|
||||||
var readMarkerId: String? = null,
|
|
||||||
var hasUnreadMessages: Boolean = false,
|
|
||||||
var tags: RealmList<RoomTagEntity> = RealmList(),
|
|
||||||
var userDrafts: UserDraftsEntity? = null,
|
|
||||||
var breadcrumbsIndex: Int = RoomSummary.NOT_IN_BREADCRUMBS,
|
|
||||||
var canonicalAlias: String? = null,
|
|
||||||
var aliases: RealmList<String> = RealmList(),
|
|
||||||
// this is required for querying
|
|
||||||
var flatAliases: String = "",
|
|
||||||
var isEncrypted: Boolean = false,
|
|
||||||
var encryptionEventTs: Long? = 0,
|
|
||||||
var roomEncryptionTrustLevelStr: String? = null,
|
|
||||||
var inviterId: String? = null,
|
|
||||||
var hasFailedSending: Boolean = false
|
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
|
var displayName: String? = ""
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
var avatarUrl: String? = ""
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
var name: String? = ""
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
var topic: String? = ""
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var latestPreviewableEvent: TimelineEventEntity? = null
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Index
|
||||||
|
var lastActivityTime: Long? = null
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var heroes: RealmList<String> = RealmList()
|
||||||
|
|
||||||
|
var joinedMembersCount: Int? = 0
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var invitedMembersCount: Int? = 0
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Index
|
||||||
|
var isDirect: Boolean = false
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var directUserId: String? = null
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var otherMemberIds: RealmList<String> = RealmList()
|
||||||
|
|
||||||
|
var notificationCount: Int = 0
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var highlightCount: Int = 0
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var readMarkerId: String? = null
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasUnreadMessages: Boolean = false
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
private var tags: RealmList<RoomTagEntity> = RealmList()
|
||||||
|
|
||||||
|
fun tags(): List<RoomTagEntity> = tags
|
||||||
|
|
||||||
|
fun updateTags(newTags: List<Pair<String, Double?>>) {
|
||||||
|
val toDelete = mutableListOf<RoomTagEntity>()
|
||||||
|
tags.forEach { existingTag ->
|
||||||
|
val updatedTag = newTags.firstOrNull { it.first == existingTag.tagName }
|
||||||
|
if (updatedTag == null) {
|
||||||
|
toDelete.add(existingTag)
|
||||||
|
} else {
|
||||||
|
existingTag.tagOrder = updatedTag.second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toDelete.forEach { it.deleteFromRealm() }
|
||||||
|
newTags.forEach { newTag ->
|
||||||
|
if (tags.all { it.tagName != newTag.first }) {
|
||||||
|
// we must add it
|
||||||
|
tags.add(
|
||||||
|
RoomTagEntity(newTag.first, newTag.second)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isFavourite = newTags.any { it.first == RoomTag.ROOM_TAG_FAVOURITE }
|
||||||
|
isLowPriority = newTags.any { it.first == RoomTag.ROOM_TAG_LOW_PRIORITY }
|
||||||
|
isServerNotice = newTags.any { it.first == RoomTag.ROOM_TAG_SERVER_NOTICE }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Index
|
||||||
|
var isFavourite: Boolean = false
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Index
|
||||||
|
var isLowPriority: Boolean = false
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Index
|
||||||
|
var isServerNotice: Boolean = false
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var userDrafts: UserDraftsEntity? = null
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var breadcrumbsIndex: Int = RoomSummary.NOT_IN_BREADCRUMBS
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var canonicalAlias: String? = null
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var aliases: RealmList<String> = RealmList()
|
||||||
|
|
||||||
|
fun updateAliases(newAliases: List<String>) {
|
||||||
|
// only update underlying field if there is a diff
|
||||||
|
if (newAliases.distinct().sorted() != aliases.distinct().sorted()) {
|
||||||
|
aliases.clear()
|
||||||
|
aliases.addAll(newAliases)
|
||||||
|
flatAliases = newAliases.joinToString(separator = "|", prefix = "|")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is required for querying
|
||||||
|
var flatAliases: String = ""
|
||||||
|
|
||||||
|
var isEncrypted: Boolean = false
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var encryptionEventTs: Long? = 0
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var roomEncryptionTrustLevelStr: String? = null
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var inviterId: String? = null
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasFailedSending: Boolean = false
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Index
|
||||||
private var membershipStr: String = Membership.NONE.name
|
private var membershipStr: String = Membership.NONE.name
|
||||||
|
|
||||||
var membership: Membership
|
var membership: Membership
|
||||||
get() {
|
get() {
|
||||||
return Membership.valueOf(membershipStr)
|
return Membership.valueOf(membershipStr)
|
||||||
}
|
}
|
||||||
set(value) {
|
set(value) {
|
||||||
membershipStr = value.name
|
if (value.name != membershipStr) {
|
||||||
|
membershipStr = value.name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Index
|
||||||
private var versioningStateStr: String = VersioningState.NONE.name
|
private var versioningStateStr: String = VersioningState.NONE.name
|
||||||
var versioningState: VersioningState
|
var versioningState: VersioningState
|
||||||
get() {
|
get() {
|
||||||
return VersioningState.valueOf(versioningStateStr)
|
return VersioningState.valueOf(versioningStateStr)
|
||||||
}
|
}
|
||||||
set(value) {
|
set(value) {
|
||||||
versioningStateStr = value.name
|
if (value.name != versioningStateStr) {
|
||||||
|
versioningStateStr = value.name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var roomEncryptionTrustLevel: RoomEncryptionTrustLevel?
|
var roomEncryptionTrustLevel: RoomEncryptionTrustLevel?
|
||||||
|
@ -84,7 +240,9 @@ internal open class RoomSummaryEntity(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
set(value) {
|
set(value) {
|
||||||
roomEncryptionTrustLevelStr = value?.name
|
if (value?.name != roomEncryptionTrustLevelStr) {
|
||||||
|
roomEncryptionTrustLevelStr = value?.name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object
|
companion object
|
||||||
|
|
|
@ -18,17 +18,20 @@ package org.matrix.android.sdk.internal.session.room
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Transformations
|
import androidx.lifecycle.Transformations
|
||||||
|
import androidx.paging.PagedList
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
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.Room
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
import org.matrix.android.sdk.api.session.room.RoomService
|
import org.matrix.android.sdk.api.session.room.RoomService
|
||||||
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.UpdatableFilterLivePageResult
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||||
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.api.session.room.model.create.CreateRoomParams
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||||
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
|
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
|
||||||
|
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.api.util.toOptional
|
import org.matrix.android.sdk.api.util.toOptional
|
||||||
|
@ -96,6 +99,20 @@ internal class DefaultRoomService @Inject constructor(
|
||||||
return roomSummaryDataSource.getRoomSummariesLive(queryParams)
|
return roomSummaryDataSource.getRoomSummariesLive(queryParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config)
|
||||||
|
: LiveData<PagedList<RoomSummary>> {
|
||||||
|
return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams, pagedListConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config)
|
||||||
|
: UpdatableFilterLivePageResult {
|
||||||
|
return roomSummaryDataSource.getFilteredPagedRoomSummariesLive(queryParams, pagedListConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount {
|
||||||
|
return roomSummaryDataSource.getNotificationCountForRooms(queryParams)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List<RoomSummary> {
|
override fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List<RoomSummary> {
|
||||||
return roomSummaryDataSource.getBreadcrumbs(queryParams)
|
return roomSummaryDataSource.getBreadcrumbs(queryParams)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,18 +17,19 @@
|
||||||
package org.matrix.android.sdk.internal.session.room.create
|
package org.matrix.android.sdk.internal.session.room.create
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import kotlinx.coroutines.TimeoutCancellationException
|
import kotlinx.coroutines.TimeoutCancellationException
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.api.failure.MatrixError
|
import org.matrix.android.sdk.api.failure.MatrixError
|
||||||
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
|
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
|
||||||
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
|
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
|
||||||
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
|
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomEntityFields
|
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||||
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.network.GlobalErrorReceiver
|
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||||
|
@ -96,12 +97,18 @@ internal class DefaultCreateRoomTask @Inject constructor(
|
||||||
// Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before)
|
// Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before)
|
||||||
try {
|
try {
|
||||||
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
||||||
realm.where(RoomEntity::class.java)
|
realm.where(RoomSummaryEntity::class.java)
|
||||||
.equalTo(RoomEntityFields.ROOM_ID, roomId)
|
.equalTo(RoomSummaryEntityFields.ROOM_ID, roomId)
|
||||||
|
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
||||||
}
|
}
|
||||||
} catch (exception: TimeoutCancellationException) {
|
} catch (exception: TimeoutCancellationException) {
|
||||||
throw CreateRoomFailure.CreatedWithTimeout
|
throw CreateRoomFailure.CreatedWithTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Realm.getInstance(realmConfiguration).executeTransactionAsync {
|
||||||
|
RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
|
||||||
if (otherUserId != null) {
|
if (otherUserId != null) {
|
||||||
handleDirectChatCreation(roomId, otherUserId)
|
handleDirectChatCreation(roomId, otherUserId)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,20 +16,23 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.membership.joining
|
package org.matrix.android.sdk.internal.session.room.membership.joining
|
||||||
|
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import kotlinx.coroutines.TimeoutCancellationException
|
||||||
import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
|
import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
|
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomEntityFields
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||||
|
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.network.GlobalErrorReceiver
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
|
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
|
||||||
import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask
|
import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import kotlinx.coroutines.TimeoutCancellationException
|
|
||||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -68,12 +71,18 @@ internal class DefaultJoinRoomTask @Inject constructor(
|
||||||
val roomId = joinRoomResponse.roomId
|
val roomId = joinRoomResponse.roomId
|
||||||
try {
|
try {
|
||||||
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
||||||
realm.where(RoomEntity::class.java)
|
realm.where(RoomSummaryEntity::class.java)
|
||||||
.equalTo(RoomEntityFields.ROOM_ID, roomId)
|
.equalTo(RoomSummaryEntityFields.ROOM_ID, roomId)
|
||||||
|
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
||||||
}
|
}
|
||||||
} catch (exception: TimeoutCancellationException) {
|
} catch (exception: TimeoutCancellationException) {
|
||||||
throw JoinRoomFailure.JoinedWithTimeout
|
throw JoinRoomFailure.JoinedWithTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Realm.getInstance(realmConfiguration).executeTransactionAsync {
|
||||||
|
RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
|
||||||
setReadMarkers(roomId)
|
setReadMarkers(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,18 @@ package org.matrix.android.sdk.internal.session.room.summary
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Transformations
|
import androidx.lifecycle.Transformations
|
||||||
|
import androidx.paging.LivePagedListBuilder
|
||||||
|
import androidx.paging.PagedList
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmQuery
|
||||||
|
import io.realm.Sort
|
||||||
|
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
||||||
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.UpdatableFilterLivePageResult
|
||||||
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.api.session.room.model.VersioningState
|
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
||||||
|
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.api.util.toOptional
|
import org.matrix.android.sdk.api.util.toOptional
|
||||||
import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper
|
import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper
|
||||||
|
@ -32,8 +40,6 @@ 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.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 io.realm.Realm
|
|
||||||
import io.realm.RealmQuery
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
|
internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
|
||||||
|
@ -98,6 +104,62 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
||||||
.sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX)
|
.sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||||
|
pagedListConfig: PagedList.Config): LiveData<PagedList<RoomSummary>> {
|
||||||
|
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
||||||
|
roomSummariesQuery(realm, queryParams)
|
||||||
|
.sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
|
||||||
|
}
|
||||||
|
val dataSourceFactory = realmDataSourceFactory.map {
|
||||||
|
roomSummaryMapper.map(it)
|
||||||
|
}
|
||||||
|
return monarchy.findAllPagedWithChanges(
|
||||||
|
realmDataSourceFactory,
|
||||||
|
LivePagedListBuilder(dataSourceFactory, pagedListConfig)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||||
|
pagedListConfig: PagedList.Config): UpdatableFilterLivePageResult {
|
||||||
|
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
||||||
|
roomSummariesQuery(realm, queryParams)
|
||||||
|
.sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
|
||||||
|
}
|
||||||
|
val dataSourceFactory = realmDataSourceFactory.map {
|
||||||
|
roomSummaryMapper.map(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val mapped = monarchy.findAllPagedWithChanges(
|
||||||
|
realmDataSourceFactory,
|
||||||
|
LivePagedListBuilder(dataSourceFactory, pagedListConfig)
|
||||||
|
)
|
||||||
|
|
||||||
|
return object : UpdatableFilterLivePageResult {
|
||||||
|
override val livePagedList: LiveData<PagedList<RoomSummary>> = mapped
|
||||||
|
|
||||||
|
override fun updateQuery(queryParams: RoomSummaryQueryParams) {
|
||||||
|
realmDataSourceFactory.updateQuery {
|
||||||
|
roomSummariesQuery(it, queryParams)
|
||||||
|
.sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount {
|
||||||
|
var notificationCount: RoomAggregateNotificationCount? = null
|
||||||
|
monarchy.doWithRealm { realm ->
|
||||||
|
val roomSummariesQuery = roomSummariesQuery(realm, queryParams)
|
||||||
|
val notifCount = roomSummariesQuery.sum(RoomSummaryEntityFields.NOTIFICATION_COUNT).toInt()
|
||||||
|
val highlightCount = roomSummariesQuery.sum(RoomSummaryEntityFields.HIGHLIGHT_COUNT).toInt()
|
||||||
|
notificationCount = RoomAggregateNotificationCount(
|
||||||
|
notifCount,
|
||||||
|
highlightCount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return notificationCount!!
|
||||||
|
}
|
||||||
|
|
||||||
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 = RoomSummaryEntity.where(realm)
|
||||||
query.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId)
|
query.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId)
|
||||||
|
@ -105,6 +167,28 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
||||||
query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias)
|
query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias)
|
||||||
query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
|
query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
|
||||||
query.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
|
query.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
|
||||||
|
|
||||||
|
queryParams.roomCategoryFilter?.let {
|
||||||
|
when (it) {
|
||||||
|
RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
|
||||||
|
RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
|
||||||
|
RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0)
|
||||||
|
RoomCategoryFilter.ALL -> {
|
||||||
|
// nop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queryParams.roomTagQueryFilter?.let {
|
||||||
|
it.isFavorite?.let { fav ->
|
||||||
|
query.equalTo(RoomSummaryEntityFields.IS_FAVOURITE, fav)
|
||||||
|
}
|
||||||
|
it.isLowPriority?.let { lp ->
|
||||||
|
query.equalTo(RoomSummaryEntityFields.IS_LOW_PRIORITY, lp)
|
||||||
|
}
|
||||||
|
it.isServerNotice?.let { sn ->
|
||||||
|
query.equalTo(RoomSummaryEntityFields.IS_SERVER_NOTICE, sn)
|
||||||
|
}
|
||||||
|
}
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,6 +98,11 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
|
|
||||||
val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
||||||
|
|
||||||
|
val lastActivityFromEvent = latestPreviewableEvent?.root?.originServerTs
|
||||||
|
if (lastActivityFromEvent != null) {
|
||||||
|
roomSummaryEntity.lastActivityTime = lastActivityFromEvent
|
||||||
|
}
|
||||||
|
|
||||||
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
|
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
|
||||||
// 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)
|
||||||
|
@ -112,9 +117,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
|
|
||||||
val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel<RoomAliasesContent>()?.aliases
|
val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel<RoomAliasesContent>()?.aliases
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
roomSummaryEntity.aliases.clear()
|
roomSummaryEntity.updateAliases(roomAliases)
|
||||||
roomSummaryEntity.aliases.addAll(roomAliases)
|
|
||||||
roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|")
|
|
||||||
roomSummaryEntity.isEncrypted = encryptionEvent != null
|
roomSummaryEntity.isEncrypted = encryptionEvent != null
|
||||||
roomSummaryEntity.encryptionEventTs = encryptionEvent?.originServerTs
|
roomSummaryEntity.encryptionEventTs = encryptionEvent?.originServerTs
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.session.sync
|
||||||
import org.matrix.android.sdk.api.session.room.model.tag.RoomTagContent
|
import org.matrix.android.sdk.api.session.room.model.tag.RoomTagContent
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomTagEntity
|
import org.matrix.android.sdk.internal.database.model.RoomTagEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RoomTagHandler @Inject constructor() {
|
internal class RoomTagHandler @Inject constructor() {
|
||||||
|
@ -31,12 +31,8 @@ internal class RoomTagHandler @Inject constructor() {
|
||||||
}
|
}
|
||||||
val tags = content.tags.entries.map { (tagName, params) ->
|
val tags = content.tags.entries.map { (tagName, params) ->
|
||||||
RoomTagEntity(tagName, params["order"] as? Double)
|
RoomTagEntity(tagName, params["order"] as? Double)
|
||||||
|
Pair(tagName, params["order"] as? Double)
|
||||||
}
|
}
|
||||||
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
|
RoomSummaryEntity.getOrCreate(realm, roomId).updateTags(tags)
|
||||||
?: RoomSummaryEntity(roomId)
|
|
||||||
|
|
||||||
roomSummaryEntity.tags.clear()
|
|
||||||
roomSummaryEntity.tags.addAll(tags)
|
|
||||||
realm.insertOrUpdate(roomSummaryEntity)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,7 +161,7 @@ Formatter\.formatShortFileSize===1
|
||||||
# android\.text\.TextUtils
|
# android\.text\.TextUtils
|
||||||
|
|
||||||
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
|
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
|
||||||
enum class===93
|
enum class===94
|
||||||
|
|
||||||
### Do not import temporary legacy classes
|
### Do not import temporary legacy classes
|
||||||
import org.matrix.android.sdk.internal.legacy.riot===3
|
import org.matrix.android.sdk.internal.legacy.riot===3
|
||||||
|
|
|
@ -19,78 +19,26 @@ package im.vector.app
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleObserver
|
import androidx.lifecycle.LifecycleObserver
|
||||||
import androidx.lifecycle.OnLifecycleEvent
|
import androidx.lifecycle.OnLifecycleEvent
|
||||||
import arrow.core.Option
|
|
||||||
import im.vector.app.features.grouplist.ALL_COMMUNITIES_GROUP_ID
|
|
||||||
import im.vector.app.features.grouplist.SelectedGroupDataSource
|
|
||||||
import im.vector.app.features.home.HomeRoomListDataSource
|
|
||||||
import im.vector.app.features.home.room.list.ChronologicalRoomComparator
|
|
||||||
import io.reactivex.Observable
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
|
||||||
import io.reactivex.disposables.CompositeDisposable
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
import io.reactivex.functions.BiFunction
|
|
||||||
import io.reactivex.rxkotlin.addTo
|
|
||||||
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
|
||||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
|
||||||
import org.matrix.android.sdk.rx.rx
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class handles the global app state. At the moment, it only manages room list.
|
* This class handles the global app state.
|
||||||
* It requires to be added to ProcessLifecycleOwner.get().lifecycle
|
* It requires to be added to ProcessLifecycleOwner.get().lifecycle
|
||||||
*/
|
*/
|
||||||
|
// TODO Keep this class for now, will maybe be used fro Space
|
||||||
@Singleton
|
@Singleton
|
||||||
class AppStateHandler @Inject constructor(
|
class AppStateHandler @Inject constructor() : LifecycleObserver {
|
||||||
private val sessionDataSource: ActiveSessionDataSource,
|
|
||||||
private val homeRoomListDataSource: HomeRoomListDataSource,
|
|
||||||
private val selectedGroupDataSource: SelectedGroupDataSource,
|
|
||||||
private val chronologicalRoomComparator: ChronologicalRoomComparator) : LifecycleObserver {
|
|
||||||
|
|
||||||
private val compositeDisposable = CompositeDisposable()
|
private val compositeDisposable = CompositeDisposable()
|
||||||
|
|
||||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||||
fun entersForeground() {
|
fun entersForeground() {
|
||||||
observeRoomsAndGroup()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||||
fun entersBackground() {
|
fun entersBackground() {
|
||||||
compositeDisposable.clear()
|
compositeDisposable.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeRoomsAndGroup() {
|
|
||||||
Observable
|
|
||||||
.combineLatest<List<RoomSummary>, Option<GroupSummary>, List<RoomSummary>>(
|
|
||||||
sessionDataSource.observe()
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.switchMap {
|
|
||||||
val query = roomSummaryQueryParams {}
|
|
||||||
it.orNull()?.rx()?.liveRoomSummaries(query)
|
|
||||||
?: Observable.just(emptyList())
|
|
||||||
}
|
|
||||||
.throttleLast(300, TimeUnit.MILLISECONDS),
|
|
||||||
selectedGroupDataSource.observe(),
|
|
||||||
BiFunction { rooms, selectedGroupOption ->
|
|
||||||
val selectedGroup = selectedGroupOption.orNull()
|
|
||||||
val filteredRooms = rooms.filter {
|
|
||||||
if (selectedGroup == null || selectedGroup.groupId == ALL_COMMUNITIES_GROUP_ID) {
|
|
||||||
true
|
|
||||||
} else if (it.isDirect) {
|
|
||||||
it.otherMemberIds
|
|
||||||
.intersect(selectedGroup.userIds)
|
|
||||||
.isNotEmpty()
|
|
||||||
} else {
|
|
||||||
selectedGroup.roomIds.contains(it.roomId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
filteredRooms.sortedWith(chronologicalRoomComparator)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.subscribe {
|
|
||||||
homeRoomListDataSource.post(it)
|
|
||||||
}
|
|
||||||
.addTo(compositeDisposable)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,6 @@ import im.vector.app.features.crypto.keysrequest.KeyRequestHandler
|
||||||
import im.vector.app.features.crypto.verification.IncomingVerificationRequestHandler
|
import im.vector.app.features.crypto.verification.IncomingVerificationRequestHandler
|
||||||
import im.vector.app.features.grouplist.SelectedGroupDataSource
|
import im.vector.app.features.grouplist.SelectedGroupDataSource
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import im.vector.app.features.home.HomeRoomListDataSource
|
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore
|
import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder
|
import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder
|
||||||
|
@ -113,8 +112,6 @@ interface VectorComponent {
|
||||||
|
|
||||||
fun errorFormatter(): ErrorFormatter
|
fun errorFormatter(): ErrorFormatter
|
||||||
|
|
||||||
fun homeRoomListObservableStore(): HomeRoomListDataSource
|
|
||||||
|
|
||||||
fun selectedGroupStore(): SelectedGroupDataSource
|
fun selectedGroupStore(): SelectedGroupDataSource
|
||||||
|
|
||||||
fun roomDetailPendingActionStore(): RoomDetailPendingActionStore
|
fun roomDetailPendingActionStore(): RoomDetailPendingActionStore
|
||||||
|
|
|
@ -127,6 +127,12 @@ abstract class VectorBaseFragment<VB: ViewBinding> : BaseMvRxFragment(), HasScre
|
||||||
Timber.i("onResume Fragment ${javaClass.simpleName}")
|
Timber.i("onResume Fragment ${javaClass.simpleName}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
Timber.i("onPause Fragment ${javaClass.simpleName}")
|
||||||
|
}
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
@ -149,7 +155,9 @@ abstract class VectorBaseFragment<VB: ViewBinding> : BaseMvRxFragment(), HasScre
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
Timber.i("onDestroy Fragment ${javaClass.simpleName}")
|
||||||
uiDisposables.dispose()
|
uiDisposables.dispose()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,4 +20,5 @@ import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
sealed class HomeDetailAction : VectorViewModelAction {
|
sealed class HomeDetailAction : VectorViewModelAction {
|
||||||
data class SwitchDisplayMode(val displayMode: RoomListDisplayMode) : HomeDetailAction()
|
data class SwitchDisplayMode(val displayMode: RoomListDisplayMode) : HomeDetailAction()
|
||||||
|
object MarkAllRoomsRead : HomeDetailAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ package im.vector.app.features.home
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
@ -33,8 +35,8 @@ import im.vector.app.core.platform.ToolbarConfigurable
|
||||||
import im.vector.app.core.platform.VectorBaseActivity
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.ui.views.CurrentCallsView
|
import im.vector.app.core.ui.views.CurrentCallsView
|
||||||
import im.vector.app.core.ui.views.KnownCallsViewHolder
|
|
||||||
import im.vector.app.core.ui.views.KeysBackupBanner
|
import im.vector.app.core.ui.views.KeysBackupBanner
|
||||||
|
import im.vector.app.core.ui.views.KnownCallsViewHolder
|
||||||
import im.vector.app.databinding.FragmentHomeDetailBinding
|
import im.vector.app.databinding.FragmentHomeDetailBinding
|
||||||
import im.vector.app.features.call.SharedKnownCallsViewModel
|
import im.vector.app.features.call.SharedKnownCallsViewModel
|
||||||
import im.vector.app.features.call.VectorCallActivity
|
import im.vector.app.features.call.VectorCallActivity
|
||||||
|
@ -49,7 +51,6 @@ import im.vector.app.features.themes.ThemeUtils
|
||||||
import im.vector.app.features.workers.signout.BannerState
|
import im.vector.app.features.workers.signout.BannerState
|
||||||
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
|
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
|
||||||
import im.vector.app.features.workers.signout.ServerBackupStatusViewState
|
import im.vector.app.features.workers.signout.ServerBackupStatusViewState
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||||
|
@ -79,6 +80,32 @@ class HomeDetailFragment @Inject constructor(
|
||||||
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
|
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
|
||||||
private lateinit var sharedCallActionViewModel: SharedKnownCallsViewModel
|
private lateinit var sharedCallActionViewModel: SharedKnownCallsViewModel
|
||||||
|
|
||||||
|
private var hasUnreadRooms = false
|
||||||
|
set(value) {
|
||||||
|
if (value != field) {
|
||||||
|
field = value
|
||||||
|
invalidateOptionsMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMenuRes() = R.menu.room_list
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
when (item.itemId) {
|
||||||
|
R.id.menu_home_mark_all_as_read -> {
|
||||||
|
viewModel.handle(HomeDetailAction.MarkAllRoomsRead)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||||
|
menu.findItem(R.id.menu_home_mark_all_as_read).isVisible = hasUnreadRooms
|
||||||
|
super.onPrepareOptionsMenu(menu)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentHomeDetailBinding {
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentHomeDetailBinding {
|
||||||
return FragmentHomeDetailBinding.inflate(inflater, container, false)
|
return FragmentHomeDetailBinding.inflate(inflater, container, false)
|
||||||
}
|
}
|
||||||
|
@ -314,6 +341,8 @@ class HomeDetailFragment @Inject constructor(
|
||||||
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms)
|
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms)
|
||||||
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup)
|
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup)
|
||||||
views.syncStateView.render(it.syncState)
|
views.syncStateView.render(it.syncState)
|
||||||
|
|
||||||
|
hasUnreadRooms = it.hasUnreadMessages
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun BadgeDrawable.render(count: Int, highlight: Boolean) {
|
private fun BadgeDrawable.render(count: Int, highlight: Boolean) {
|
||||||
|
|
|
@ -16,22 +16,30 @@
|
||||||
|
|
||||||
package im.vector.app.features.home
|
package im.vector.app.features.home
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.airbnb.mvrx.FragmentViewModelContext
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
import im.vector.app.core.di.HasScreenInjector
|
import im.vector.app.core.di.HasScreenInjector
|
||||||
import im.vector.app.core.platform.EmptyViewEvents
|
import im.vector.app.core.platform.EmptyViewEvents
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.grouplist.SelectedGroupDataSource
|
import im.vector.app.features.grouplist.SelectedGroupDataSource
|
||||||
import im.vector.app.features.ui.UiStateRepository
|
import im.vector.app.features.ui.UiStateRepository
|
||||||
import io.reactivex.schedulers.Schedulers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
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.roomSummaryQueryParams
|
||||||
|
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||||
|
import org.matrix.android.sdk.rx.asObservable
|
||||||
import org.matrix.android.sdk.rx.rx
|
import org.matrix.android.sdk.rx.rx
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View model used to update the home bottom bar notification counts, observe the sync state and
|
* View model used to update the home bottom bar notification counts, observe the sync state and
|
||||||
|
@ -41,7 +49,6 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val uiStateRepository: UiStateRepository,
|
private val uiStateRepository: UiStateRepository,
|
||||||
private val selectedGroupStore: SelectedGroupDataSource,
|
private val selectedGroupStore: SelectedGroupDataSource,
|
||||||
private val homeRoomListStore: HomeRoomListDataSource,
|
|
||||||
private val stringProvider: StringProvider)
|
private val stringProvider: StringProvider)
|
||||||
: VectorViewModel<HomeDetailViewState, HomeDetailAction, EmptyViewEvents>(initialState) {
|
: VectorViewModel<HomeDetailViewState, HomeDetailAction, EmptyViewEvents>(initialState) {
|
||||||
|
|
||||||
|
@ -75,6 +82,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
||||||
override fun handle(action: HomeDetailAction) {
|
override fun handle(action: HomeDetailAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is HomeDetailAction.SwitchDisplayMode -> handleSwitchDisplayMode(action)
|
is HomeDetailAction.SwitchDisplayMode -> handleSwitchDisplayMode(action)
|
||||||
|
HomeDetailAction.MarkAllRoomsRead -> handleMarkAllRoomsRead()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,6 +98,26 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
||||||
|
|
||||||
// PRIVATE METHODS *****************************************************************************
|
// PRIVATE METHODS *****************************************************************************
|
||||||
|
|
||||||
|
private fun handleMarkAllRoomsRead() = withState { _ ->
|
||||||
|
// questionable to use viewmodelscope
|
||||||
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
|
val roomIds = session.getRoomSummaries(
|
||||||
|
roomSummaryQueryParams {
|
||||||
|
memberships = listOf(Membership.JOIN)
|
||||||
|
roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.map { it.roomId }
|
||||||
|
try {
|
||||||
|
awaitCallback<Unit> {
|
||||||
|
session.markAllAsRead(roomIds, it)
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.d(failure, "Failed to mark all as read")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun observeSyncState() {
|
private fun observeSyncState() {
|
||||||
session.rx()
|
session.rx()
|
||||||
.liveSyncState()
|
.liveSyncState()
|
||||||
|
@ -113,43 +141,51 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeRoomSummaries() {
|
private fun observeRoomSummaries() {
|
||||||
homeRoomListStore
|
session.getPagedRoomSummariesLive(
|
||||||
.observe()
|
roomSummaryQueryParams {
|
||||||
.observeOn(Schedulers.computation())
|
memberships = Membership.activeMemberships()
|
||||||
.map { it.asSequence() }
|
}
|
||||||
.subscribe { summaries ->
|
)
|
||||||
val invitesDm = summaries
|
.asObservable()
|
||||||
.filter { it.membership == Membership.INVITE && it.isDirect }
|
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||||
.count()
|
.subscribe {
|
||||||
|
val dmInvites = session.getRoomSummaries(
|
||||||
|
roomSummaryQueryParams {
|
||||||
|
memberships = listOf(Membership.INVITE)
|
||||||
|
roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||||
|
}
|
||||||
|
).size
|
||||||
|
|
||||||
val invitesRoom = summaries
|
val roomsInvite = session.getRoomSummaries(
|
||||||
.filter { it.membership == Membership.INVITE && it.isDirect.not() }
|
roomSummaryQueryParams {
|
||||||
.count()
|
memberships = listOf(Membership.INVITE)
|
||||||
|
roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||||
|
}
|
||||||
|
).size
|
||||||
|
|
||||||
val peopleNotifications = summaries
|
val dmRooms = session.getNotificationCountForRooms(
|
||||||
.filter { it.isDirect }
|
roomSummaryQueryParams {
|
||||||
.map { it.notificationCount }
|
memberships = listOf(Membership.JOIN)
|
||||||
.sum()
|
roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||||
val peopleHasHighlight = summaries
|
}
|
||||||
.filter { it.isDirect }
|
)
|
||||||
.any { it.highlightCount > 0 }
|
|
||||||
|
|
||||||
val roomsNotifications = summaries
|
val otherRooms = session.getNotificationCountForRooms(
|
||||||
.filter { !it.isDirect }
|
roomSummaryQueryParams {
|
||||||
.map { it.notificationCount }
|
memberships = listOf(Membership.JOIN)
|
||||||
.sum()
|
roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||||
val roomsHasHighlight = summaries
|
}
|
||||||
.filter { !it.isDirect }
|
)
|
||||||
.any { it.highlightCount > 0 }
|
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
notificationCountCatchup = peopleNotifications + roomsNotifications + invitesDm + invitesRoom,
|
notificationCountCatchup = dmRooms.totalCount + otherRooms.totalCount + roomsInvite + dmInvites,
|
||||||
notificationHighlightCatchup = peopleHasHighlight || roomsHasHighlight,
|
notificationHighlightCatchup = dmRooms.isHighlight || otherRooms.isHighlight,
|
||||||
notificationCountPeople = peopleNotifications + invitesDm,
|
notificationCountPeople = dmRooms.totalCount + dmInvites,
|
||||||
notificationHighlightPeople = peopleHasHighlight || invitesDm > 0,
|
notificationHighlightPeople = dmRooms.isHighlight || dmInvites > 0,
|
||||||
notificationCountRooms = roomsNotifications + invitesRoom,
|
notificationCountRooms = otherRooms.totalCount + roomsInvite,
|
||||||
notificationHighlightRooms = roomsHasHighlight || invitesRoom > 0
|
notificationHighlightRooms = otherRooms.isHighlight || roomsInvite > 0,
|
||||||
|
hasUnreadMessages = dmRooms.totalCount + otherRooms.totalCount > 0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,5 +34,6 @@ data class HomeDetailViewState(
|
||||||
val notificationHighlightPeople: Boolean = false,
|
val notificationHighlightPeople: Boolean = false,
|
||||||
val notificationCountRooms: Int = 0,
|
val notificationCountRooms: Int = 0,
|
||||||
val notificationHighlightRooms: Boolean = false,
|
val notificationHighlightRooms: Boolean = false,
|
||||||
|
val hasUnreadMessages: Boolean = false,
|
||||||
val syncState: SyncState = SyncState.Idle
|
val syncState: SyncState = SyncState.Idle
|
||||||
) : MvRxState
|
) : MvRxState
|
||||||
|
|
|
@ -21,36 +21,44 @@ import android.content.pm.ShortcutManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.content.pm.ShortcutManagerCompat
|
import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
import io.reactivex.Observable
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.disposables.Disposables
|
||||||
|
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
|
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||||
|
import org.matrix.android.sdk.rx.asObservable
|
||||||
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 homeRoomListStore: HomeRoomListDataSource,
|
private val shortcutCreator: ShortcutCreator,
|
||||||
private val shortcutCreator: ShortcutCreator
|
private val activeSessionHolder: ActiveSessionHolder
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun observeRoomsAndBuildShortcuts(): Disposable {
|
fun observeRoomsAndBuildShortcuts(): Disposable {
|
||||||
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 Observable.empty<Unit>().subscribe()
|
return Disposables.empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
return homeRoomListStore
|
return activeSessionHolder.getSafeActiveSession()
|
||||||
.observe()
|
?.getPagedRoomSummariesLive(
|
||||||
.distinctUntilChanged()
|
roomSummaryQueryParams {
|
||||||
.observeOn(Schedulers.computation())
|
memberships = listOf(Membership.JOIN)
|
||||||
.subscribe { rooms ->
|
roomTagQueryFilter = RoomTagQueryFilter(isFavorite = true, null, null)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
?.asObservable()
|
||||||
|
?.subscribe { rooms ->
|
||||||
val shortcuts = rooms
|
val shortcuts = rooms
|
||||||
.filter { room -> room.isFavorite }
|
|
||||||
.take(n = 4) // Android only allows us to create 4 shortcuts
|
.take(n = 4) // Android only allows us to create 4 shortcuts
|
||||||
.map { shortcutCreator.create(it) }
|
.map { shortcutCreator.create(it) }
|
||||||
|
|
||||||
ShortcutManagerCompat.removeAllDynamicShortcuts(context)
|
ShortcutManagerCompat.removeAllDynamicShortcuts(context)
|
||||||
ShortcutManagerCompat.addDynamicShortcuts(context, shortcuts)
|
ShortcutManagerCompat.addDynamicShortcuts(context, shortcuts)
|
||||||
}
|
}
|
||||||
|
?: Disposables.empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearShortcuts() {
|
fun clearShortcuts() {
|
||||||
|
|
|
@ -22,12 +22,11 @@ import org.matrix.android.sdk.api.session.room.notification.RoomNotificationStat
|
||||||
|
|
||||||
sealed class RoomListAction : VectorViewModelAction {
|
sealed class RoomListAction : VectorViewModelAction {
|
||||||
data class SelectRoom(val roomSummary: RoomSummary) : RoomListAction()
|
data class SelectRoom(val roomSummary: RoomSummary) : RoomListAction()
|
||||||
data class ToggleCategory(val category: RoomCategory) : RoomListAction()
|
data class ToggleSection(val section: RoomsSection) : RoomListAction()
|
||||||
data class AcceptInvitation(val roomSummary: RoomSummary) : RoomListAction()
|
data class AcceptInvitation(val roomSummary: RoomSummary) : RoomListAction()
|
||||||
data class RejectInvitation(val roomSummary: RoomSummary) : RoomListAction()
|
data class RejectInvitation(val roomSummary: RoomSummary) : RoomListAction()
|
||||||
data class FilterWith(val filter: String) : RoomListAction()
|
data class FilterWith(val filter: String) : RoomListAction()
|
||||||
data class ChangeRoomNotificationState(val roomId: String, val notificationState: RoomNotificationState) : RoomListAction()
|
data class ChangeRoomNotificationState(val roomId: String, val notificationState: RoomNotificationState) : RoomListAction()
|
||||||
data class ToggleTag(val roomId: String, val tag: String) : RoomListAction()
|
data class ToggleTag(val roomId: String, val tag: String) : RoomListAction()
|
||||||
data class LeaveRoom(val roomId: String) : RoomListAction()
|
data class LeaveRoom(val roomId: String) : RoomListAction()
|
||||||
object MarkAllRoomsRead : RoomListAction()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home.room.list
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.epoxy.helpFooterItem
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.core.resources.UserPreferencesProvider
|
||||||
|
import im.vector.app.features.home.RoomListDisplayMode
|
||||||
|
import im.vector.app.features.home.room.filtered.filteredRoomFooterItem
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomListFooterController @Inject constructor(
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val userPreferencesProvider: UserPreferencesProvider
|
||||||
|
) : TypedEpoxyController<RoomListViewState>() {
|
||||||
|
|
||||||
|
var listener: RoomListListener? = null
|
||||||
|
|
||||||
|
override fun buildModels(data: RoomListViewState?) {
|
||||||
|
when (data?.displayMode) {
|
||||||
|
RoomListDisplayMode.FILTERED -> {
|
||||||
|
filteredRoomFooterItem {
|
||||||
|
id("filter_footer")
|
||||||
|
listener(listener)
|
||||||
|
currentFilter(data.roomFilter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
if (userPreferencesProvider.shouldShowLongClickOnRoomHelp()) {
|
||||||
|
helpFooterItem {
|
||||||
|
id("long_click_help")
|
||||||
|
text(stringProvider.getString(R.string.help_long_click_on_room_for_more_options))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,19 +20,15 @@ import android.content.DialogInterface
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
|
||||||
import android.view.MenuItem
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.recyclerview.widget.ConcatAdapter
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.airbnb.epoxy.OnModelBuildFinishedListener
|
import com.airbnb.epoxy.OnModelBuildFinishedListener
|
||||||
import com.airbnb.mvrx.Fail
|
|
||||||
import com.airbnb.mvrx.Incomplete
|
|
||||||
import com.airbnb.mvrx.Success
|
|
||||||
import com.airbnb.mvrx.args
|
import com.airbnb.mvrx.args
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
|
@ -44,6 +40,7 @@ import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.platform.OnBackPressed
|
import im.vector.app.core.platform.OnBackPressed
|
||||||
import im.vector.app.core.platform.StateView
|
import im.vector.app.core.platform.StateView
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.app.core.resources.UserPreferencesProvider
|
||||||
import im.vector.app.databinding.FragmentRoomListBinding
|
import im.vector.app.databinding.FragmentRoomListBinding
|
||||||
import im.vector.app.features.home.RoomListDisplayMode
|
import im.vector.app.features.home.RoomListDisplayMode
|
||||||
import im.vector.app.features.home.room.list.actions.RoomListActionsArgs
|
import im.vector.app.features.home.room.list.actions.RoomListActionsArgs
|
||||||
|
@ -53,8 +50,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA
|
||||||
import im.vector.app.features.home.room.list.widget.NotifsFabMenuView
|
import im.vector.app.features.home.room.list.widget.NotifsFabMenuView
|
||||||
import im.vector.app.features.notifications.NotificationDrawerManager
|
import im.vector.app.features.notifications.NotificationDrawerManager
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.extensions.orTrue
|
||||||
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.model.RoomSummary
|
||||||
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.api.session.room.notification.RoomNotificationState
|
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
|
||||||
|
@ -66,12 +62,13 @@ data class RoomListParams(
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
class RoomListFragment @Inject constructor(
|
class RoomListFragment @Inject constructor(
|
||||||
private val roomController: RoomSummaryController,
|
private val pagedControllerFactory: RoomSummaryPagedControllerFactory,
|
||||||
val roomListViewModelFactory: RoomListViewModel.Factory,
|
val roomListViewModelFactory: RoomListViewModel.Factory,
|
||||||
private val notificationDrawerManager: NotificationDrawerManager,
|
private val notificationDrawerManager: NotificationDrawerManager,
|
||||||
private val sharedViewPool: RecyclerView.RecycledViewPool
|
private val footerController: RoomListFooterController,
|
||||||
|
private val userPreferencesProvider: UserPreferencesProvider
|
||||||
) : VectorBaseFragment<FragmentRoomListBinding>(),
|
) : VectorBaseFragment<FragmentRoomListBinding>(),
|
||||||
RoomSummaryController.Listener,
|
RoomListListener,
|
||||||
OnBackPressed,
|
OnBackPressed,
|
||||||
NotifsFabMenuView.Listener {
|
NotifsFabMenuView.Listener {
|
||||||
|
|
||||||
|
@ -85,28 +82,25 @@ class RoomListFragment @Inject constructor(
|
||||||
return FragmentRoomListBinding.inflate(inflater, container, false)
|
return FragmentRoomListBinding.inflate(inflater, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var hasUnreadRooms = false
|
data class SectionKey(
|
||||||
|
val name: String,
|
||||||
|
val isExpanded: Boolean,
|
||||||
|
val notifyOfLocalEcho: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
override fun getMenuRes() = R.menu.room_list
|
data class SectionAdapterInfo(
|
||||||
|
var section: SectionKey,
|
||||||
|
val headerHeaderAdapter: SectionHeaderAdapter,
|
||||||
|
val contentAdapter: RoomSummaryPagedController
|
||||||
|
)
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
private val adapterInfosList = mutableListOf<SectionAdapterInfo>()
|
||||||
when (item.itemId) {
|
private var concatAdapter : ConcatAdapter? = null
|
||||||
R.id.menu_home_mark_all_as_read -> {
|
|
||||||
roomListViewModel.handle(RoomListAction.MarkAllRoomsRead)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
|
||||||
menu.findItem(R.id.menu_home_mark_all_as_read).isVisible = hasUnreadRooms
|
|
||||||
super.onPrepareOptionsMenu(menu)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
views.stateView.contentView = views.roomListView
|
||||||
|
views.stateView.state = StateView.State.Loading
|
||||||
setupCreateRoomButton()
|
setupCreateRoomButton()
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
|
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
|
||||||
|
@ -125,6 +119,40 @@ class RoomListFragment @Inject constructor(
|
||||||
.observe()
|
.observe()
|
||||||
.subscribe { handleQuickActions(it) }
|
.subscribe { handleQuickActions(it) }
|
||||||
.disposeOnDestroyView()
|
.disposeOnDestroyView()
|
||||||
|
|
||||||
|
roomListViewModel.selectSubscribe(viewLifecycleOwner, RoomListViewState::roomMembershipChanges) { ms ->
|
||||||
|
// it's for invites local echo
|
||||||
|
adapterInfosList.filter { it.section.notifyOfLocalEcho }
|
||||||
|
.onEach {
|
||||||
|
it.contentAdapter.roomChangeMembershipStates = ms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshCollapseStates() {
|
||||||
|
var contentInsertIndex = 1
|
||||||
|
roomListViewModel.sections.forEachIndexed { index, roomsSection ->
|
||||||
|
val actualBlock = adapterInfosList[index]
|
||||||
|
val isRoomSectionExpanded = roomsSection.isExpanded.value.orTrue()
|
||||||
|
if (actualBlock.section.isExpanded && !isRoomSectionExpanded) {
|
||||||
|
// we have to remove the content adapter
|
||||||
|
concatAdapter?.removeAdapter(actualBlock.contentAdapter.adapter)
|
||||||
|
} else if (!actualBlock.section.isExpanded && isRoomSectionExpanded) {
|
||||||
|
// we must add it back!
|
||||||
|
concatAdapter?.addAdapter(contentInsertIndex, actualBlock.contentAdapter.adapter)
|
||||||
|
}
|
||||||
|
contentInsertIndex = if (isRoomSectionExpanded) {
|
||||||
|
contentInsertIndex + 2
|
||||||
|
} else {
|
||||||
|
contentInsertIndex + 1
|
||||||
|
}
|
||||||
|
actualBlock.section = actualBlock.section.copy(
|
||||||
|
isExpanded = isRoomSectionExpanded
|
||||||
|
)
|
||||||
|
actualBlock.headerHeaderAdapter.updateSection(
|
||||||
|
actualBlock.headerHeaderAdapter.roomsSectionData.copy(isExpanded = isRoomSectionExpanded)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showFailure(throwable: Throwable) {
|
override fun showFailure(throwable: Throwable) {
|
||||||
|
@ -132,12 +160,15 @@ class RoomListFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
roomController.removeModelBuildListener(modelBuildListener)
|
adapterInfosList.onEach { it.contentAdapter.removeModelBuildListener(modelBuildListener) }
|
||||||
|
adapterInfosList.clear()
|
||||||
modelBuildListener = null
|
modelBuildListener = null
|
||||||
views.roomListView.cleanup()
|
views.roomListView.cleanup()
|
||||||
roomController.listener = null
|
footerController.listener = null
|
||||||
|
// TODO Cleanup listener on the ConcatAdapter's adapters?
|
||||||
stateRestorer.clear()
|
stateRestorer.clear()
|
||||||
views.createChatFabMenu.listener = null
|
views.createChatFabMenu.listener = null
|
||||||
|
concatAdapter = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,13 +235,58 @@ class RoomListFragment @Inject constructor(
|
||||||
stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
|
stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
|
||||||
views.roomListView.layoutManager = layoutManager
|
views.roomListView.layoutManager = layoutManager
|
||||||
views.roomListView.itemAnimator = RoomListAnimator()
|
views.roomListView.itemAnimator = RoomListAnimator()
|
||||||
views.roomListView.setRecycledViewPool(sharedViewPool)
|
|
||||||
layoutManager.recycleChildrenOnDetach = true
|
layoutManager.recycleChildrenOnDetach = true
|
||||||
roomController.listener = this
|
|
||||||
modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) }
|
modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) }
|
||||||
roomController.addModelBuildListener(modelBuildListener)
|
|
||||||
views.roomListView.adapter = roomController.adapter
|
val concatAdapter = ConcatAdapter()
|
||||||
views.stateView.contentView = views.roomListView
|
|
||||||
|
roomListViewModel.sections.forEach { section ->
|
||||||
|
val sectionAdapter = SectionHeaderAdapter {
|
||||||
|
roomListViewModel.handle(RoomListAction.ToggleSection(section))
|
||||||
|
}.also {
|
||||||
|
it.updateSection(SectionHeaderAdapter.RoomsSectionData(section.sectionName))
|
||||||
|
}
|
||||||
|
|
||||||
|
val contentAdapter = pagedControllerFactory.createRoomSummaryPagedController()
|
||||||
|
.also { controller ->
|
||||||
|
section.livePages.observe(viewLifecycleOwner) { pl ->
|
||||||
|
controller.submitList(pl)
|
||||||
|
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(isHidden = pl.isEmpty()))
|
||||||
|
checkEmptyState()
|
||||||
|
}
|
||||||
|
section.notificationCount.observe(viewLifecycleOwner) { counts ->
|
||||||
|
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
|
||||||
|
notificationCount = counts.totalCount,
|
||||||
|
isHighlighted = counts.isHighlight
|
||||||
|
))
|
||||||
|
}
|
||||||
|
section.isExpanded.observe(viewLifecycleOwner) { _ ->
|
||||||
|
refreshCollapseStates()
|
||||||
|
}
|
||||||
|
controller.listener = this
|
||||||
|
}
|
||||||
|
adapterInfosList.add(
|
||||||
|
SectionAdapterInfo(
|
||||||
|
SectionKey(
|
||||||
|
name = section.sectionName,
|
||||||
|
isExpanded = section.isExpanded.value.orTrue(),
|
||||||
|
notifyOfLocalEcho = section.notifyOfLocalEcho
|
||||||
|
),
|
||||||
|
sectionAdapter,
|
||||||
|
contentAdapter
|
||||||
|
)
|
||||||
|
)
|
||||||
|
concatAdapter.addAdapter(sectionAdapter)
|
||||||
|
concatAdapter.addAdapter(contentAdapter.adapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the footer controller
|
||||||
|
footerController.listener = this
|
||||||
|
concatAdapter.addAdapter(footerController.adapter)
|
||||||
|
|
||||||
|
this.concatAdapter = concatAdapter
|
||||||
|
views.roomListView.adapter = concatAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
private val showFabRunnable = Runnable {
|
private val showFabRunnable = Runnable {
|
||||||
|
@ -278,89 +354,41 @@ class RoomListFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun invalidate() = withState(roomListViewModel) { state ->
|
override fun invalidate() = withState(roomListViewModel) { state ->
|
||||||
when (state.asyncFilteredRooms) {
|
footerController.setData(state)
|
||||||
is Incomplete -> renderLoading()
|
|
||||||
is Success -> renderSuccess(state)
|
|
||||||
is Fail -> renderFailure(state.asyncFilteredRooms.error)
|
|
||||||
}
|
|
||||||
roomController.update(state)
|
|
||||||
// Mark all as read menu
|
|
||||||
when (roomListParams.displayMode) {
|
|
||||||
RoomListDisplayMode.NOTIFICATIONS,
|
|
||||||
RoomListDisplayMode.PEOPLE,
|
|
||||||
RoomListDisplayMode.ROOMS -> {
|
|
||||||
val newValue = state.hasUnread
|
|
||||||
if (hasUnreadRooms != newValue) {
|
|
||||||
hasUnreadRooms = newValue
|
|
||||||
invalidateOptionsMenu()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderSuccess(state: RoomListViewState) {
|
private fun checkEmptyState() {
|
||||||
val allRooms = state.asyncRooms()
|
val hasNoRoom = adapterInfosList.all { it.headerHeaderAdapter.roomsSectionData.isHidden }
|
||||||
val filteredRooms = state.asyncFilteredRooms()
|
if (hasNoRoom) {
|
||||||
if (filteredRooms.isNullOrEmpty()) {
|
val emptyState = when (roomListParams.displayMode) {
|
||||||
renderEmptyState(allRooms)
|
RoomListDisplayMode.NOTIFICATIONS -> {
|
||||||
} else {
|
|
||||||
views.stateView.state = StateView.State.Content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun renderEmptyState(allRooms: List<RoomSummary>?) {
|
|
||||||
val hasNoRoom = allRooms
|
|
||||||
?.filter {
|
|
||||||
it.membership == Membership.JOIN || it.membership == Membership.INVITE
|
|
||||||
}
|
|
||||||
.isNullOrEmpty()
|
|
||||||
val emptyState = when (roomListParams.displayMode) {
|
|
||||||
RoomListDisplayMode.NOTIFICATIONS -> {
|
|
||||||
if (hasNoRoom) {
|
|
||||||
StateView.State.Empty(
|
|
||||||
title = getString(R.string.room_list_catchup_welcome_title),
|
|
||||||
image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_home_bottom_catchup),
|
|
||||||
message = getString(R.string.room_list_catchup_welcome_body)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
StateView.State.Empty(
|
StateView.State.Empty(
|
||||||
title = getString(R.string.room_list_catchup_empty_title),
|
title = getString(R.string.room_list_catchup_empty_title),
|
||||||
image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_noun_party_popper),
|
image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_noun_party_popper),
|
||||||
message = getString(R.string.room_list_catchup_empty_body))
|
message = getString(R.string.room_list_catchup_empty_body))
|
||||||
}
|
}
|
||||||
|
RoomListDisplayMode.PEOPLE ->
|
||||||
|
StateView.State.Empty(
|
||||||
|
title = getString(R.string.room_list_people_empty_title),
|
||||||
|
image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_dm),
|
||||||
|
isBigImage = true,
|
||||||
|
message = getString(R.string.room_list_people_empty_body)
|
||||||
|
)
|
||||||
|
RoomListDisplayMode.ROOMS ->
|
||||||
|
StateView.State.Empty(
|
||||||
|
title = getString(R.string.room_list_rooms_empty_title),
|
||||||
|
image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_room),
|
||||||
|
isBigImage = true,
|
||||||
|
message = getString(R.string.room_list_rooms_empty_body)
|
||||||
|
)
|
||||||
|
else ->
|
||||||
|
// Always display the content in this mode, because if the footer
|
||||||
|
StateView.State.Content
|
||||||
}
|
}
|
||||||
RoomListDisplayMode.PEOPLE ->
|
views.stateView.state = emptyState
|
||||||
StateView.State.Empty(
|
} else {
|
||||||
title = getString(R.string.room_list_people_empty_title),
|
views.stateView.state = StateView.State.Content
|
||||||
image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_dm),
|
|
||||||
isBigImage = true,
|
|
||||||
message = getString(R.string.room_list_people_empty_body)
|
|
||||||
)
|
|
||||||
RoomListDisplayMode.ROOMS ->
|
|
||||||
StateView.State.Empty(
|
|
||||||
title = getString(R.string.room_list_rooms_empty_title),
|
|
||||||
image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_room),
|
|
||||||
isBigImage = true,
|
|
||||||
message = getString(R.string.room_list_rooms_empty_body)
|
|
||||||
)
|
|
||||||
else ->
|
|
||||||
// Always display the content in this mode, because if the footer
|
|
||||||
StateView.State.Content
|
|
||||||
}
|
}
|
||||||
views.stateView.state = emptyState
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun renderLoading() {
|
|
||||||
views.stateView.state = StateView.State.Loading
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun renderFailure(error: Throwable) {
|
|
||||||
val message = when (error) {
|
|
||||||
is Failure.NetworkConnection -> getString(R.string.network_error_please_check_and_retry)
|
|
||||||
else -> getString(R.string.unknown_error)
|
|
||||||
}
|
|
||||||
views.stateView.state = StateView.State.Error(message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed(toolbarButton: Boolean): Boolean {
|
override fun onBackPressed(toolbarButton: Boolean): Boolean {
|
||||||
|
@ -377,7 +405,11 @@ class RoomListFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRoomLongClicked(room: RoomSummary): Boolean {
|
override fun onRoomLongClicked(room: RoomSummary): Boolean {
|
||||||
roomController.onRoomLongClicked()
|
userPreferencesProvider.neverShowLongClickOnRoomHelpAgain()
|
||||||
|
withState(roomListViewModel) {
|
||||||
|
// refresh footer
|
||||||
|
footerController.setData(it)
|
||||||
|
}
|
||||||
RoomListQuickActionsBottomSheet
|
RoomListQuickActionsBottomSheet
|
||||||
.newInstance(room.roomId, RoomListActionsArgs.Mode.FULL)
|
.newInstance(room.roomId, RoomListActionsArgs.Mode.FULL)
|
||||||
.show(childFragmentManager, "ROOM_LIST_QUICK_ACTIONS")
|
.show(childFragmentManager, "ROOM_LIST_QUICK_ACTIONS")
|
||||||
|
@ -394,10 +426,6 @@ class RoomListFragment @Inject constructor(
|
||||||
roomListViewModel.handle(RoomListAction.RejectInvitation(room))
|
roomListViewModel.handle(RoomListAction.RejectInvitation(room))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onToggleRoomCategory(roomCategory: RoomCategory) {
|
|
||||||
roomListViewModel.handle(RoomListAction.ToggleCategory(roomCategory))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createRoom(initialName: String) {
|
override fun createRoom(initialName: String) {
|
||||||
navigator.openCreateRoom(requireActivity(), initialName)
|
navigator.openCreateRoom(requireActivity(), initialName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home.room.list
|
||||||
|
|
||||||
|
import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
|
||||||
|
interface RoomListListener : FilteredRoomFooterItem.FilteredRoomFooterItemListener {
|
||||||
|
fun onRoomClicked(room: RoomSummary)
|
||||||
|
fun onRoomLongClicked(room: RoomSummary): Boolean
|
||||||
|
fun onRejectRoomInvitation(room: RoomSummary)
|
||||||
|
fun onAcceptRoomInvitation(room: RoomSummary)
|
||||||
|
}
|
|
@ -16,37 +16,61 @@
|
||||||
|
|
||||||
package im.vector.app.features.home.room.list
|
package im.vector.app.features.home.room.list
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.airbnb.mvrx.FragmentViewModelContext
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.exhaustive
|
import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.core.utils.DataSource
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.features.home.RoomListDisplayMode
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
|
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
||||||
|
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||||
|
import org.matrix.android.sdk.api.session.room.UpdatableFilterLivePageResult
|
||||||
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
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.model.tag.RoomTag
|
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
||||||
|
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||||
import org.matrix.android.sdk.api.session.room.state.isPublic
|
import org.matrix.android.sdk.api.session.room.state.isPublic
|
||||||
|
import org.matrix.android.sdk.rx.asObservable
|
||||||
import org.matrix.android.sdk.rx.rx
|
import org.matrix.android.sdk.rx.rx
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.lang.Exception
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
class RoomListViewModel @Inject constructor(
|
||||||
private val session: Session,
|
initialState: RoomListViewState,
|
||||||
private val roomSummariesSource: DataSource<List<RoomSummary>>)
|
private val session: Session,
|
||||||
: VectorViewModel<RoomListViewState, RoomListAction, RoomListViewEvents>(initialState) {
|
private val stringProvider: StringProvider
|
||||||
|
) : VectorViewModel<RoomListViewState, RoomListAction, RoomListViewEvents>(initialState) {
|
||||||
|
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(initialState: RoomListViewState): RoomListViewModel
|
fun create(initialState: RoomListViewState): RoomListViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var updatableQuery: UpdatableFilterLivePageResult? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
observeMembershipChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeMembershipChanges() {
|
||||||
|
session.rx()
|
||||||
|
.liveRoomChangeMembershipState()
|
||||||
|
.subscribe {
|
||||||
|
setState { copy(roomMembershipChanges = it) }
|
||||||
|
}
|
||||||
|
.disposeOnClear()
|
||||||
|
}
|
||||||
|
|
||||||
companion object : MvRxViewModelFactory<RoomListViewModel, RoomListViewState> {
|
companion object : MvRxViewModelFactory<RoomListViewModel, RoomListViewState> {
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@ -56,28 +80,136 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val displayMode = initialState.displayMode
|
val sections: List<RoomsSection> by lazy {
|
||||||
private val roomListDisplayModeFilter = RoomListDisplayModeFilter(displayMode)
|
val sections = mutableListOf<RoomsSection>()
|
||||||
|
if (initialState.displayMode == RoomListDisplayMode.PEOPLE) {
|
||||||
|
addSection(sections, R.string.invitations_header, true) {
|
||||||
|
it.memberships = listOf(Membership.INVITE)
|
||||||
|
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
addSection(sections, R.string.bottom_action_favourites) {
|
||||||
observeRoomSummaries()
|
it.memberships = listOf(Membership.JOIN)
|
||||||
observeMembershipChanges()
|
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||||
|
it.roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
addSection(sections, R.string.bottom_action_people_x) {
|
||||||
|
it.memberships = listOf(Membership.JOIN)
|
||||||
|
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||||
|
}
|
||||||
|
} else if (initialState.displayMode == RoomListDisplayMode.ROOMS) {
|
||||||
|
addSection(sections, R.string.invitations_header, true) {
|
||||||
|
it.memberships = listOf(Membership.INVITE)
|
||||||
|
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||||
|
}
|
||||||
|
|
||||||
|
addSection(sections, R.string.bottom_action_favourites) {
|
||||||
|
it.memberships = listOf(Membership.JOIN)
|
||||||
|
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||||
|
it.roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
addSection(sections, R.string.bottom_action_rooms) {
|
||||||
|
it.memberships = listOf(Membership.JOIN)
|
||||||
|
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||||
|
it.roomTagQueryFilter = RoomTagQueryFilter(false, false, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
addSection(sections, R.string.low_priority_header) {
|
||||||
|
it.memberships = listOf(Membership.JOIN)
|
||||||
|
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||||
|
it.roomTagQueryFilter = RoomTagQueryFilter(null, true, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
addSection(sections, R.string.system_alerts_header) {
|
||||||
|
it.memberships = listOf(Membership.JOIN)
|
||||||
|
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||||
|
it.roomTagQueryFilter = RoomTagQueryFilter(null, null, true)
|
||||||
|
}
|
||||||
|
} else if (initialState.displayMode == RoomListDisplayMode.FILTERED) {
|
||||||
|
withQueryParams(
|
||||||
|
{
|
||||||
|
it.memberships = Membership.activeMemberships()
|
||||||
|
},
|
||||||
|
{ qpm ->
|
||||||
|
val name = stringProvider.getString(R.string.bottom_action_rooms)
|
||||||
|
session.getFilteredPagedRoomSummariesLive(qpm)
|
||||||
|
.let { updatableFilterLivePageResult ->
|
||||||
|
updatableQuery = updatableFilterLivePageResult
|
||||||
|
sections.add(RoomsSection(name, updatableFilterLivePageResult.livePagedList))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else if (initialState.displayMode == RoomListDisplayMode.NOTIFICATIONS) {
|
||||||
|
addSection(sections, R.string.invitations_header, true) {
|
||||||
|
it.memberships = listOf(Membership.INVITE)
|
||||||
|
it.roomCategoryFilter = RoomCategoryFilter.ALL
|
||||||
|
}
|
||||||
|
|
||||||
|
addSection(sections, R.string.bottom_action_rooms, true) {
|
||||||
|
it.memberships = listOf(Membership.JOIN)
|
||||||
|
it.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sections
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handle(action: RoomListAction) {
|
override fun handle(action: RoomListAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is RoomListAction.SelectRoom -> handleSelectRoom(action)
|
is RoomListAction.SelectRoom -> handleSelectRoom(action)
|
||||||
is RoomListAction.ToggleCategory -> handleToggleCategory(action)
|
|
||||||
is RoomListAction.AcceptInvitation -> handleAcceptInvitation(action)
|
is RoomListAction.AcceptInvitation -> handleAcceptInvitation(action)
|
||||||
is RoomListAction.RejectInvitation -> handleRejectInvitation(action)
|
is RoomListAction.RejectInvitation -> handleRejectInvitation(action)
|
||||||
is RoomListAction.FilterWith -> handleFilter(action)
|
is RoomListAction.FilterWith -> handleFilter(action)
|
||||||
is RoomListAction.MarkAllRoomsRead -> handleMarkAllRoomsRead()
|
|
||||||
is RoomListAction.LeaveRoom -> handleLeaveRoom(action)
|
is RoomListAction.LeaveRoom -> handleLeaveRoom(action)
|
||||||
is RoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
|
is RoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
|
||||||
is RoomListAction.ToggleTag -> handleToggleTag(action)
|
is RoomListAction.ToggleTag -> handleToggleTag(action)
|
||||||
|
is RoomListAction.ToggleSection -> handleToggleSection(action.section)
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addSection(sections: MutableList<RoomsSection>,
|
||||||
|
@StringRes nameRes: Int,
|
||||||
|
notifyOfLocalEcho: Boolean = false,
|
||||||
|
query: (RoomSummaryQueryParams.Builder) -> Unit) {
|
||||||
|
withQueryParams(
|
||||||
|
{ query.invoke(it) },
|
||||||
|
{ roomQueryParams ->
|
||||||
|
|
||||||
|
val name = stringProvider.getString(nameRes)
|
||||||
|
session.getPagedRoomSummariesLive(roomQueryParams)
|
||||||
|
.let { livePagedList ->
|
||||||
|
|
||||||
|
// use it also as a source to update count
|
||||||
|
livePagedList.asObservable()
|
||||||
|
.observeOn(Schedulers.computation())
|
||||||
|
.subscribe {
|
||||||
|
sections.find { it.sectionName == name }
|
||||||
|
?.notificationCount
|
||||||
|
?.postValue(session.getNotificationCountForRooms(roomQueryParams))
|
||||||
|
}
|
||||||
|
.disposeOnClear()
|
||||||
|
|
||||||
|
sections.add(
|
||||||
|
RoomsSection(
|
||||||
|
sectionName = name,
|
||||||
|
livePages = livePagedList,
|
||||||
|
notifyOfLocalEcho = notifyOfLocalEcho
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun withQueryParams(builder: (RoomSummaryQueryParams.Builder) -> Unit, block: (RoomSummaryQueryParams) -> Unit) {
|
||||||
|
RoomSummaryQueryParams.Builder()
|
||||||
|
.apply { builder.invoke(this) }
|
||||||
|
.build()
|
||||||
|
.let { block(it) }
|
||||||
|
}
|
||||||
|
|
||||||
fun isPublicRoom(roomId: String): Boolean {
|
fun isPublicRoom(roomId: String): Boolean {
|
||||||
return session.getRoom(roomId)?.isPublic().orFalse()
|
return session.getRoom(roomId)?.isPublic().orFalse()
|
||||||
}
|
}
|
||||||
|
@ -88,8 +220,14 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||||
_viewEvents.post(RoomListViewEvents.SelectRoom(action.roomSummary))
|
_viewEvents.post(RoomListViewEvents.SelectRoom(action.roomSummary))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleToggleCategory(action: RoomListAction.ToggleCategory) = setState {
|
private fun handleToggleSection(roomSection: RoomsSection) {
|
||||||
this.toggle(action.category)
|
roomSection.isExpanded.postValue(!roomSection.isExpanded.value.orFalse())
|
||||||
|
/* TODO Cleanup if it is working
|
||||||
|
sections.find { it.sectionName == roomSection.sectionName }
|
||||||
|
?.let { section ->
|
||||||
|
section.isExpanded.postValue(!section.isExpanded.value.orFalse())
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleFilter(action: RoomListAction.FilterWith) {
|
private fun handleFilter(action: RoomListAction.FilterWith) {
|
||||||
|
@ -98,23 +236,12 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||||
roomFilter = action.filter
|
roomFilter = action.filter
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
updatableQuery?.updateQuery(
|
||||||
|
roomSummaryQueryParams {
|
||||||
private fun observeRoomSummaries() {
|
memberships = Membership.activeMemberships()
|
||||||
roomSummariesSource
|
displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE)
|
||||||
.observe()
|
|
||||||
.observeOn(Schedulers.computation())
|
|
||||||
.execute { asyncRooms ->
|
|
||||||
copy(asyncRooms = asyncRooms)
|
|
||||||
}
|
|
||||||
|
|
||||||
roomSummariesSource
|
|
||||||
.observe()
|
|
||||||
.observeOn(Schedulers.computation())
|
|
||||||
.map { buildRoomSummaries(it) }
|
|
||||||
.execute { async ->
|
|
||||||
copy(asyncFilteredRooms = async)
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleAcceptInvitation(action: RoomListAction.AcceptInvitation) = withState { state ->
|
private fun handleAcceptInvitation(action: RoomListAction.AcceptInvitation) = withState { state ->
|
||||||
|
@ -126,6 +253,19 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||||
return@withState
|
return@withState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// quick echo
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
roomMembershipChanges = roomMembershipChanges.mapValues {
|
||||||
|
if (it.key == roomId) {
|
||||||
|
ChangeMembershipState.Joining
|
||||||
|
} else {
|
||||||
|
it.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val room = session.getRoom(roomId) ?: return@withState
|
val room = session.getRoom(roomId) ?: return@withState
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
|
@ -163,15 +303,6 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleMarkAllRoomsRead() = withState { state ->
|
|
||||||
state.asyncFilteredRooms.invoke()
|
|
||||||
?.flatMap { it.value }
|
|
||||||
?.filter { it.membership == Membership.JOIN }
|
|
||||||
?.map { it.roomId }
|
|
||||||
?.toList()
|
|
||||||
?.let { session.markAllAsRead(it, NoOpMatrixCallback()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleChangeNotificationMode(action: RoomListAction.ChangeRoomNotificationState) {
|
private fun handleChangeNotificationMode(action: RoomListAction.ChangeRoomNotificationState) {
|
||||||
val room = session.getRoom(action.roomId)
|
val room = session.getRoom(action.roomId)
|
||||||
if (room != null) {
|
if (room != null) {
|
||||||
|
@ -226,46 +357,4 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||||
_viewEvents.post(value)
|
_viewEvents.post(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeMembershipChanges() {
|
|
||||||
session.rx()
|
|
||||||
.liveRoomChangeMembershipState()
|
|
||||||
.subscribe {
|
|
||||||
Timber.v("ChangeMembership states: $it")
|
|
||||||
setState { copy(roomMembershipChanges = it) }
|
|
||||||
}
|
|
||||||
.disposeOnClear()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildRoomSummaries(rooms: List<RoomSummary>): RoomSummaries {
|
|
||||||
// Set up init size on directChats and groupRooms as they are the biggest ones
|
|
||||||
val invites = ArrayList<RoomSummary>()
|
|
||||||
val favourites = ArrayList<RoomSummary>()
|
|
||||||
val directChats = ArrayList<RoomSummary>(rooms.size)
|
|
||||||
val groupRooms = ArrayList<RoomSummary>(rooms.size)
|
|
||||||
val lowPriorities = ArrayList<RoomSummary>()
|
|
||||||
val serverNotices = ArrayList<RoomSummary>()
|
|
||||||
|
|
||||||
rooms
|
|
||||||
.filter { roomListDisplayModeFilter.test(it) }
|
|
||||||
.forEach { room ->
|
|
||||||
val tags = room.tags.map { it.name }
|
|
||||||
when {
|
|
||||||
room.membership == Membership.INVITE -> invites.add(room)
|
|
||||||
tags.contains(RoomTag.ROOM_TAG_SERVER_NOTICE) -> serverNotices.add(room)
|
|
||||||
tags.contains(RoomTag.ROOM_TAG_FAVOURITE) -> favourites.add(room)
|
|
||||||
tags.contains(RoomTag.ROOM_TAG_LOW_PRIORITY) -> lowPriorities.add(room)
|
|
||||||
room.isDirect -> directChats.add(room)
|
|
||||||
else -> groupRooms.add(room)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return RoomSummaries().apply {
|
|
||||||
put(RoomCategory.INVITE, invites)
|
|
||||||
put(RoomCategory.FAVOURITE, favourites)
|
|
||||||
put(RoomCategory.DIRECT, directChats)
|
|
||||||
put(RoomCategory.GROUP, groupRooms)
|
|
||||||
put(RoomCategory.LOW_PRIORITY, lowPriorities)
|
|
||||||
put(RoomCategory.SERVER_NOTICE, serverNotices)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,20 +16,20 @@
|
||||||
|
|
||||||
package im.vector.app.features.home.room.list
|
package im.vector.app.features.home.room.list
|
||||||
|
|
||||||
import im.vector.app.features.home.HomeRoomListDataSource
|
import im.vector.app.core.resources.StringProvider
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
|
|
||||||
class RoomListViewModelFactory @Inject constructor(private val session: Provider<Session>,
|
class RoomListViewModelFactory @Inject constructor(private val session: Provider<Session>,
|
||||||
private val homeRoomListDataSource: Provider<HomeRoomListDataSource>)
|
private val stringProvider: StringProvider)
|
||||||
: RoomListViewModel.Factory {
|
: RoomListViewModel.Factory {
|
||||||
|
|
||||||
override fun create(initialState: RoomListViewState): RoomListViewModel {
|
override fun create(initialState: RoomListViewState): RoomListViewModel {
|
||||||
return RoomListViewModel(
|
return RoomListViewModel(
|
||||||
initialState,
|
initialState,
|
||||||
session.get(),
|
session.get(),
|
||||||
homeRoomListDataSource.get()
|
stringProvider
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,73 +16,15 @@
|
||||||
|
|
||||||
package im.vector.app.features.home.room.list
|
package im.vector.app.features.home.room.list
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import com.airbnb.mvrx.Async
|
|
||||||
import com.airbnb.mvrx.MvRxState
|
import com.airbnb.mvrx.MvRxState
|
||||||
import com.airbnb.mvrx.Uninitialized
|
|
||||||
import im.vector.app.R
|
|
||||||
import im.vector.app.features.home.RoomListDisplayMode
|
import im.vector.app.features.home.RoomListDisplayMode
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
|
||||||
|
|
||||||
data class RoomListViewState(
|
data class RoomListViewState(
|
||||||
val displayMode: RoomListDisplayMode,
|
val displayMode: RoomListDisplayMode,
|
||||||
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
|
|
||||||
val roomFilter: String = "",
|
val roomFilter: String = "",
|
||||||
val asyncFilteredRooms: Async<RoomSummaries> = Uninitialized,
|
val roomMembershipChanges: Map<String, ChangeMembershipState> = emptyMap()
|
||||||
val roomMembershipChanges: Map<String, ChangeMembershipState> = emptyMap(),
|
|
||||||
val isInviteExpanded: Boolean = true,
|
|
||||||
val isFavouriteRoomsExpanded: Boolean = true,
|
|
||||||
val isDirectRoomsExpanded: Boolean = true,
|
|
||||||
val isGroupRoomsExpanded: Boolean = true,
|
|
||||||
val isLowPriorityRoomsExpanded: Boolean = true,
|
|
||||||
val isServerNoticeRoomsExpanded: Boolean = true
|
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
constructor(args: RoomListParams) : this(displayMode = args.displayMode)
|
constructor(args: RoomListParams) : this(displayMode = args.displayMode)
|
||||||
|
|
||||||
fun isCategoryExpanded(roomCategory: RoomCategory): Boolean {
|
|
||||||
return when (roomCategory) {
|
|
||||||
RoomCategory.INVITE -> isInviteExpanded
|
|
||||||
RoomCategory.FAVOURITE -> isFavouriteRoomsExpanded
|
|
||||||
RoomCategory.DIRECT -> isDirectRoomsExpanded
|
|
||||||
RoomCategory.GROUP -> isGroupRoomsExpanded
|
|
||||||
RoomCategory.LOW_PRIORITY -> isLowPriorityRoomsExpanded
|
|
||||||
RoomCategory.SERVER_NOTICE -> isServerNoticeRoomsExpanded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toggle(roomCategory: RoomCategory): RoomListViewState {
|
|
||||||
return when (roomCategory) {
|
|
||||||
RoomCategory.INVITE -> copy(isInviteExpanded = !isInviteExpanded)
|
|
||||||
RoomCategory.FAVOURITE -> copy(isFavouriteRoomsExpanded = !isFavouriteRoomsExpanded)
|
|
||||||
RoomCategory.DIRECT -> copy(isDirectRoomsExpanded = !isDirectRoomsExpanded)
|
|
||||||
RoomCategory.GROUP -> copy(isGroupRoomsExpanded = !isGroupRoomsExpanded)
|
|
||||||
RoomCategory.LOW_PRIORITY -> copy(isLowPriorityRoomsExpanded = !isLowPriorityRoomsExpanded)
|
|
||||||
RoomCategory.SERVER_NOTICE -> copy(isServerNoticeRoomsExpanded = !isServerNoticeRoomsExpanded)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val hasUnread: Boolean
|
|
||||||
get() = asyncFilteredRooms.invoke()
|
|
||||||
?.flatMap { it.value }
|
|
||||||
?.filter { it.membership == Membership.JOIN }
|
|
||||||
?.any { it.hasUnreadMessages }
|
|
||||||
?: false
|
|
||||||
}
|
|
||||||
|
|
||||||
typealias RoomSummaries = LinkedHashMap<RoomCategory, List<RoomSummary>>
|
|
||||||
|
|
||||||
enum class RoomCategory(@StringRes val titleRes: Int) {
|
|
||||||
INVITE(R.string.invitations_header),
|
|
||||||
FAVOURITE(R.string.bottom_action_favourites),
|
|
||||||
DIRECT(R.string.bottom_action_people_x),
|
|
||||||
GROUP(R.string.bottom_action_rooms),
|
|
||||||
LOW_PRIORITY(R.string.low_priority_header),
|
|
||||||
SERVER_NOTICE(R.string.system_alerts_header)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun RoomSummaries?.isNullOrEmpty(): Boolean {
|
|
||||||
return this == null || this.values.flatten().isEmpty()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,170 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019 New Vector Ltd
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package im.vector.app.features.home.room.list
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import com.airbnb.epoxy.EpoxyController
|
|
||||||
import im.vector.app.R
|
|
||||||
import im.vector.app.core.epoxy.helpFooterItem
|
|
||||||
import im.vector.app.core.resources.StringProvider
|
|
||||||
import im.vector.app.core.resources.UserPreferencesProvider
|
|
||||||
import im.vector.app.features.home.RoomListDisplayMode
|
|
||||||
import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem
|
|
||||||
import im.vector.app.features.home.room.filtered.filteredRoomFooterItem
|
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class RoomSummaryController @Inject constructor(private val stringProvider: StringProvider,
|
|
||||||
private val roomSummaryItemFactory: RoomSummaryItemFactory,
|
|
||||||
private val roomListNameFilter: RoomListNameFilter,
|
|
||||||
private val userPreferencesProvider: UserPreferencesProvider
|
|
||||||
) : EpoxyController() {
|
|
||||||
|
|
||||||
var listener: Listener? = null
|
|
||||||
|
|
||||||
private var viewState: RoomListViewState? = null
|
|
||||||
|
|
||||||
init {
|
|
||||||
// We are requesting a model build directly as the first build of epoxy is on the main thread.
|
|
||||||
// It avoids to build the whole list of rooms on the main thread.
|
|
||||||
requestModelBuild()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun update(viewState: RoomListViewState) {
|
|
||||||
this.viewState = viewState
|
|
||||||
requestModelBuild()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onRoomLongClicked() {
|
|
||||||
userPreferencesProvider.neverShowLongClickOnRoomHelpAgain()
|
|
||||||
requestModelBuild()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun buildModels() {
|
|
||||||
val nonNullViewState = viewState ?: return
|
|
||||||
when (nonNullViewState.displayMode) {
|
|
||||||
RoomListDisplayMode.FILTERED -> buildFilteredRooms(nonNullViewState)
|
|
||||||
else -> buildRooms(nonNullViewState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildFilteredRooms(viewState: RoomListViewState) {
|
|
||||||
val summaries = viewState.asyncRooms() ?: return
|
|
||||||
|
|
||||||
roomListNameFilter.filter = viewState.roomFilter
|
|
||||||
|
|
||||||
val filteredSummaries = summaries
|
|
||||||
.filter { it.membership == Membership.JOIN && roomListNameFilter.test(it) }
|
|
||||||
|
|
||||||
buildRoomModels(filteredSummaries,
|
|
||||||
viewState.roomMembershipChanges,
|
|
||||||
emptySet())
|
|
||||||
|
|
||||||
addFilterFooter(viewState)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildRooms(viewState: RoomListViewState) {
|
|
||||||
var showHelp = false
|
|
||||||
val roomSummaries = viewState.asyncFilteredRooms()
|
|
||||||
roomSummaries?.forEach { (category, summaries) ->
|
|
||||||
if (summaries.isEmpty()) {
|
|
||||||
return@forEach
|
|
||||||
} else {
|
|
||||||
val isExpanded = viewState.isCategoryExpanded(category)
|
|
||||||
buildRoomCategory(viewState, summaries, category.titleRes, viewState.isCategoryExpanded(category)) {
|
|
||||||
listener?.onToggleRoomCategory(category)
|
|
||||||
}
|
|
||||||
if (isExpanded) {
|
|
||||||
buildRoomModels(summaries,
|
|
||||||
viewState.roomMembershipChanges,
|
|
||||||
emptySet())
|
|
||||||
// Never set showHelp to true for invitation
|
|
||||||
if (category != RoomCategory.INVITE) {
|
|
||||||
showHelp = userPreferencesProvider.shouldShowLongClickOnRoomHelp()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showHelp) {
|
|
||||||
buildLongClickHelp()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildLongClickHelp() {
|
|
||||||
helpFooterItem {
|
|
||||||
id("long_click_help")
|
|
||||||
text(stringProvider.getString(R.string.help_long_click_on_room_for_more_options))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addFilterFooter(viewState: RoomListViewState) {
|
|
||||||
filteredRoomFooterItem {
|
|
||||||
id("filter_footer")
|
|
||||||
listener(listener)
|
|
||||||
currentFilter(viewState.roomFilter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildRoomCategory(viewState: RoomListViewState,
|
|
||||||
summaries: List<RoomSummary>,
|
|
||||||
@StringRes titleRes: Int,
|
|
||||||
isExpanded: Boolean,
|
|
||||||
mutateExpandedState: () -> Unit) {
|
|
||||||
// TODO should add some business logic later
|
|
||||||
val unreadCount = if (summaries.isEmpty()) {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
summaries.map { it.notificationCount }.sumBy { i -> i }
|
|
||||||
}
|
|
||||||
val showHighlighted = summaries.any { it.highlightCount > 0 }
|
|
||||||
roomCategoryItem {
|
|
||||||
id(titleRes)
|
|
||||||
title(stringProvider.getString(titleRes))
|
|
||||||
expanded(isExpanded)
|
|
||||||
unreadNotificationCount(unreadCount)
|
|
||||||
showHighlighted(showHighlighted)
|
|
||||||
listener {
|
|
||||||
mutateExpandedState()
|
|
||||||
update(viewState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildRoomModels(summaries: List<RoomSummary>,
|
|
||||||
roomChangedMembershipStates: Map<String, ChangeMembershipState>,
|
|
||||||
selectedRoomIds: Set<String>) {
|
|
||||||
summaries.forEach { roomSummary ->
|
|
||||||
roomSummaryItemFactory
|
|
||||||
.create(roomSummary,
|
|
||||||
roomChangedMembershipStates,
|
|
||||||
selectedRoomIds,
|
|
||||||
listener)
|
|
||||||
.addTo(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Listener : FilteredRoomFooterItem.FilteredRoomFooterItemListener {
|
|
||||||
fun onToggleRoomCategory(roomCategory: RoomCategory)
|
|
||||||
fun onRoomClicked(room: RoomSummary)
|
|
||||||
fun onRoomLongClicked(room: RoomSummary): Boolean
|
|
||||||
fun onRejectRoomInvitation(room: RoomSummary)
|
|
||||||
fun onAcceptRoomInvitation(room: RoomSummary)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -40,7 +40,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
||||||
fun create(roomSummary: RoomSummary,
|
fun create(roomSummary: RoomSummary,
|
||||||
roomChangeMembershipStates: Map<String, ChangeMembershipState>,
|
roomChangeMembershipStates: Map<String, ChangeMembershipState>,
|
||||||
selectedRoomIds: Set<String>,
|
selectedRoomIds: Set<String>,
|
||||||
listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
|
listener: RoomListListener?): VectorEpoxyModel<*> {
|
||||||
return when (roomSummary.membership) {
|
return when (roomSummary.membership) {
|
||||||
Membership.INVITE -> {
|
Membership.INVITE -> {
|
||||||
val changeMembershipState = roomChangeMembershipStates[roomSummary.roomId] ?: ChangeMembershipState.Unknown
|
val changeMembershipState = roomChangeMembershipStates[roomSummary.roomId] ?: ChangeMembershipState.Unknown
|
||||||
|
@ -52,7 +52,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
||||||
|
|
||||||
private fun createInvitationItem(roomSummary: RoomSummary,
|
private fun createInvitationItem(roomSummary: RoomSummary,
|
||||||
changeMembershipState: ChangeMembershipState,
|
changeMembershipState: ChangeMembershipState,
|
||||||
listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
|
listener: RoomListListener?): VectorEpoxyModel<*> {
|
||||||
val secondLine = if (roomSummary.isDirect) {
|
val secondLine = if (roomSummary.isDirect) {
|
||||||
roomSummary.inviterId
|
roomSummary.inviterId
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home.room.list
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.EpoxyModel
|
||||||
|
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
||||||
|
import im.vector.app.core.utils.createUIHandler
|
||||||
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomSummaryPagedControllerFactory @Inject constructor(
|
||||||
|
private val roomSummaryItemFactory: RoomSummaryItemFactory
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun createRoomSummaryPagedController(): RoomSummaryPagedController {
|
||||||
|
return RoomSummaryPagedController(roomSummaryItemFactory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoomSummaryPagedController(
|
||||||
|
private val roomSummaryItemFactory: RoomSummaryItemFactory
|
||||||
|
) : PagedListEpoxyController<RoomSummary>(
|
||||||
|
// Important it must match the PageList builder notify Looper
|
||||||
|
modelBuildingHandler = createUIHandler()
|
||||||
|
) {
|
||||||
|
|
||||||
|
var listener: RoomListListener? = null
|
||||||
|
|
||||||
|
var roomChangeMembershipStates: Map<String, ChangeMembershipState>? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
// ideally we could search for visible models and update only those
|
||||||
|
requestForcedModelBuild()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
|
||||||
|
// for place holder if enabled
|
||||||
|
item ?: return roomSummaryItemFactory.createRoomItem(
|
||||||
|
roomSummary = RoomSummary(
|
||||||
|
roomId = "null_item_pos_$currentPosition",
|
||||||
|
name = "",
|
||||||
|
encryptionEventTs = null,
|
||||||
|
isEncrypted = false,
|
||||||
|
typingUsers = emptyList()
|
||||||
|
),
|
||||||
|
selectedRoomIds = emptySet(),
|
||||||
|
onClick = null,
|
||||||
|
onLongClick = null
|
||||||
|
)
|
||||||
|
|
||||||
|
return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), listener)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home.room.list
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.paging.PagedList
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||||
|
|
||||||
|
data class RoomsSection(
|
||||||
|
val sectionName: String,
|
||||||
|
val livePages: LiveData<PagedList<RoomSummary>>,
|
||||||
|
val isExpanded: MutableLiveData<Boolean> = MutableLiveData(true),
|
||||||
|
val notificationCount: MutableLiveData<RoomAggregateNotificationCount> = MutableLiveData(RoomAggregateNotificationCount(0, 0)),
|
||||||
|
val notifyOfLocalEcho: Boolean = false
|
||||||
|
)
|
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home.room.list
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.graphics.drawable.DrawableCompat
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.utils.DebouncedClickListener
|
||||||
|
import im.vector.app.databinding.ItemRoomCategoryBinding
|
||||||
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
|
|
||||||
|
class SectionHeaderAdapter constructor(
|
||||||
|
private val onClickAction: (() -> Unit)
|
||||||
|
) : RecyclerView.Adapter<SectionHeaderAdapter.VH>() {
|
||||||
|
|
||||||
|
data class RoomsSectionData(
|
||||||
|
val name: String,
|
||||||
|
val isExpanded: Boolean = true,
|
||||||
|
val notificationCount: Int = 0,
|
||||||
|
val isHighlighted: Boolean = false,
|
||||||
|
val isHidden: Boolean = true
|
||||||
|
)
|
||||||
|
|
||||||
|
lateinit var roomsSectionData: RoomsSectionData
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun updateSection(newRoomsSectionData: RoomsSectionData) {
|
||||||
|
if (!::roomsSectionData.isInitialized || newRoomsSectionData != roomsSectionData) {
|
||||||
|
roomsSectionData = newRoomsSectionData
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
setHasStableIds(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemId(position: Int) = roomsSectionData.hashCode().toLong()
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int) = R.layout.item_room_category
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
|
||||||
|
return VH.create(parent, this.onClickAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: VH, position: Int) {
|
||||||
|
holder.bind(roomsSectionData)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = if (roomsSectionData.isHidden) 0 else 1
|
||||||
|
|
||||||
|
class VH constructor(
|
||||||
|
private val binding: ItemRoomCategoryBinding,
|
||||||
|
onClickAction: (() -> Unit)
|
||||||
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
binding.root.setOnClickListener(DebouncedClickListener({
|
||||||
|
onClickAction.invoke()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(roomsSectionData: RoomsSectionData) {
|
||||||
|
binding.roomCategoryTitleView.text = roomsSectionData.name
|
||||||
|
val tintColor = ThemeUtils.getColor(binding.root.context, R.attr.riotx_text_secondary)
|
||||||
|
val expandedArrowDrawableRes = if (roomsSectionData.isExpanded) R.drawable.ic_expand_more_white else R.drawable.ic_expand_less_white
|
||||||
|
val expandedArrowDrawable = ContextCompat.getDrawable(binding.root.context, expandedArrowDrawableRes)?.also {
|
||||||
|
DrawableCompat.setTint(it, tintColor)
|
||||||
|
}
|
||||||
|
binding.roomCategoryUnreadCounterBadgeView.render(UnreadCounterBadgeView.State(roomsSectionData.notificationCount, roomsSectionData.isHighlighted))
|
||||||
|
binding.roomCategoryTitleView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(parent: ViewGroup, onClickAction: () -> Unit): VH {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_room_category, parent, false)
|
||||||
|
val binding = ItemRoomCategoryBinding.bind(view)
|
||||||
|
return VH(binding, onClickAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue