commit
e73923dca3
@ -2,7 +2,7 @@ Changes in RiotX 0.10.0 (2019-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
-
|
||||
- Breadcrumbs: switch from one room to another quickly (#571)
|
||||
|
||||
Improvements 🙌:
|
||||
-
|
||||
|
@ -17,7 +17,7 @@ Client request the sign-up flows, once the homeserver is chosen by the user and
|
||||
}
|
||||
```
|
||||
|
||||
We get the flows with a 401, which also means the the registration is possible on this homeserver.
|
||||
We get the flows with a 401, which also means that the registration is possible on this homeserver.
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -38,6 +38,10 @@ class RxSession(private val session: Session) {
|
||||
return session.liveGroupSummaries().asObservable()
|
||||
}
|
||||
|
||||
fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
|
||||
return session.liveBreadcrumbs().asObservable()
|
||||
}
|
||||
|
||||
fun liveSyncState(): Observable<SyncState> {
|
||||
return session.syncState().asObservable()
|
||||
}
|
||||
|
@ -54,6 +54,18 @@ interface RoomService {
|
||||
*/
|
||||
fun liveRoomSummaries(): LiveData<List<RoomSummary>>
|
||||
|
||||
/**
|
||||
* Get a live list of Breadcrumbs
|
||||
* @return the [LiveData] of [RoomSummary]
|
||||
*/
|
||||
fun liveBreadcrumbs(): LiveData<List<RoomSummary>>
|
||||
|
||||
/**
|
||||
* Inform the Matrix SDK that a room is displayed.
|
||||
* The SDK will update the breadcrumbs in the user account data
|
||||
*/
|
||||
fun onRoomDisplayed(roomId: String): Cancelable
|
||||
|
||||
/**
|
||||
* Mark all rooms as read
|
||||
*/
|
||||
|
@ -19,16 +19,14 @@ package im.vector.matrix.android.internal.crypto.store.db.query
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoRoomEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoRoomEntityFields
|
||||
import io.realm.Realm
|
||||
import io.realm.kotlin.createObject
|
||||
import io.realm.kotlin.where
|
||||
|
||||
/**
|
||||
* Get or create a room
|
||||
*/
|
||||
internal fun CryptoRoomEntity.Companion.getOrCreate(realm: Realm, roomId: String): CryptoRoomEntity {
|
||||
return getById(realm, roomId)
|
||||
?: let {
|
||||
realm.createObject(CryptoRoomEntity::class.java, roomId)
|
||||
}
|
||||
return getById(realm, roomId) ?: realm.createObject(roomId)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20,18 +20,20 @@ import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntityFields
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.createPrimaryKey
|
||||
import io.realm.Realm
|
||||
import io.realm.kotlin.createObject
|
||||
import io.realm.kotlin.where
|
||||
|
||||
/**
|
||||
* Get or create a device info
|
||||
*/
|
||||
internal fun DeviceInfoEntity.Companion.getOrCreate(realm: Realm, userId: String, deviceId: String): DeviceInfoEntity {
|
||||
val key = DeviceInfoEntity.createPrimaryKey(userId, deviceId)
|
||||
|
||||
return realm.where<DeviceInfoEntity>()
|
||||
.equalTo(DeviceInfoEntityFields.PRIMARY_KEY, DeviceInfoEntity.createPrimaryKey(userId, deviceId))
|
||||
.equalTo(DeviceInfoEntityFields.PRIMARY_KEY, key)
|
||||
.findFirst()
|
||||
?: let {
|
||||
realm.createObject(DeviceInfoEntity::class.java, DeviceInfoEntity.createPrimaryKey(userId, deviceId)).apply {
|
||||
this.deviceId = deviceId
|
||||
}
|
||||
}
|
||||
?: realm.createObject<DeviceInfoEntity>(key)
|
||||
.apply {
|
||||
this.deviceId = deviceId
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.store.db.query
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.UserEntity
|
||||
import im.vector.matrix.android.internal.crypto.store.db.model.UserEntityFields
|
||||
import io.realm.Realm
|
||||
import io.realm.kotlin.createObject
|
||||
import io.realm.kotlin.where
|
||||
|
||||
/**
|
||||
@ -28,9 +29,7 @@ internal fun UserEntity.Companion.getOrCreate(realm: Realm, userId: String): Use
|
||||
return realm.where<UserEntity>()
|
||||
.equalTo(UserEntityFields.USER_ID, userId)
|
||||
.findFirst()
|
||||
?: let {
|
||||
realm.createObject(UserEntity::class.java, userId)
|
||||
}
|
||||
?: realm.createObject(userId)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.matrix.android.internal.database.model
|
||||
|
||||
import io.realm.RealmList
|
||||
import io.realm.RealmObject
|
||||
|
||||
internal open class BreadcrumbsEntity(
|
||||
var recentRoomIds: RealmList<String> = RealmList()
|
||||
) : RealmObject() {
|
||||
|
||||
companion object
|
||||
}
|
@ -38,7 +38,8 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
|
||||
var readMarkerId: String? = null,
|
||||
var hasUnreadMessages: Boolean = false,
|
||||
var tags: RealmList<RoomTagEntity> = RealmList(),
|
||||
var userDrafts: UserDraftsEntity? = null
|
||||
var userDrafts: UserDraftsEntity? = null,
|
||||
var breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS
|
||||
) : RealmObject() {
|
||||
|
||||
private var membershipStr: String = Membership.NONE.name
|
||||
@ -59,5 +60,7 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
|
||||
versioningStateStr = value.name
|
||||
}
|
||||
|
||||
companion object
|
||||
companion object {
|
||||
const val NOT_IN_BREADCRUMBS = -1
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ import io.realm.annotations.RealmModule
|
||||
SyncEntity::class,
|
||||
UserEntity::class,
|
||||
IgnoredUserEntity::class,
|
||||
BreadcrumbsEntity::class,
|
||||
EventAnnotationsSummaryEntity::class,
|
||||
ReactionAggregatedSummaryEntity::class,
|
||||
EditAggregatedSummaryEntity::class,
|
||||
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.matrix.android.internal.database.query
|
||||
|
||||
import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity
|
||||
import io.realm.Realm
|
||||
import io.realm.kotlin.createObject
|
||||
import io.realm.kotlin.where
|
||||
|
||||
internal fun BreadcrumbsEntity.Companion.get(realm: Realm): BreadcrumbsEntity? {
|
||||
return realm.where<BreadcrumbsEntity>().findFirst()
|
||||
}
|
||||
|
||||
internal fun BreadcrumbsEntity.Companion.getOrCreate(realm: Realm): BreadcrumbsEntity {
|
||||
return get(realm) ?: realm.createObject()
|
||||
}
|
@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.database.model.ReadMarkerEntity
|
||||
import im.vector.matrix.android.internal.database.model.ReadMarkerEntityFields
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.kotlin.createObject
|
||||
import io.realm.kotlin.where
|
||||
|
||||
internal fun ReadMarkerEntity.Companion.where(realm: Realm, roomId: String): RealmQuery<ReadMarkerEntity> {
|
||||
@ -28,6 +29,5 @@ internal fun ReadMarkerEntity.Companion.where(realm: Realm, roomId: String): Rea
|
||||
}
|
||||
|
||||
internal fun ReadMarkerEntity.Companion.getOrCreate(realm: Realm, roomId: String): ReadMarkerEntity {
|
||||
return where(realm, roomId).findFirst()
|
||||
?: realm.createObject(ReadMarkerEntity::class.java, roomId)
|
||||
return where(realm, roomId).findFirst() ?: realm.createObject(roomId)
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
||||
import im.vector.matrix.android.internal.database.model.ReadReceiptEntityFields
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.kotlin.createObject
|
||||
import io.realm.kotlin.where
|
||||
|
||||
internal fun ReadReceiptEntity.Companion.where(realm: Realm, roomId: String, userId: String): RealmQuery<ReadReceiptEntity> {
|
||||
@ -44,10 +45,11 @@ internal fun ReadReceiptEntity.Companion.createUnmanaged(roomId: String, eventId
|
||||
|
||||
internal fun ReadReceiptEntity.Companion.getOrCreate(realm: Realm, roomId: String, userId: String): ReadReceiptEntity {
|
||||
return ReadReceiptEntity.where(realm, roomId, userId).findFirst()
|
||||
?: realm.createObject(ReadReceiptEntity::class.java, buildPrimaryKey(roomId, userId)).apply {
|
||||
this.roomId = roomId
|
||||
this.userId = userId
|
||||
}
|
||||
?: realm.createObject<ReadReceiptEntity>(buildPrimaryKey(roomId, userId))
|
||||
.apply {
|
||||
this.roomId = roomId
|
||||
this.userId = userId
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildPrimaryKey(roomId: String, userId: String) = "${roomId}_$userId"
|
||||
|
@ -21,6 +21,7 @@ import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.RealmResults
|
||||
import io.realm.kotlin.createObject
|
||||
import io.realm.kotlin.where
|
||||
|
||||
internal fun RoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery<RoomSummaryEntity> {
|
||||
@ -32,8 +33,7 @@ internal fun RoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = n
|
||||
}
|
||||
|
||||
internal fun RoomSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: String): RoomSummaryEntity {
|
||||
return where(realm, roomId).findFirst()
|
||||
?: realm.createObject(RoomSummaryEntity::class.java, roomId)
|
||||
return where(realm, roomId).findFirst() ?: realm.createObject(roomId)
|
||||
}
|
||||
|
||||
internal fun RoomSummaryEntity.Companion.getDirectRooms(realm: Realm): RealmResults<RoomSummaryEntity> {
|
||||
|
@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.room.model.message.*
|
||||
import im.vector.matrix.android.internal.network.parsing.RuntimeJsonAdapterFactory
|
||||
import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataBreadcrumbs
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataDirectMessages
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataFallback
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataIgnoredUsers
|
||||
@ -34,6 +35,7 @@ object MoshiProvider {
|
||||
.registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES)
|
||||
.registerSubtype(UserAccountDataIgnoredUsers::class.java, UserAccountData.TYPE_IGNORED_USER_LIST)
|
||||
.registerSubtype(UserAccountDataPushRules::class.java, UserAccountData.TYPE_PUSH_RULES)
|
||||
.registerSubtype(UserAccountDataBreadcrumbs::class.java, UserAccountData.TYPE_BREADCRUMBS)
|
||||
)
|
||||
.add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java)
|
||||
.registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT)
|
||||
|
@ -33,6 +33,7 @@ import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
||||
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
|
||||
import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.UpdateBreadcrumbsTask
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import io.realm.Realm
|
||||
@ -43,6 +44,7 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
|
||||
private val createRoomTask: CreateRoomTask,
|
||||
private val joinRoomTask: JoinRoomTask,
|
||||
private val markAllRoomsReadTask: MarkAllRoomsReadTask,
|
||||
private val updateBreadcrumbsTask: UpdateBreadcrumbsTask,
|
||||
private val roomFactory: RoomFactory,
|
||||
private val taskExecutor: TaskExecutor) : RoomService {
|
||||
|
||||
@ -75,6 +77,25 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
|
||||
)
|
||||
}
|
||||
|
||||
override fun liveBreadcrumbs(): LiveData<List<RoomSummary>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{ realm ->
|
||||
RoomSummaryEntity.where(realm)
|
||||
.isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
|
||||
.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
|
||||
.greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummaryEntity.NOT_IN_BREADCRUMBS)
|
||||
.sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX)
|
||||
},
|
||||
{ roomSummaryMapper.map(it) }
|
||||
)
|
||||
}
|
||||
|
||||
override fun onRoomDisplayed(roomId: String): Cancelable {
|
||||
return updateBreadcrumbsTask
|
||||
.configureWith(UpdateBreadcrumbsTask.Params(roomId))
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun joinRoom(roomId: String, viaServers: List<String>, callback: MatrixCallback<Unit>): Cancelable {
|
||||
return joinRoomTask
|
||||
.configureWith(JoinRoomTask.Params(roomId, viaServers)) {
|
||||
|
@ -29,6 +29,7 @@ import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
||||
import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.*
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.SaveBreadcrumbsTask
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.SaveIgnoredUsersTask
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
@ -44,6 +45,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc
|
||||
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
||||
private val savePushRulesTask: SavePushRulesTask,
|
||||
private val saveIgnoredUsersTask: SaveIgnoredUsersTask,
|
||||
private val saveBreadcrumbsTask: SaveBreadcrumbsTask,
|
||||
private val taskExecutor: TaskExecutor) {
|
||||
|
||||
suspend fun handle(accountData: UserAccountDataSync?, invites: Map<String, InvitedRoomSync>?) {
|
||||
@ -52,6 +54,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc
|
||||
is UserAccountDataDirectMessages -> handleDirectChatRooms(it)
|
||||
is UserAccountDataPushRules -> handlePushRules(it)
|
||||
is UserAccountDataIgnoredUsers -> handleIgnoredUsers(it)
|
||||
is UserAccountDataBreadcrumbs -> handleBreadcrumbs(it)
|
||||
is UserAccountDataFallback -> Timber.d("Receive account data of unhandled type ${it.type}")
|
||||
else -> error("Missing code here!")
|
||||
}
|
||||
@ -130,4 +133,10 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc
|
||||
.executeBy(taskExecutor)
|
||||
// TODO If not initial sync, we should execute a init sync
|
||||
}
|
||||
|
||||
private fun handleBreadcrumbs(userAccountDataBreadcrumbs: UserAccountDataBreadcrumbs) {
|
||||
saveBreadcrumbsTask
|
||||
.configureWith(SaveBreadcrumbsTask.Params(userAccountDataBreadcrumbs.content.recentRoomIds))
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ internal abstract class UserAccountData {
|
||||
companion object {
|
||||
const val TYPE_IGNORED_USER_LIST = "m.ignored_user_list"
|
||||
const val TYPE_DIRECT_MESSAGES = "m.direct"
|
||||
const val TYPE_BREADCRUMBS = "im.vector.setting.breadcrumbs" // Was previously "im.vector.riot.breadcrumb_rooms"
|
||||
const val TYPE_PREVIEW_URLS = "org.matrix.preview_urls"
|
||||
const val TYPE_WIDGETS = "m.widgets"
|
||||
const val TYPE_PUSH_RULES = "m.push_rules"
|
||||
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.matrix.android.internal.session.sync.model.accountdata
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserAccountDataBreadcrumbs(
|
||||
@Json(name = "type") override val type: String = TYPE_BREADCRUMBS,
|
||||
@Json(name = "content") val content: BreadcrumbsContent
|
||||
) : UserAccountData()
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class BreadcrumbsContent(
|
||||
@Json(name = "recent_rooms") val recentRoomIds: List<String> = emptyList()
|
||||
)
|
@ -35,5 +35,11 @@ internal abstract class AccountDataModule {
|
||||
}
|
||||
|
||||
@Binds
|
||||
abstract fun bindUpdateUserAccountDataTask(updateUserAccountDataTask: DefaultUpdateUserAccountDataTask): UpdateUserAccountDataTask
|
||||
abstract fun bindUpdateUserAccountDataTask(task: DefaultUpdateUserAccountDataTask): UpdateUserAccountDataTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindSaveBreadcrumbsTask(task: DefaultSaveBreadcrumbsTask): SaveBreadcrumbsTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindUpdateBreadcrumsTask(task: DefaultUpdateBreadcrumbsTask): UpdateBreadcrumbsTask
|
||||
}
|
||||
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.matrix.android.internal.session.user.accountdata
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
||||
import im.vector.matrix.android.internal.database.query.getOrCreate
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import im.vector.matrix.android.internal.util.awaitTransaction
|
||||
import io.realm.RealmList
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Save the Breadcrumbs roomId list in DB, either from the sync, or updated locally
|
||||
*/
|
||||
internal interface SaveBreadcrumbsTask : Task<SaveBreadcrumbsTask.Params, Unit> {
|
||||
data class Params(
|
||||
val recentRoomIds: List<String>
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultSaveBreadcrumbsTask @Inject constructor(
|
||||
private val monarchy: Monarchy
|
||||
) : SaveBreadcrumbsTask {
|
||||
|
||||
override suspend fun execute(params: SaveBreadcrumbsTask.Params) {
|
||||
monarchy.awaitTransaction { realm ->
|
||||
// Get or create a breadcrumbs entity
|
||||
val entity = BreadcrumbsEntity.getOrCreate(realm)
|
||||
|
||||
// And save the new received list
|
||||
entity.recentRoomIds = RealmList<String>().apply { addAll(params.recentRoomIds) }
|
||||
|
||||
// Update the room summaries
|
||||
// Reset all the indexes...
|
||||
RoomSummaryEntity.where(realm)
|
||||
.greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummaryEntity.NOT_IN_BREADCRUMBS)
|
||||
.findAll()
|
||||
.forEach {
|
||||
it.breadcrumbsIndex = RoomSummaryEntity.NOT_IN_BREADCRUMBS
|
||||
}
|
||||
|
||||
// ...and apply new indexes
|
||||
params.recentRoomIds.forEachIndexed { index, roomId ->
|
||||
RoomSummaryEntity.where(realm, roomId)
|
||||
.findFirst()
|
||||
?.breadcrumbsIndex = index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.matrix.android.internal.session.user.accountdata
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity
|
||||
import im.vector.matrix.android.internal.database.query.get
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import im.vector.matrix.android.internal.util.fetchCopied
|
||||
import javax.inject.Inject
|
||||
|
||||
// Use the same arbitrary value than Riot-Web
|
||||
private const val MAX_BREADCRUMBS_ROOMS_NUMBER = 20
|
||||
|
||||
internal interface UpdateBreadcrumbsTask : Task<UpdateBreadcrumbsTask.Params, Unit> {
|
||||
data class Params(
|
||||
val newTopRoomId: String
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultUpdateBreadcrumbsTask @Inject constructor(
|
||||
private val saveBreadcrumbsTask: SaveBreadcrumbsTask,
|
||||
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
||||
private val monarchy: Monarchy
|
||||
) : UpdateBreadcrumbsTask {
|
||||
|
||||
override suspend fun execute(params: UpdateBreadcrumbsTask.Params) {
|
||||
val newBreadcrumbs =
|
||||
// Get the breadcrumbs entity, if any
|
||||
monarchy.fetchCopied { BreadcrumbsEntity.get(it) }
|
||||
?.recentRoomIds
|
||||
?.apply {
|
||||
// Modify the list to add the newTopRoomId first
|
||||
// Ensure the newTopRoomId is not already in the list
|
||||
remove(params.newTopRoomId)
|
||||
// Add the newTopRoomId at first position
|
||||
add(0, params.newTopRoomId)
|
||||
}
|
||||
?.take(MAX_BREADCRUMBS_ROOMS_NUMBER)
|
||||
?: listOf(params.newTopRoomId)
|
||||
|
||||
// Update the DB locally, do not wait for the sync
|
||||
saveBreadcrumbsTask.execute(SaveBreadcrumbsTask.Params(newBreadcrumbs))
|
||||
|
||||
// FIXME It can remove the previous breadcrumbs, if not synced yet
|
||||
// And update account data
|
||||
updateUserAccountDataTask.execute(UpdateUserAccountDataTask.BreadcrumbsParams(
|
||||
breadcrumbsContent = BreadcrumbsContent(newBreadcrumbs)
|
||||
))
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.user.accountdata
|
||||
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
@ -38,6 +39,15 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
|
||||
return directMessages
|
||||
}
|
||||
}
|
||||
|
||||
data class BreadcrumbsParams(override val type: String = UserAccountData.TYPE_BREADCRUMBS,
|
||||
private val breadcrumbsContent: BreadcrumbsContent
|
||||
) : Params {
|
||||
|
||||
override fun getData(): Any {
|
||||
return breadcrumbsContent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class DefaultUpdateUserAccountDataTask @Inject constructor(private val accountDataApi: AccountDataAPI,
|
||||
|
@ -33,6 +33,7 @@ import im.vector.riotx.features.home.LoadingFragment
|
||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomDirectoryUsersFragment
|
||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomKnownUsersFragment
|
||||
import im.vector.riotx.features.home.group.GroupListFragment
|
||||
import im.vector.riotx.features.home.room.breadcrumbs.BreadcrumbsFragment
|
||||
import im.vector.riotx.features.home.room.detail.RoomDetailFragment
|
||||
import im.vector.riotx.features.home.room.list.RoomListFragment
|
||||
import im.vector.riotx.features.login.*
|
||||
@ -249,4 +250,9 @@ interface FragmentModule {
|
||||
@IntoMap
|
||||
@FragmentKey(PublicRoomsFragment::class)
|
||||
fun bindPublicRoomsFragment(fragment: PublicRoomsFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(BreadcrumbsFragment::class)
|
||||
fun bindBreadcrumbsFragment(fragment: BreadcrumbsFragment): Fragment
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedVie
|
||||
import im.vector.riotx.features.crypto.verification.SasVerificationViewModel
|
||||
import im.vector.riotx.features.home.HomeSharedActionViewModel
|
||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomSharedActionViewModel
|
||||
import im.vector.riotx.features.home.room.detail.RoomDetailSharedActionViewModel
|
||||
import im.vector.riotx.features.home.room.detail.timeline.action.MessageSharedActionViewModel
|
||||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
||||
import im.vector.riotx.features.login.LoginSharedActionViewModel
|
||||
@ -118,4 +119,9 @@ interface ViewModelModule {
|
||||
@IntoMap
|
||||
@ViewModelKey(LoginSharedActionViewModel::class)
|
||||
fun bindLoginSharedActionViewModel(viewModel: LoginSharedActionViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(RoomDetailSharedActionViewModel::class)
|
||||
fun bindRoomDetailSharedActionViewModel(viewModel: RoomDetailSharedActionViewModel): ViewModel
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.riotx.features.home.room.breadcrumbs
|
||||
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
|
||||
private const val ANIM_DURATION_IN_MILLIS = 200L
|
||||
|
||||
class BreadcrumbsAnimator : DefaultItemAnimator() {
|
||||
|
||||
init {
|
||||
addDuration = ANIM_DURATION_IN_MILLIS
|
||||
removeDuration = ANIM_DURATION_IN_MILLIS
|
||||
moveDuration = ANIM_DURATION_IN_MILLIS
|
||||
changeDuration = ANIM_DURATION_IN_MILLIS
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.riotx.features.home.room.breadcrumbs
|
||||
|
||||
import android.view.View
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import im.vector.riotx.core.utils.DebouncedClickListener
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
import javax.inject.Inject
|
||||
|
||||
class BreadcrumbsController @Inject constructor(
|
||||
private val avatarRenderer: AvatarRenderer
|
||||
) : EpoxyController() {
|
||||
|
||||
var listener: Listener? = null
|
||||
|
||||
private var viewState: BreadcrumbsViewState? = 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 breadcrumbs on the main thread.
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
fun update(viewState: BreadcrumbsViewState) {
|
||||
this.viewState = viewState
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
override fun buildModels() {
|
||||
val safeViewState = viewState ?: return
|
||||
|
||||
// An empty breadcrumbs list can only be temporary because when entering in a room,
|
||||
// this one is added to the breadcrumbs
|
||||
|
||||
safeViewState.asyncBreadcrumbs.invoke()
|
||||
?.forEach {
|
||||
breadcrumbsItem {
|
||||
id(it.roomId)
|
||||
avatarRenderer(avatarRenderer)
|
||||
roomId(it.roomId)
|
||||
roomName(it.displayName)
|
||||
avatarUrl(it.avatarUrl)
|
||||
unreadNotificationCount(it.notificationCount)
|
||||
showHighlighted(it.highlightCount > 0)
|
||||
hasUnreadMessage(it.hasUnreadMessages)
|
||||
hasDraft(it.userDrafts.isNotEmpty())
|
||||
itemClickListener(
|
||||
DebouncedClickListener(View.OnClickListener { _ ->
|
||||
listener?.onBreadcrumbClicked(it.roomId)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun onBreadcrumbClicked(roomId: String)
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.riotx.features.home.room.breadcrumbs
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import im.vector.riotx.features.home.room.detail.RoomDetailSharedAction
|
||||
import im.vector.riotx.features.home.room.detail.RoomDetailSharedActionViewModel
|
||||
import kotlinx.android.synthetic.main.fragment_breadcrumbs.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class BreadcrumbsFragment @Inject constructor(
|
||||
private val breadcrumbsController: BreadcrumbsController,
|
||||
val breadcrumbsViewModelFactory: BreadcrumbsViewModel.Factory
|
||||
) : VectorBaseFragment(), BreadcrumbsController.Listener {
|
||||
|
||||
private lateinit var sharedActionViewModel: RoomDetailSharedActionViewModel
|
||||
private val breadcrumbsViewModel: BreadcrumbsViewModel by fragmentViewModel()
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_breadcrumbs
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setupRecyclerView()
|
||||
sharedActionViewModel = activityViewModelProvider.get(RoomDetailSharedActionViewModel::class.java)
|
||||
|
||||
breadcrumbsViewModel.subscribe { renderState(it) }
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
breadcrumbsRecyclerView.adapter = null
|
||||
}
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
val layoutManager = LinearLayoutManager(context)
|
||||
breadcrumbsRecyclerView.layoutManager = layoutManager
|
||||
breadcrumbsRecyclerView.itemAnimator = BreadcrumbsAnimator()
|
||||
breadcrumbsController.listener = this
|
||||
breadcrumbsRecyclerView.setController(breadcrumbsController)
|
||||
}
|
||||
|
||||
private fun renderState(state: BreadcrumbsViewState) {
|
||||
breadcrumbsController.update(state)
|
||||
}
|
||||
|
||||
// BreadcrumbsController.Listener **************************************************************
|
||||
|
||||
override fun onBreadcrumbClicked(roomId: String) {
|
||||
sharedActionViewModel.post(RoomDetailSharedAction.SwitchToRoom(roomId))
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.riotx.features.home.room.breadcrumbs
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
import im.vector.riotx.features.home.room.list.UnreadCounterBadgeView
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_breadcrumbs)
|
||||
abstract class BreadcrumbsItem : VectorEpoxyModel<BreadcrumbsItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||
@EpoxyAttribute lateinit var roomId: String
|
||||
@EpoxyAttribute lateinit var roomName: CharSequence
|
||||
@EpoxyAttribute var avatarUrl: String? = null
|
||||
@EpoxyAttribute var unreadNotificationCount: Int = 0
|
||||
@EpoxyAttribute var showHighlighted: Boolean = false
|
||||
@EpoxyAttribute var hasUnreadMessage: Boolean = false
|
||||
@EpoxyAttribute var hasDraft: Boolean = false
|
||||
@EpoxyAttribute var itemClickListener: View.OnClickListener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.rootView.setOnClickListener(itemClickListener)
|
||||
holder.unreadIndentIndicator.isVisible = hasUnreadMessage
|
||||
avatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView)
|
||||
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted))
|
||||
holder.draftIndentIndicator.isVisible = hasDraft
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.breadcrumbsUnreadCounterBadgeView)
|
||||
val unreadIndentIndicator by bind<View>(R.id.breadcrumbsUnreadIndicator)
|
||||
val draftIndentIndicator by bind<View>(R.id.breadcrumbsDraftBadge)
|
||||
val avatarImageView by bind<ImageView>(R.id.breadcrumbsImageView)
|
||||
val rootView by bind<ViewGroup>(R.id.breadcrumbsRoot)
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.riotx.features.home.room.breadcrumbs
|
||||
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotx.core.platform.EmptyAction
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
|
||||
class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: BreadcrumbsViewState,
|
||||
private val session: Session)
|
||||
: VectorViewModel<BreadcrumbsViewState, EmptyAction>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(initialState: BreadcrumbsViewState): BreadcrumbsViewModel
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<BreadcrumbsViewModel, BreadcrumbsViewState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: BreadcrumbsViewState): BreadcrumbsViewModel? {
|
||||
val fragment: BreadcrumbsFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
return fragment.breadcrumbsViewModelFactory.create(state)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
observeBreadcrumbs()
|
||||
}
|
||||
|
||||
override fun handle(action: EmptyAction) {
|
||||
// No op
|
||||
}
|
||||
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
||||
private fun observeBreadcrumbs() {
|
||||
session.rx()
|
||||
.liveBreadcrumbs()
|
||||
.observeOn(Schedulers.computation())
|
||||
.execute { asyncBreadcrumbs ->
|
||||
copy(asyncBreadcrumbs = asyncBreadcrumbs)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.riotx.features.home.room.breadcrumbs
|
||||
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
|
||||
data class BreadcrumbsViewState(
|
||||
val asyncBreadcrumbs: Async<List<RoomSummary>> = Uninitialized
|
||||
) : MvRxState
|
@ -20,17 +20,25 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.hideKeyboard
|
||||
import im.vector.riotx.core.extensions.replaceFragment
|
||||
import im.vector.riotx.core.platform.ToolbarConfigurable
|
||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
import im.vector.riotx.features.home.room.breadcrumbs.BreadcrumbsFragment
|
||||
import kotlinx.android.synthetic.main.activity_room_detail.*
|
||||
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
|
||||
|
||||
class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.activity_room_detail
|
||||
}
|
||||
override fun getLayoutRes() = R.layout.activity_room_detail
|
||||
|
||||
private lateinit var sharedActionViewModel: RoomDetailSharedActionViewModel
|
||||
|
||||
// Simple filter
|
||||
private var currentRoomId: String? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -38,14 +46,57 @@ class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||
if (isFirstCreation()) {
|
||||
val roomDetailArgs: RoomDetailArgs = intent?.extras?.getParcelable(EXTRA_ROOM_DETAIL_ARGS)
|
||||
?: return
|
||||
currentRoomId = roomDetailArgs.roomId
|
||||
replaceFragment(R.id.roomDetailContainer, RoomDetailFragment::class.java, roomDetailArgs)
|
||||
replaceFragment(R.id.roomDetailDrawerContainer, BreadcrumbsFragment::class.java)
|
||||
}
|
||||
|
||||
sharedActionViewModel = viewModelProvider.get(RoomDetailSharedActionViewModel::class.java)
|
||||
|
||||
sharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { sharedAction ->
|
||||
when (sharedAction) {
|
||||
is RoomDetailSharedAction.SwitchToRoom -> switchToRoom(sharedAction)
|
||||
}
|
||||
}
|
||||
.disposeOnDestroy()
|
||||
|
||||
drawerLayout.addDrawerListener(drawerListener)
|
||||
}
|
||||
|
||||
private fun switchToRoom(switchToRoom: RoomDetailSharedAction.SwitchToRoom) {
|
||||
drawerLayout.closeDrawer(GravityCompat.START)
|
||||
// Do not replace the Fragment if it's the same roomId
|
||||
if (currentRoomId != switchToRoom.roomId) {
|
||||
currentRoomId = switchToRoom.roomId
|
||||
replaceFragment(R.id.roomDetailContainer, RoomDetailFragment::class.java, RoomDetailArgs(switchToRoom.roomId))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
drawerLayout.removeDrawerListener(drawerListener)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun configure(toolbar: Toolbar) {
|
||||
configureToolbar(toolbar)
|
||||
}
|
||||
|
||||
private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
|
||||
override fun onDrawerStateChanged(newState: Int) {
|
||||
hideKeyboard()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
|
||||
drawerLayout.closeDrawer(GravityCompat.START)
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val EXTRA_ROOM_DETAIL_ARGS = "EXTRA_ROOM_DETAIL_ARGS"
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.riotx.features.home.room.detail
|
||||
|
||||
import im.vector.riotx.core.platform.VectorSharedAction
|
||||
|
||||
/**
|
||||
* Supported navigation actions for [RoomDetailActivity]
|
||||
*/
|
||||
sealed class RoomDetailSharedAction : VectorSharedAction {
|
||||
data class SwitchToRoom(val roomId: String) : RoomDetailSharedAction()
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.riotx.features.home.room.detail
|
||||
|
||||
import im.vector.riotx.core.platform.VectorSharedActionViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Activity shared view model
|
||||
*/
|
||||
class RoomDetailSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<RoomDetailSharedAction>()
|
@ -143,6 +143,9 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
timeline.addListener(this)
|
||||
timeline.start()
|
||||
setState { copy(timeline = this@RoomDetailViewModel.timeline) }
|
||||
|
||||
// Inform the SDK that the room is displayed
|
||||
session.onRoomDisplayed(initialState.roomId)
|
||||
}
|
||||
|
||||
override fun handle(action: RoomDetailAction) {
|
||||
|
@ -42,7 +42,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
|
||||
|
||||
init {
|
||||
// We are requesting a model build directly as the first build of epoxy is on the main thread.
|
||||
// It avoids to build the the whole list of rooms on the main thread.
|
||||
// It avoids to build the whole list of rooms on the main thread.
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<include layout="@layout/merge_overlay_waiting_view" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
|
||||
|
@ -1,14 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/drawerLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
tools:openDrawer="start">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/vector_coordinator_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/roomDetailContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<include layout="@layout/merge_overlay_waiting_view" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/roomDetailContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
android:id="@+id/roomDetailDrawerContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start" />
|
||||
|
||||
<include layout="@layout/merge_overlay_waiting_view" />
|
||||
|
||||
</FrameLayout>
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
|
8
vector/src/main/res/layout/fragment_breadcrumbs.xml
Normal file
8
vector/src/main/res/layout/fragment_breadcrumbs.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/breadcrumbsRecyclerView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?riotx_background"
|
||||
tools:listitem="@layout/item_breadcrumbs" />
|
70
vector/src/main/res/layout/item_breadcrumbs.xml
Normal file
70
vector/src/main/res/layout/item_breadcrumbs.xml
Normal file
@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/breadcrumbsRoot"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?riotx_background"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground">
|
||||
|
||||
<View
|
||||
android:id="@+id/breadcrumbsUnreadIndicator"
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="?attr/colorAccent"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/breadcrumbsImageView"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<im.vector.riotx.features.home.room.list.UnreadCounterBadgeView
|
||||
android:id="@+id/breadcrumbsUnreadCounterBadgeView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:minWidth="16dp"
|
||||
android:minHeight="16dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="10sp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintCircle="@+id/breadcrumbsImageView"
|
||||
app:layout_constraintCircleAngle="45"
|
||||
app:layout_constraintCircleRadius="28dp"
|
||||
tools:background="@drawable/bg_unread_highlight"
|
||||
tools:ignore="MissingConstraints"
|
||||
tools:text="24"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/breadcrumbsDraftBadge"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:background="@drawable/circle"
|
||||
android:padding="3dp"
|
||||
android:src="@drawable/ic_edit"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintCircle="@+id/breadcrumbsImageView"
|
||||
app:layout_constraintCircleAngle="135"
|
||||
app:layout_constraintCircleRadius="28dp"
|
||||
tools:ignore="MissingConstraints"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
x
Reference in New Issue
Block a user