From 9a42c121e46bb2301d5b61eed1db11bb6a586536 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 25 Jan 2019 18:04:08 +0100 Subject: [PATCH 01/11] Room list : show unread badge with room name. Still need to mark a room as read to update marker. --- .../home/room/list/RoomSummaryController.kt | 4 ++ .../home/room/list/RoomSummaryFormatter.kt | 35 +++++++++++++ .../home/room/list/RoomSummaryItem.kt | 4 ++ .../home/room/list/UnreadCounterBadgeView.kt | 52 +++++++++++++++++++ .../main/res/drawable/bg_unread_highlight.xml | 5 ++ .../res/drawable/bg_unread_notification.xml | 6 +++ app/src/main/res/layout/item_room.xml | 30 ++++++++--- .../main/res/layout/item_room_category.xml | 1 - .../api/session/room/model/RoomSummary.kt | 4 +- .../database/mapper/RoomSummaryMapper.kt | 14 ++--- .../database/model/RoomSummaryEntity.kt | 4 +- .../internal/session/sync/RoomSyncHandler.kt | 35 +++++++++++-- 12 files changed, 173 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryFormatter.kt create mode 100755 app/src/main/java/im/vector/riotredesign/features/home/room/list/UnreadCounterBadgeView.kt create mode 100644 app/src/main/res/drawable/bg_unread_highlight.xml create mode 100644 app/src/main/res/drawable/bg_unread_notification.xml diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt index 1c618174..c8922e72 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt @@ -61,11 +61,15 @@ class RoomSummaryController(private val callback: Callback? = null private fun buildRoomModels(summaries: List, selectedRoomId: String?) { summaries.forEach { roomSummary -> + val unreadCount = roomSummary.notificationCount + val showHighlighted = roomSummary.highlightCount > 0 val isSelected = roomSummary.roomId == selectedRoomId RoomSummaryItem( roomName = roomSummary.displayName, avatarUrl = roomSummary.avatarUrl, isSelected = isSelected, + showHighlighted = showHighlighted, + unreadCount = unreadCount, listener = { callback?.onRoomSelected(roomSummary) } ) .id(roomSummary.roomId) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryFormatter.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryFormatter.kt new file mode 100644 index 00000000..4666d99b --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryFormatter.kt @@ -0,0 +1,35 @@ +/* + * 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.riotredesign.features.home.room.list + +object RoomSummaryFormatter { + + /** + * Format the unread messages counter. + * + * @param count the count + * @return the formatted value + */ + fun formatUnreadMessagesCounter(count: Int): String { + return if (count > 999) { + "${count / 1000}.${count % 1000 / 100}K" + } else { + count.toString() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt index 3949f323..6b4bd8a3 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt @@ -28,14 +28,18 @@ data class RoomSummaryItem( val roomName: CharSequence, val avatarUrl: String?, val isSelected: Boolean, + val unreadCount: Int, + val showHighlighted: Boolean, val listener: (() -> Unit)? = null ) : KotlinModel(R.layout.item_room) { + private val unreadCounterBadgeView by bind(R.id.roomUnreadCounterBadgeView) private val titleView by bind(R.id.roomNameView) private val avatarImageView by bind(R.id.roomAvatarImageView) private val rootView by bind(R.id.itemRoomLayout) override fun bind() { + unreadCounterBadgeView.render(unreadCount, showHighlighted) rootView.isChecked = isSelected rootView.setOnClickListener { listener?.invoke() } titleView.text = roomName diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/UnreadCounterBadgeView.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/UnreadCounterBadgeView.kt new file mode 100755 index 00000000..44de73e8 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/UnreadCounterBadgeView.kt @@ -0,0 +1,52 @@ +/* + * 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.riotredesign.features.home.room.list + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import androidx.appcompat.widget.AppCompatTextView +import im.vector.riotredesign.R + +class UnreadCounterBadgeView : AppCompatTextView { + + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) + + fun render(count: Int, highlighted: Boolean) { + if (count == 0) { + visibility = View.INVISIBLE + } else { + visibility = View.VISIBLE + val bgRes = if (highlighted) { + R.drawable.bg_unread_highlight + } else { + R.drawable.bg_unread_notification + } + setBackgroundResource(bgRes) + text = RoomSummaryFormatter.formatUnreadMessagesCounter(count) + } + } + + enum class Status { + NOTIFICATION, + HIGHLIGHT + } + +} diff --git a/app/src/main/res/drawable/bg_unread_highlight.xml b/app/src/main/res/drawable/bg_unread_highlight.xml new file mode 100644 index 00000000..371c1e7d --- /dev/null +++ b/app/src/main/res/drawable/bg_unread_highlight.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_unread_notification.xml b/app/src/main/res/drawable/bg_unread_notification.xml new file mode 100644 index 00000000..d6320a37 --- /dev/null +++ b/app/src/main/res/drawable/bg_unread_notification.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_room.xml b/app/src/main/res/layout/item_room.xml index 93540f7e..9179bd2b 100644 --- a/app/src/main/res/layout/item_room.xml +++ b/app/src/main/res/layout/item_room.xml @@ -1,18 +1,17 @@ - + android:focusable="true" + android:foreground="?attr/selectableItemBackground" + android:paddingLeft="8dp" + android:paddingRight="16dp"> + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_room_category.xml b/app/src/main/res/layout/item_room_category.xml index f3e1c54a..fdced9d6 100644 --- a/app/src/main/res/layout/item_room_category.xml +++ b/app/src/main/res/layout/item_room_category.xml @@ -35,7 +35,6 @@ android:id="@+id/roomCategoryAddButton" android:layout_width="32dp" android:layout_height="32dp" - android:scaleType="centerInside" android:src="@drawable/ic_add_circle_white" android:tint="@color/bluey_grey_two" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt index 0759086b..3ebe2604 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt @@ -26,5 +26,7 @@ data class RoomSummary( val topic: String = "", val avatarUrl: String = "", val isDirect: Boolean, - val otherMemberIds: List = emptyList() + val otherMemberIds: List = emptyList(), + var notificationCount: Int = 0, + var highlightCount: Int = 0 ) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt index 550943a7..9522b90a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt @@ -24,12 +24,14 @@ internal object RoomSummaryMapper { fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary { return RoomSummary( - roomSummaryEntity.roomId, - roomSummaryEntity.displayName ?: "", - roomSummaryEntity.topic ?: "", - roomSummaryEntity.avatarUrl ?: "", - roomSummaryEntity.isDirect, - roomSummaryEntity.otherMemberIds.toList() + roomId = roomSummaryEntity.roomId, + displayName = roomSummaryEntity.displayName ?: "", + topic = roomSummaryEntity.topic ?: "", + avatarUrl = roomSummaryEntity.avatarUrl ?: "", + isDirect = roomSummaryEntity.isDirect, + otherMemberIds = roomSummaryEntity.otherMemberIds.toList(), + highlightCount = roomSummaryEntity.highlightCount, + notificationCount = roomSummaryEntity.notificationCount ) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt index 79524026..3ff5a0e9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt @@ -29,7 +29,9 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "", var joinedMembersCount: Int? = 0, var invitedMembersCount: Int? = 0, var isDirect: Boolean = false, - var otherMemberIds: RealmList = RealmList() + var otherMemberIds: RealmList = RealmList(), + var notificationCount: Int = 0, + var highlightCount: Int = 0 ) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt index ed1a7d5e..77066c73 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt @@ -31,7 +31,12 @@ import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection -import im.vector.matrix.android.internal.session.sync.model.* +import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync +import im.vector.matrix.android.internal.session.sync.model.RoomSync +import im.vector.matrix.android.internal.session.sync.model.RoomSyncEphemeral +import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary +import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications +import im.vector.matrix.android.internal.session.sync.model.RoomsSyncResponse import io.realm.Realm import io.realm.kotlin.createObject @@ -57,9 +62,9 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, private fun handleRoomSync(realm: Realm, handlingStrategy: HandlingStrategy) { val rooms = when (handlingStrategy) { - is HandlingStrategy.JOINED -> handlingStrategy.data.map { handleJoinedRoom(realm, it.key, it.value) } + is HandlingStrategy.JOINED -> handlingStrategy.data.map { handleJoinedRoom(realm, it.key, it.value) } is HandlingStrategy.INVITED -> handlingStrategy.data.map { handleInvitedRoom(realm, it.key, it.value) } - is HandlingStrategy.LEFT -> handlingStrategy.data.map { handleLeftRoom(it.key, it.value) } + is HandlingStrategy.LEFT -> handlingStrategy.data.map { handleLeftRoom(it.key, it.value) } } realm.insertOrUpdate(rooms) } @@ -69,7 +74,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, roomSync: RoomSync): RoomEntity { val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: realm.createObject(roomId) + ?: realm.createObject(roomId) if (roomEntity.membership == MyMembership.INVITED) { roomEntity.chunks.deleteAllFromRealm() @@ -105,9 +110,14 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, handleRoomSummary(realm, roomId, roomSync.summary) } + if (roomSync.unreadNotifications != null) { + handleUnreadNotifications(realm, roomId, roomSync.unreadNotifications) + } + if (roomSync.ephemeral != null && roomSync.ephemeral.events.isNotEmpty()) { handleEphemeral(realm, roomId, roomSync.ephemeral) } + return roomEntity } @@ -159,7 +169,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, roomSummary: RoomSyncSummary) { val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() - ?: RoomSummaryEntity(roomId) + ?: RoomSummaryEntity(roomId) if (roomSummary.heroes.isNotEmpty()) { roomSummaryEntity.heroes.clear() @@ -182,4 +192,19 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, .map { it.content.toModel() } .flatMap { readReceiptHandler.handle(realm, roomId, it) } } + + private fun handleUnreadNotifications(realm: Realm, roomId: String, unreadNotifications: RoomSyncUnreadNotifications) { + val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() + ?: RoomSummaryEntity(roomId) + + if (unreadNotifications.highlightCount != null) { + roomSummaryEntity.highlightCount = unreadNotifications.highlightCount + } + if (unreadNotifications.notificationCount != null) { + roomSummaryEntity.notificationCount = unreadNotifications.notificationCount + } + realm.insertOrUpdate(roomSummaryEntity) + + } + } \ No newline at end of file From b2e2c14e698072cec96d34c4949897d288fdf08f Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 28 Jan 2019 17:56:23 +0100 Subject: [PATCH 02/11] Room : start to handle read markers. Only used when you open the room at the moment. --- .../home/room/detail/RoomDetailViewModel.kt | 1 + .../res/drawable/bg_unread_notification.xml | 2 +- app/src/main/res/layout/item_room.xml | 2 +- app/src/main/res/values/colors.xml | 1 + .../matrix/android/api/session/room/Room.kt | 3 +- .../api/session/room/read/ReadService.kt | 31 ++++++++++ .../database/query/EventEntityQueries.kt | 8 ++- .../internal/session/room/DefaultRoom.kt | 20 ++++++- .../android/internal/session/room/RoomAPI.kt | 16 ++++- .../internal/session/room/RoomModule.kt | 14 ++++- .../session/room/read/DefaultReadService.kt | 59 +++++++++++++++++++ .../session/room/read/SetReadMarkersTask.kt | 51 ++++++++++++++++ .../matrix/android/internal/util/Monarchy.kt | 11 ++++ 13 files changed, 212 insertions(+), 7 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/read/ReadService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt index 537a23f8..57ca3508 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -50,6 +50,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, observeRoomSummary() observeTimeline() room.loadRoomMembersIfNeeded() + room.markLatestAsRead(callback = object : MatrixCallback {}) } fun accept(action: RoomDetailActions) { diff --git a/app/src/main/res/drawable/bg_unread_notification.xml b/app/src/main/res/drawable/bg_unread_notification.xml index d6320a37..496134a7 100644 --- a/app/src/main/res/drawable/bg_unread_notification.xml +++ b/app/src/main/res/drawable/bg_unread_notification.xml @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/layout/item_room.xml b/app/src/main/res/layout/item_room.xml index 9179bd2b..c0148ee7 100644 --- a/app/src/main/res/layout/item_room.xml +++ b/app/src/main/res/layout/item_room.xml @@ -52,7 +52,7 @@ android:gravity="center" android:minWidth="24dp" android:textColor="@android:color/white" - android:textSize="10sp" + android:textSize="12sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintDimensionRatio="1:1" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 9f20f5f9..9797d945 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -16,4 +16,5 @@ #a5aab2 #ebedf8 #a5a5a5 + #61708B diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt index bcb1e9dc..fb27b29a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.api.session.room import androidx.lifecycle.LiveData import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.android.api.session.room.read.ReadService import im.vector.matrix.android.api.session.room.send.SendService import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.util.Cancelable @@ -26,7 +27,7 @@ import im.vector.matrix.android.api.util.Cancelable /** * This interface defines methods to interact within a room. */ -interface Room : TimelineService, SendService { +interface Room : TimelineService, SendService, ReadService { /** * The roomId of this room diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/read/ReadService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/read/ReadService.kt new file mode 100644 index 00000000..102967e7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/read/ReadService.kt @@ -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.api.session.room.read + +import im.vector.matrix.android.api.MatrixCallback + +interface ReadService { + + fun markLatestAsRead(callback: MatrixCallback) + + fun markAllAsRead(callback: MatrixCallback) + + fun setReadReceipt(eventId: String, callback: MatrixCallback) + + fun setReadMarkers(fullyReadEventId: String, readReceiptEventId: String?, callback: MatrixCallback) + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt index 48bcd0b3..d6efe178 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.internal.database.query +import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity.LinkFilterMode.* import im.vector.matrix.android.internal.database.model.EventEntityFields @@ -48,6 +49,12 @@ internal fun EventEntity.Companion.where(realm: Realm, } } +internal fun EventEntity.Companion.latestEvent(realm: Realm, + roomId: String): EventEntity? { + val chunkEntity = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) + return chunkEntity?.events?.where()?.sort(EventEntityFields.DISPLAY_INDEX)?.findFirst() +} + internal fun RealmQuery.next(from: Int? = null, strict: Boolean = true): EventEntity? { if (from != null) { @@ -62,7 +69,6 @@ internal fun RealmQuery.next(from: Int? = null, strict: Boolean = t .findFirst() } - internal fun RealmQuery.last(since: Int? = null, strict: Boolean = false): EventEntity? { if (since != null) { if (strict) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt index 9a0bad45..5b716d4e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt @@ -22,10 +22,11 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.Room -import im.vector.matrix.android.api.session.room.send.SendService import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.android.api.session.room.read.ReadService +import im.vector.matrix.android.api.session.room.send.SendService import im.vector.matrix.android.api.session.room.timeline.TimelineData import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.util.Cancelable @@ -49,6 +50,7 @@ internal data class DefaultRoom( private val monarchy by inject() private val timelineService by inject { parametersOf(roomId) } private val sendService by inject { parametersOf(roomId) } + private val readService by inject { parametersOf(roomId) } private val taskExecutor by inject() override val roomSummary: LiveData by lazy { @@ -76,5 +78,21 @@ internal data class DefaultRoom( return sendService.sendTextMessage(text, callback) } + override fun setReadReceipt(eventId: String, callback: MatrixCallback) { + readService.setReadReceipt(eventId, callback) + } + + override fun setReadMarkers(fullyReadEventId: String, readReceiptEventId: String?, callback: MatrixCallback) { + readService.setReadMarkers(fullyReadEventId, readReceiptEventId, callback) + } + + override fun markAllAsRead(callback: MatrixCallback) { + readService.markAllAsRead(callback) + } + + override fun markLatestAsRead(callback: MatrixCallback) { + readService.markLatestAsRead(callback) + } + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt index b14b0fcf..bba9c134 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt @@ -24,7 +24,12 @@ import im.vector.matrix.android.internal.session.room.send.SendResponse import im.vector.matrix.android.internal.session.room.timeline.EventContextResponse import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse import retrofit2.Call -import retrofit2.http.* +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.PUT +import retrofit2.http.Path +import retrofit2.http.Query internal interface RoomAPI { @@ -100,5 +105,14 @@ internal interface RoomAPI { @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/event/{eventId}") fun getEvent(@Path("roomId") roomId: String, @Path("eventId") eventId: String): Call + /** + * Send read markers. + * + * @param roomId the room id + * @param markers the read markers + */ + @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/read_markers") + fun sendReadMarker(@Path("roomId") roomId: String, @Body markers: Map): Call + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index 3ebaa0ee..65eb76b9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -17,14 +17,18 @@ package im.vector.matrix.android.internal.session.room import im.vector.matrix.android.api.auth.data.SessionParams +import im.vector.matrix.android.api.session.room.read.ReadService import im.vector.matrix.android.api.session.room.send.SendService -import im.vector.matrix.android.internal.session.room.send.EventFactory import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.internal.session.DefaultSession import im.vector.matrix.android.internal.session.room.members.DefaultLoadRoomMembersTask import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor +import im.vector.matrix.android.internal.session.room.read.DefaultReadService +import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask +import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask import im.vector.matrix.android.internal.session.room.send.DefaultSendService +import im.vector.matrix.android.internal.session.room.send.EventFactory import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService @@ -63,6 +67,10 @@ class RoomModule { DefaultGetContextOfEventTask(get(), get()) as GetContextOfEventTask } + scope(DefaultSession.SCOPE) { + DefaultSetReadMarkersTask(get()) as SetReadMarkersTask + } + scope(DefaultSession.SCOPE) { val sessionParams = get() EventFactory(sessionParams.credentials) @@ -79,5 +87,9 @@ class RoomModule { DefaultSendService(roomId, get(), get()) as SendService } + factory { (roomId: String) -> + DefaultReadService(roomId, get(), get(), get()) as ReadService + } + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt new file mode 100644 index 00000000..e5d762eb --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt @@ -0,0 +1,59 @@ +/* + * 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.room.read + +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.room.read.ReadService +import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.query.latestEvent +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith +import im.vector.matrix.android.internal.util.fetchManaged + +internal class DefaultReadService(private val roomId: String, + private val monarchy: Monarchy, + private val setReadMarkersTask: SetReadMarkersTask, + private val taskExecutor: TaskExecutor) : ReadService { + + override fun markLatestAsRead(callback: MatrixCallback) { + val lastEvent = getLatestEvent() + val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = null, readReceiptEventId = lastEvent?.eventId) + setReadMarkersTask.configureWith(params).executeBy(taskExecutor) + } + + override fun markAllAsRead(callback: MatrixCallback) { + val lastEvent = getLatestEvent() + val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = lastEvent?.eventId, readReceiptEventId = null) + setReadMarkersTask.configureWith(params).executeBy(taskExecutor) + } + + override fun setReadReceipt(eventId: String, callback: MatrixCallback) { + val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = null, readReceiptEventId = eventId) + setReadMarkersTask.configureWith(params).executeBy(taskExecutor) + } + + override fun setReadMarkers(fullyReadEventId: String, readReceiptEventId: String?, callback: MatrixCallback) { + val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = fullyReadEventId, readReceiptEventId = readReceiptEventId) + setReadMarkersTask.configureWith(params).executeBy(taskExecutor) + } + + private fun getLatestEvent(): EventEntity? { + return monarchy.fetchManaged { EventEntity.latestEvent(it, roomId) } + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt new file mode 100644 index 00000000..2f639377 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt @@ -0,0 +1,51 @@ +/* + * 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.room.read + +import arrow.core.Try +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.task.Task + +internal interface SetReadMarkersTask : Task { + + data class Params( + val roomId: String, + val fullyReadEventId: String?, + val readReceiptEventId: String? + ) +} + +private const val READ_MARKER = "m.fully_read" +private const val READ_RECEIPT = "m.read" + +internal class DefaultSetReadMarkersTask(private val roomAPI: RoomAPI +) : SetReadMarkersTask { + + override fun execute(params: SetReadMarkersTask.Params): Try { + val markers = HashMap() + if (params.fullyReadEventId?.isNotEmpty() == true) { + markers[READ_MARKER] = params.fullyReadEventId + } + if (params.readReceiptEventId?.isNotEmpty() == true) { + markers[READ_RECEIPT] = params.readReceiptEventId + } + return executeRequest { + apiCall = roomAPI.sendReadMarker(params.roomId, markers) + } + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/Monarchy.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/Monarchy.kt index 24813320..8750385e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/Monarchy.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/Monarchy.kt @@ -19,6 +19,8 @@ package im.vector.matrix.android.internal.util import arrow.core.Try import com.zhuinden.monarchy.Monarchy import io.realm.Realm +import io.realm.RealmModel +import java.util.concurrent.atomic.AtomicReference internal fun Monarchy.tryTransactionSync(transaction: (realm: Realm) -> Unit): Try { return Try { @@ -30,4 +32,13 @@ internal fun Monarchy.tryTransactionAsync(transaction: (realm: Realm) -> Unit): return Try { this.writeAsync(transaction) } +} + +fun Monarchy.fetchManaged(query: (Realm) -> T?): T? { + val ref = AtomicReference() + doWithRealm { realm -> + val result = query.invoke(realm) + ref.set(result) + } + return ref.get() } \ No newline at end of file From 0f66fa089517f5bec0f33f28c2db54434231763f Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 28 Jan 2019 18:42:29 +0100 Subject: [PATCH 03/11] Room : refactoring how a room is created --- .../matrix/android/api/session/room/Room.kt | 6 -- .../android/api/session/room/RoomService.kt | 2 +- .../internal/database/mapper/RoomMapper.kt | 37 ----------- .../android/internal/session/SessionModule.kt | 4 +- .../internal/session/room/DefaultRoom.kt | 53 ++++------------ .../session/room/DefaultRoomService.kt | 11 ++-- .../session/room/RoomAvatarResolver.kt | 13 ++-- .../internal/session/room/RoomFactory.kt | 61 +++++++++++++++++++ .../internal/session/room/RoomModule.kt | 25 +------- .../session/room/RoomSummaryUpdater.kt | 21 +++---- .../room/members/RoomDisplayNameResolver.kt | 21 ++++--- 11 files changed, 110 insertions(+), 144 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMapper.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt index fb27b29a..85714bb2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt @@ -17,7 +17,6 @@ package im.vector.matrix.android.api.session.room import androidx.lifecycle.LiveData -import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.read.ReadService import im.vector.matrix.android.api.session.room.send.SendService @@ -34,11 +33,6 @@ interface Room : TimelineService, SendService, ReadService { */ val roomId: String - /** - * The membership of this room for the current user - */ - val myMembership: MyMembership - /** * A live [RoomSummary] associated with the room */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt index 85f1c2f8..79b514fd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt @@ -27,7 +27,7 @@ interface RoomService { /** * Get a room from a roomId * @param roomId the roomId to look for. - * @return the room with roomId or null + * @return a room with roomId or null */ fun getRoom(roomId: String): Room? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMapper.kt deleted file mode 100644 index bfedb78d..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMapper.kt +++ /dev/null @@ -1,37 +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.matrix.android.internal.database.mapper - -import im.vector.matrix.android.api.session.room.Room -import im.vector.matrix.android.internal.database.model.RoomEntity -import im.vector.matrix.android.internal.session.room.DefaultRoom - - -internal object RoomMapper { - - - fun map(roomEntity: RoomEntity): Room { - return DefaultRoom( - roomEntity.roomId, - roomEntity.membership - ) - } -} - -internal fun RoomEntity.asDomain(): Room { - return RoomMapper.map(this) -} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 460a899b..5655886e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -28,6 +28,7 @@ import im.vector.matrix.android.internal.session.group.DefaultGroupService import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater import im.vector.matrix.android.internal.session.room.DefaultRoomService import im.vector.matrix.android.internal.session.room.RoomAvatarResolver +import im.vector.matrix.android.internal.session.room.RoomFactory import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.members.RoomMemberDisplayNameResolver @@ -84,10 +85,9 @@ internal class SessionModule(private val sessionParams: SessionParams) { } scope(DefaultSession.SCOPE) { - DefaultRoomService(get()) as RoomService + DefaultRoomService(get(), get()) as RoomService } - scope(DefaultSession.SCOPE) { DefaultGroupService(get()) as GroupService } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt index 5b716d4e..e241a9f6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt @@ -19,39 +19,35 @@ package im.vector.matrix.android.internal.session.room import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations import com.zhuinden.monarchy.Monarchy -import im.vector.matrix.android.api.MatrixCallback -import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.model.Membership -import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.read.ReadService import im.vector.matrix.android.api.session.room.send.SendService -import im.vector.matrix.android.api.session.room.timeline.TimelineData import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.database.mapper.asDomain 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.where -import im.vector.matrix.android.internal.di.MatrixKoinComponent import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith -import org.koin.core.parameter.parametersOf -import org.koin.standalone.inject internal data class DefaultRoom( override val roomId: String, - override val myMembership: MyMembership -) : Room, MatrixKoinComponent { + private val loadRoomMembersTask: LoadRoomMembersTask, + private val monarchy: Monarchy, + private val timelineService: TimelineService, + private val sendService: SendService, + private val readService: ReadService, + private val taskExecutor: TaskExecutor - private val loadRoomMembersTask by inject() - private val monarchy by inject() - private val timelineService by inject { parametersOf(roomId) } - private val sendService by inject { parametersOf(roomId) } - private val readService by inject { parametersOf(roomId) } - private val taskExecutor by inject() + +) : Room, + TimelineService by timelineService, + SendService by sendService, + ReadService by readService { override val roomSummary: LiveData by lazy { val liveData = monarchy @@ -64,35 +60,8 @@ internal data class DefaultRoom( } } - override fun timeline(eventId: String?): LiveData { - return timelineService.timeline(eventId) - } - override fun loadRoomMembersIfNeeded(): Cancelable { val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE) return loadRoomMembersTask.configureWith(params).executeBy(taskExecutor) } - - - override fun sendTextMessage(text: String, callback: MatrixCallback): Cancelable { - return sendService.sendTextMessage(text, callback) - } - - override fun setReadReceipt(eventId: String, callback: MatrixCallback) { - readService.setReadReceipt(eventId, callback) - } - - override fun setReadMarkers(fullyReadEventId: String, readReceiptEventId: String?, callback: MatrixCallback) { - readService.setReadMarkers(fullyReadEventId, readReceiptEventId, callback) - } - - override fun markAllAsRead(callback: MatrixCallback) { - readService.markAllAsRead(callback) - } - - override fun markLatestAsRead(callback: MatrixCallback) { - readService.markLatestAsRead(callback) - } - - } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt index 85f9c0e4..0b0bae8d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt @@ -26,15 +26,14 @@ import im.vector.matrix.android.internal.database.model.RoomEntity 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.where +import im.vector.matrix.android.internal.util.fetchManaged -internal class DefaultRoomService(private val monarchy: Monarchy) : RoomService { +internal class DefaultRoomService(private val monarchy: Monarchy, + private val roomFactory: RoomFactory) : RoomService { override fun getRoom(roomId: String): Room? { - var room: Room? = null - monarchy.doWithRealm { realm -> - room = RoomEntity.where(realm, roomId).findFirst()?.asDomain() - } - return room + monarchy.fetchManaged { RoomEntity.where(it, roomId).findFirst() } ?: return null + return roomFactory.instantiate(roomId) } override fun liveRoomSummaries(): LiveData> { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt index ee2b3613..235d18e7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt @@ -20,11 +20,11 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.RoomAvatarContent import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.query.last import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.room.members.RoomMembers @@ -34,20 +34,21 @@ internal class RoomAvatarResolver(private val monarchy: Monarchy, /** * Compute the room avatar url - * + * @param roomId the roomId of the room to resolve avatar * @return the room avatar url, can be a fallback to a room member avatar or null */ - fun resolve(room: Room): String? { + fun resolve(roomId: String): String? { var res: String? = null monarchy.doWithRealm { realm -> - val roomName = EventEntity.where(realm, room.roomId, EventType.STATE_ROOM_AVATAR).last()?.asDomain() + val roomEntity = RoomEntity.where(realm, roomId).findFirst() + val roomName = EventEntity.where(realm, roomId, EventType.STATE_ROOM_AVATAR).last()?.asDomain() res = roomName?.content.toModel()?.avatarUrl if (!res.isNullOrEmpty()) { return@doWithRealm } - val roomMembers = RoomMembers(realm, room.roomId) + val roomMembers = RoomMembers(realm, roomId) val members = roomMembers.getLoaded() - if (room.myMembership == MyMembership.INVITED) { + if (roomEntity?.membership == MyMembership.INVITED) { if (members.size == 1) { res = members.entries.first().value.avatarUrl } else if (members.size > 1) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt new file mode 100644 index 00000000..82c2f024 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt @@ -0,0 +1,61 @@ +/* + * 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.room + +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.room.Room +import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask +import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor +import im.vector.matrix.android.internal.session.room.read.DefaultReadService +import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask +import im.vector.matrix.android.internal.session.room.send.DefaultSendService +import im.vector.matrix.android.internal.session.room.send.EventFactory +import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService +import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask +import im.vector.matrix.android.internal.session.room.timeline.PaginationTask +import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.util.PagingRequestHelper +import java.util.concurrent.Executors + +internal class RoomFactory(private val loadRoomMembersTask: LoadRoomMembersTask, + private val monarchy: Monarchy, + private val paginationTask: PaginationTask, + private val contextOfEventTask: GetContextOfEventTask, + private val setReadMarkersTask: SetReadMarkersTask, + private val eventFactory: EventFactory, + private val taskExecutor: TaskExecutor) { + + fun instantiate(roomId: String): Room { + val helper = PagingRequestHelper(Executors.newSingleThreadExecutor()) + val timelineBoundaryCallback = TimelineBoundaryCallback(roomId, taskExecutor, paginationTask, monarchy, helper) + val roomMemberExtractor = RoomMemberExtractor(monarchy, roomId) + val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineBoundaryCallback, contextOfEventTask, roomMemberExtractor) + val sendService = DefaultSendService(roomId, eventFactory, monarchy) + val readService = DefaultReadService(roomId, monarchy, setReadMarkersTask, taskExecutor) + return DefaultRoom( + roomId, + loadRoomMembersTask, + monarchy, + timelineService, + sendService, + readService, + taskExecutor + ) + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index 65eb76b9..df870b23 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -17,29 +17,19 @@ package im.vector.matrix.android.internal.session.room import im.vector.matrix.android.api.auth.data.SessionParams -import im.vector.matrix.android.api.session.room.read.ReadService -import im.vector.matrix.android.api.session.room.send.SendService -import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.internal.session.DefaultSession import im.vector.matrix.android.internal.session.room.members.DefaultLoadRoomMembersTask import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask -import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor -import im.vector.matrix.android.internal.session.room.read.DefaultReadService import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask -import im.vector.matrix.android.internal.session.room.send.DefaultSendService import im.vector.matrix.android.internal.session.room.send.EventFactory import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask -import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask import im.vector.matrix.android.internal.session.room.timeline.PaginationTask -import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor -import im.vector.matrix.android.internal.util.PagingRequestHelper import org.koin.dsl.module.module import retrofit2.Retrofit -import java.util.concurrent.Executors class RoomModule { @@ -76,19 +66,8 @@ class RoomModule { EventFactory(sessionParams.credentials) } - factory { (roomId: String) -> - val helper = PagingRequestHelper(Executors.newSingleThreadExecutor()) - val timelineBoundaryCallback = TimelineBoundaryCallback(roomId, get(), get(), get(), helper) - val roomMemberExtractor = RoomMemberExtractor(get(), roomId) - DefaultTimelineService(roomId, get(), get(), timelineBoundaryCallback, get(), roomMemberExtractor) as TimelineService - } - - factory { (roomId: String) -> - DefaultSendService(roomId, get(), get()) as SendService - } - - factory { (roomId: String) -> - DefaultReadService(roomId, get(), get(), get()) as ReadService + scope(DefaultSession.SCOPE) { + RoomFactory(get(), get(), get(), get(), get(), get(), get()) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index f414847a..bb998328 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -21,7 +21,6 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.model.RoomTopicContent import im.vector.matrix.android.internal.database.RealmLiveEntityObserver import im.vector.matrix.android.internal.database.mapper.asDomain @@ -45,26 +44,26 @@ internal class RoomSummaryUpdater(monarchy: Monarchy, override val query = Monarchy.Query { RoomEntity.where(it) } override fun process(inserted: List, updated: List, deleted: List) { - val rooms = (inserted + updated).map { it.asDomain() } + val rooms = (inserted + updated).map { it.roomId } monarchy.writeAsync { realm -> rooms.forEach { updateRoom(realm, it) } } } - private fun updateRoom(realm: Realm, room: Room?) { - if (room == null) { + private fun updateRoom(realm: Realm, roomId: String?) { + if (roomId == null) { return } - val roomSummary = RoomSummaryEntity.where(realm, room.roomId).findFirst() - ?: realm.createObject(room.roomId) + val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() + ?: realm.createObject(roomId) - val lastMessageEvent = EventEntity.where(realm, room.roomId, EventType.MESSAGE).last() - val lastTopicEvent = EventEntity.where(realm, room.roomId, EventType.STATE_ROOM_TOPIC).last()?.asDomain() + val lastMessageEvent = EventEntity.where(realm, roomId, EventType.MESSAGE).last() + val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).last()?.asDomain() - val otherRoomMembers = RoomMembers(realm, room.roomId).getLoaded().filterKeys { it != credentials.userId } + val otherRoomMembers = RoomMembers(realm, roomId).getLoaded().filterKeys { it != credentials.userId } - roomSummary.displayName = roomDisplayNameResolver.resolve(context, room).toString() - roomSummary.avatarUrl = roomAvatarResolver.resolve(room) + roomSummary.displayName = roomDisplayNameResolver.resolve(context, roomId).toString() + roomSummary.avatarUrl = roomAvatarResolver.resolve(roomId) roomSummary.topic = lastTopicEvent?.content.toModel()?.topic roomSummary.lastMessage = lastMessageEvent roomSummary.otherMemberIds.clear() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/RoomDisplayNameResolver.kt index 48cd8278..7eb0f85f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/RoomDisplayNameResolver.kt @@ -22,13 +22,13 @@ import im.vector.matrix.android.R import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.RoomAliasesContent import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent import im.vector.matrix.android.api.session.room.model.RoomNameContent import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.query.last import im.vector.matrix.android.internal.database.query.where @@ -45,10 +45,10 @@ internal class RoomDisplayNameResolver(private val monarchy: Monarchy, * Compute the room display name * * @param context - * @param room: the room to resolve the name of. + * @param roomId: the roomId to resolve the name of. * @return the room display name */ - fun resolve(context: Context, room: Room): CharSequence { + fun resolve(context: Context, roomId: String): CharSequence { // this algorithm is the one defined in // https://github.com/matrix-org/matrix-js-sdk/blob/develop/lib/models/room.js#L617 // calculateRoomName(room, userId) @@ -57,29 +57,30 @@ internal class RoomDisplayNameResolver(private val monarchy: Monarchy, // https://docs.google.com/document/d/11i14UI1cUz-OJ0knD5BFu7fmT6Fo327zvMYqfSAR7xs/edit#heading=h.qif6pkqyjgzn var name: CharSequence? = null monarchy.doWithRealm { realm -> - val roomName = EventEntity.where(realm, room.roomId, EventType.STATE_ROOM_NAME).last()?.asDomain() + val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() + val roomName = EventEntity.where(realm, roomId, EventType.STATE_ROOM_NAME).last()?.asDomain() name = roomName?.content.toModel()?.name if (!name.isNullOrEmpty()) { return@doWithRealm } - val canonicalAlias = EventEntity.where(realm, room.roomId, EventType.STATE_CANONICAL_ALIAS).last()?.asDomain() + val canonicalAlias = EventEntity.where(realm, roomId, EventType.STATE_CANONICAL_ALIAS).last()?.asDomain() name = canonicalAlias?.content.toModel()?.canonicalAlias if (!name.isNullOrEmpty()) { return@doWithRealm } - val aliases = EventEntity.where(realm, room.roomId, EventType.STATE_ROOM_ALIASES).last()?.asDomain() + val aliases = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).last()?.asDomain() name = aliases?.content.toModel()?.aliases?.firstOrNull() if (!name.isNullOrEmpty()) { return@doWithRealm } - val roomMembers = RoomMembers(realm, room.roomId) + val roomMembers = RoomMembers(realm, roomId) val otherRoomMembers = roomMembers.getLoaded() .filterKeys { it != credentials.userId } - if (room.myMembership == MyMembership.INVITED) { + if (roomEntity?.membership == MyMembership.INVITED) { //TODO handle invited /* if (currentUser != null @@ -94,7 +95,7 @@ internal class RoomDisplayNameResolver(private val monarchy: Monarchy, name = context.getString(R.string.room_displayname_room_invite) } else { - val roomSummary = RoomSummaryEntity.where(realm, room.roomId).findFirst() + val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() val memberIds = if (roomSummary?.heroes?.isNotEmpty() == true) { roomSummary.heroes } else { @@ -125,6 +126,6 @@ internal class RoomDisplayNameResolver(private val monarchy: Monarchy, } return@doWithRealm } - return name ?: room.roomId + return name ?: roomId } } From d3d536f4f037f49fe719786283a2af1a6b160bbf Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 29 Jan 2019 14:16:22 +0100 Subject: [PATCH 04/11] Room : add RoomTag to handle favourites, low priority and system alerts in room list. --- .../riotredesign/features/home/HomeModule.kt | 5 ++ .../home/room/list/RoomListFragment.kt | 6 +- .../home/room/list/RoomListViewModel.kt | 46 +++++++++--- .../home/room/list/RoomListViewState.kt | 7 +- .../home/room/list/RoomSummaryController.kt | 71 +++++++++++++------ app/src/main/res/values/strings.xml | 16 +++-- .../matrix/android/api/session/room/Room.kt | 1 + .../api/session/room/model/RoomSummary.kt | 5 +- .../api/session/room/model/tag/RoomTag.kt | 31 ++++++++ .../session/room/model/tag/RoomTagContent.kt | 25 +++++++ .../database/mapper/RoomSummaryMapper.kt | 7 +- .../database/model/RoomSummaryEntity.kt | 3 +- .../internal/database/model/RoomTagEntity.kt | 28 ++++++++ .../internal/session/sync/RoomSyncHandler.kt | 17 ++++- .../internal/session/sync/RoomTagHandler.kt | 50 +++++++++++++ .../internal/session/sync/SyncModule.kt | 6 +- .../session/sync/model/RoomSyncAccountData.kt | 2 +- 17 files changed, 276 insertions(+), 50 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/tag/RoomTag.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/tag/RoomTagContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomTagEntity.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomTagHandler.kt diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt index ae703990..b8370270 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt @@ -27,6 +27,7 @@ import im.vector.riotredesign.features.home.room.detail.timeline.TimelineDateFor import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import im.vector.riotredesign.features.home.room.detail.timeline.TimelineItemFactory import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider +import im.vector.riotredesign.features.home.room.list.RoomSummaryController import org.koin.dsl.module.module class HomeModule { @@ -65,6 +66,10 @@ class HomeModule { HomeNavigator() } + factory { + RoomSummaryController(get()) + } + factory { (roomId: String) -> TimelineEventController(roomId, get(), get(), get()) } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt index 8aa095cd..904cf66c 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt @@ -22,7 +22,6 @@ import android.text.TextWatcher import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.inputmethod.EditorInfo import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Success @@ -30,7 +29,6 @@ import com.airbnb.mvrx.activityViewModel import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotredesign.R -import im.vector.riotredesign.core.extensions.hideKeyboard import im.vector.riotredesign.core.extensions.setupAsSearch import im.vector.riotredesign.core.platform.RiotFragment import im.vector.riotredesign.core.platform.StateView @@ -47,8 +45,8 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback { } private val homeNavigator by inject() + private val roomController by inject() private val homeViewModel: RoomListViewModel by activityViewModel() - private lateinit var roomController: RoomSummaryController override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_room_list, container, false) @@ -56,7 +54,7 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - roomController = RoomSummaryController(this) + roomController.callback = this stateView.contentView = epoxyRecyclerView epoxyRecyclerView.setController(roomController) setupFilterView() diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt index 5028bc5d..f53e1a34 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.matrix.rx.rx import im.vector.riotredesign.core.platform.RiotViewModel import im.vector.riotredesign.features.home.group.SelectedGroupHolder @@ -96,15 +97,7 @@ class RoomListViewModel(initialState: RoomListViewState, selectedGroupHolder.selectedGroup(), roomListFilter, Function3 { rooms, selectedGroupOption, filterRoomOption -> - val filterRoom = filterRoomOption.orNull() - val filteredRooms = rooms.filter { - if (filterRoom.isNullOrBlank()) { - true - } else { - it.displayName.contains(other = filterRoom, ignoreCase = true) - } - } - + val filteredRooms = filterRooms(rooms, filterRoomOption) val selectedGroup = selectedGroupOption.orNull() val filteredDirectRooms = filteredRooms .filter { it.isDirect } @@ -123,7 +116,7 @@ class RoomListViewModel(initialState: RoomListViewState, .filter { selectedGroup?.roomIds?.contains(it.roomId) ?: true } - RoomSummaries(filteredDirectRooms, filteredGroupRooms) + buildRoomSummaries(filteredDirectRooms + filteredGroupRooms) } ) .execute { async -> @@ -132,4 +125,37 @@ class RoomListViewModel(initialState: RoomListViewState, ) } } + + private fun filterRooms(rooms: List, filterRoomOption: Option): List { + val filterRoom = filterRoomOption.orNull() + return rooms.filter { + if (filterRoom.isNullOrBlank()) { + true + } else { + it.displayName.contains(other = filterRoom, ignoreCase = true) + } + } + } + + private fun buildRoomSummaries(rooms: List): RoomSummaries { + val favourites = ArrayList() + val directChats = ArrayList() + val groupRooms = ArrayList() + val lowPriorities = ArrayList() + val serverNotices = ArrayList() + + for (room in rooms) { + val tags = room.tags.map { it.name } + when { + 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(favourites, directChats, groupRooms, lowPriorities, serverNotices) + } + + } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt index 5b6d6f25..57d0be76 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt @@ -27,10 +27,13 @@ data class RoomListViewState( ) : MvRxState data class RoomSummaries( + val favourites: List, val directRooms: List, - val groupRooms: List + val groupRooms: List, + val lowPriorities: List, + val serverNotices: List ) fun RoomSummaries?.isNullOrEmpty(): Boolean { - return this == null || (directRooms.isEmpty() && groupRooms.isEmpty()) + return this == null || (directRooms.isEmpty() && groupRooms.isEmpty() && favourites.isEmpty() && lowPriorities.isEmpty() && serverNotices.isEmpty()) } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt index c8922e72..4a6c8a7b 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt @@ -16,47 +16,74 @@ package im.vector.riotredesign.features.home.room.list +import androidx.annotation.StringRes import com.airbnb.epoxy.TypedEpoxyController import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.riotredesign.R +import im.vector.riotredesign.core.resources.StringProvider -class RoomSummaryController(private val callback: Callback? = null +class RoomSummaryController(private val stringProvider: StringProvider ) : TypedEpoxyController() { private var isDirectRoomsExpanded = true private var isGroupRoomsExpanded = true + private var isFavoriteRoomsExpanded = true + private var isLowPriorityRoomsExpanded = true + private var isServerNoticeRoomsExpanded = true + + var callback: Callback? = null override fun buildModels(viewState: RoomListViewState) { val roomSummaries = viewState.asyncRooms() - RoomCategoryItem( - title = "DIRECT MESSAGES", - isExpanded = isDirectRoomsExpanded, - listener = { - isDirectRoomsExpanded = !isDirectRoomsExpanded - setData(viewState) - } - ) - .id("direct_messages") - .addTo(this) + buildRoomCategory(viewState, R.string.room_list_favourites, isFavoriteRoomsExpanded) { + isFavoriteRoomsExpanded = !isFavoriteRoomsExpanded + } + if (isFavoriteRoomsExpanded) { + buildRoomModels(roomSummaries?.favourites ?: emptyList(), viewState.selectedRoomId) + } + + buildRoomCategory(viewState, R.string.room_list_direct, isDirectRoomsExpanded) { + isDirectRoomsExpanded = !isDirectRoomsExpanded + } if (isDirectRoomsExpanded) { buildRoomModels(roomSummaries?.directRooms ?: emptyList(), viewState.selectedRoomId) } - RoomCategoryItem( - title = "GROUPS", - isExpanded = isGroupRoomsExpanded, - listener = { - isGroupRoomsExpanded = !isGroupRoomsExpanded - setData(viewState) - } - ) - .id("group_messages") - .addTo(this) - + buildRoomCategory(viewState, R.string.room_list_group, isGroupRoomsExpanded) { + isGroupRoomsExpanded = !isGroupRoomsExpanded + } if (isGroupRoomsExpanded) { buildRoomModels(roomSummaries?.groupRooms ?: emptyList(), viewState.selectedRoomId) } + buildRoomCategory(viewState, R.string.room_list_low_priority, isLowPriorityRoomsExpanded) { + isLowPriorityRoomsExpanded = !isLowPriorityRoomsExpanded + } + if (isLowPriorityRoomsExpanded) { + buildRoomModels(roomSummaries?.lowPriorities ?: emptyList(), viewState.selectedRoomId) + } + + buildRoomCategory(viewState, R.string.room_list_system_alert, isServerNoticeRoomsExpanded) { + isServerNoticeRoomsExpanded = !isServerNoticeRoomsExpanded + } + if (isServerNoticeRoomsExpanded) { + buildRoomModels(roomSummaries?.serverNotices ?: emptyList(), viewState.selectedRoomId) + } + + } + + private fun buildRoomCategory(viewState: RoomListViewState, @StringRes titleRes: Int, isExpanded: Boolean, mutateExpandedState: () -> Unit) { + RoomCategoryItem( + title = stringProvider.getString(titleRes).toUpperCase(), + isExpanded = isExpanded, + listener = { + mutateExpandedState() + setData(viewState) + } + ) + .id(titleRes) + .addTo(this) } private fun buildRoomModels(summaries: List, selectedRoomId: String?) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f51599c2..62328533 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,9 +1,15 @@ - Riot X + "Riot X" - Réessayer - Pas de connexion internet - Une erreur est survenue - Rejoignez une room pour commencer à utiliser l\'application + "Retry" + "No network connection" + "An error occurred" + + "Join a room to start using the app." + "Favourites" + "People" + "Rooms" + "Low priority" + "System Alerts" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt index 85714bb2..20ec0d53 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt @@ -35,6 +35,7 @@ interface Room : TimelineService, SendService, ReadService { /** * A live [RoomSummary] associated with the room + * You can observe this summary to get dynamic data from this room. */ val roomSummary: LiveData diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt index 3ebe2604..507de556 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt @@ -16,6 +16,8 @@ package im.vector.matrix.android.api.session.room.model +import im.vector.matrix.android.api.session.room.model.tag.RoomTag + /** * This class holds some data of a room. * It can be retrieved by [im.vector.matrix.android.api.session.room.Room] and [im.vector.matrix.android.api.session.room.RoomService] @@ -28,5 +30,6 @@ data class RoomSummary( val isDirect: Boolean, val otherMemberIds: List = emptyList(), var notificationCount: Int = 0, - var highlightCount: Int = 0 + var highlightCount: Int = 0, + var tags: List = emptyList() ) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/tag/RoomTag.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/tag/RoomTag.kt new file mode 100644 index 00000000..0623e8d9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/tag/RoomTag.kt @@ -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.api.session.room.model.tag + +data class RoomTag( + val name: String, + val order: Double? +) { + + companion object { + val ROOM_TAG_FAVOURITE = "m.favourite" + val ROOM_TAG_LOW_PRIORITY = "m.lowpriority" + val ROOM_TAG_NO_TAG = "m.recent" + val ROOM_TAG_SERVER_NOTICE = "m.server_notice" + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/tag/RoomTagContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/tag/RoomTagContent.kt new file mode 100644 index 00000000..a5f7650b --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/tag/RoomTagContent.kt @@ -0,0 +1,25 @@ +/* + * 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.api.session.room.model.tag + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class RoomTagContent( + @Json(name = "tags") val tags: Map> = emptyMap() +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt index 9522b90a..5ef8fb48 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt @@ -17,12 +17,16 @@ package im.vector.matrix.android.internal.database.mapper import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.matrix.android.internal.database.model.RoomSummaryEntity internal object RoomSummaryMapper { fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary { + val tags = roomSummaryEntity.tags.map { + RoomTag(it.tagName, it.tagOrder) + } return RoomSummary( roomId = roomSummaryEntity.roomId, displayName = roomSummaryEntity.displayName ?: "", @@ -31,7 +35,8 @@ internal object RoomSummaryMapper { isDirect = roomSummaryEntity.isDirect, otherMemberIds = roomSummaryEntity.otherMemberIds.toList(), highlightCount = roomSummaryEntity.highlightCount, - notificationCount = roomSummaryEntity.notificationCount + notificationCount = roomSummaryEntity.notificationCount, + tags = tags ) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt index 3ff5a0e9..9f9c66f0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt @@ -31,7 +31,8 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "", var isDirect: Boolean = false, var otherMemberIds: RealmList = RealmList(), var notificationCount: Int = 0, - var highlightCount: Int = 0 + var highlightCount: Int = 0, + var tags: RealmList = RealmList() ) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomTagEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomTagEntity.kt new file mode 100644 index 00000000..12ca29b1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomTagEntity.kt @@ -0,0 +1,28 @@ +/* + * 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.RealmObject + +internal open class RoomTagEntity( + var tagName: String = "", + var tagOrder: Double? = null +) : RealmObject() { + + companion object + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt index 77066c73..7cd703c7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.MyMembership +import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent import im.vector.matrix.android.internal.database.helper.addAll import im.vector.matrix.android.internal.database.helper.addOrUpdate import im.vector.matrix.android.internal.database.helper.addStateEvents @@ -33,6 +34,7 @@ import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync import im.vector.matrix.android.internal.session.sync.model.RoomSync +import im.vector.matrix.android.internal.session.sync.model.RoomSyncAccountData import im.vector.matrix.android.internal.session.sync.model.RoomSyncEphemeral import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications @@ -40,9 +42,9 @@ import im.vector.matrix.android.internal.session.sync.model.RoomsSyncResponse import io.realm.Realm import io.realm.kotlin.createObject - internal class RoomSyncHandler(private val monarchy: Monarchy, - private val readReceiptHandler: ReadReceiptHandler) { + private val readReceiptHandler: ReadReceiptHandler, + private val roomTagHandler: RoomTagHandler) { sealed class HandlingStrategy { data class JOINED(val data: Map) : HandlingStrategy() @@ -118,6 +120,10 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, handleEphemeral(realm, roomId, roomSync.ephemeral) } + if (roomSync.accountData != null && roomSync.accountData.events.isNullOrEmpty().not()) { + handleRoomAccountDataEvents(realm, roomId, roomSync.accountData) + } + return roomEntity } @@ -207,4 +213,11 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, } + private fun handleRoomAccountDataEvents(realm: Realm, roomId: String, accountData: RoomSyncAccountData) { + accountData.events + .filter { it.type == EventType.TAG } + .map { it.content.toModel() } + .forEach { roomTagHandler.handle(realm, roomId, it) } + } + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomTagHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomTagHandler.kt new file mode 100644 index 00000000..c3722ea6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomTagHandler.kt @@ -0,0 +1,50 @@ +/* + * 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 + +import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent +import im.vector.matrix.android.internal.database.model.RoomSummaryEntity +import im.vector.matrix.android.internal.database.model.RoomTagEntity +import im.vector.matrix.android.internal.database.query.where +import io.realm.Realm +import java.util.* + +internal class RoomTagHandler { + + fun handle(realm: Realm, roomId: String, content: RoomTagContent?) { + if (content == null) { + return + } + val tags = ArrayList() + for (tagName in content.tags.keys) { + val params = content.tags[tagName] + val tag = if (params != null) { + RoomTagEntity(tagName, params["order"]) + } else { + RoomTagEntity(tagName, null) + } + tags.add(tag) + } + val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() + ?: RoomSummaryEntity(roomId) + + roomSummaryEntity.tags.clear() + roomSummaryEntity.tags.addAll(tags) + realm.insertOrUpdate(roomSummaryEntity) + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt index 9a296607..c62bc2e1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt @@ -36,7 +36,11 @@ internal class SyncModule { } scope(DefaultSession.SCOPE) { - RoomSyncHandler(get(), get()) + RoomTagHandler() + } + + scope(DefaultSession.SCOPE) { + RoomSyncHandler(get(), get(), get()) } scope(DefaultSession.SCOPE) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/RoomSyncAccountData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/RoomSyncAccountData.kt index 07b323b2..6c2e79f1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/RoomSyncAccountData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/RoomSyncAccountData.kt @@ -25,5 +25,5 @@ internal data class RoomSyncAccountData( /** * List of account data events (array of Event). */ - @Json(name = "events") val events: List? = null + @Json(name = "events") val events: List = emptyList() ) \ No newline at end of file From 0e7596e0aa96beef2398c213f4e2df71d0d4eeac Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 29 Jan 2019 14:39:23 +0100 Subject: [PATCH 05/11] Room list : add basic notifications count on category item. Need to add business logic later. --- .../home/room/list/RoomCategoryItem.kt | 4 +++ .../home/room/list/RoomSummaryController.kt | 33 ++++++++++++------- app/src/main/res/layout/item_room.xml | 6 ++-- .../main/res/layout/item_room_category.xml | 23 +++++++++++-- 4 files changed, 49 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomCategoryItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomCategoryItem.kt index c5d3044d..7fcc0210 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomCategoryItem.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomCategoryItem.kt @@ -26,9 +26,12 @@ import im.vector.riotredesign.core.epoxy.KotlinModel data class RoomCategoryItem( val title: CharSequence, val isExpanded: Boolean, + val unreadCount: Int, + val showHighlighted: Boolean, val listener: (() -> Unit)? = null ) : KotlinModel(R.layout.item_room_category) { + private val unreadCounterBadgeView by bind(R.id.roomCategoryUnreadCounterBadgeView) private val titleView by bind(R.id.roomCategoryTitleView) private val rootView by bind(R.id.roomCategoryRootView) @@ -41,6 +44,7 @@ data class RoomCategoryItem( val expandedArrowDrawable = ContextCompat.getDrawable(rootView.context, expandedArrowDrawableRes)?.also { DrawableCompat.setTint(it, tintColor) } + unreadCounterBadgeView.render(unreadCount, showHighlighted) titleView.setCompoundDrawablesWithIntrinsicBounds(expandedArrowDrawable, null, null, null) titleView.text = title rootView.setOnClickListener { listener?.invoke() } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt index 4a6c8a7b..931a0f6c 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt @@ -35,48 +35,57 @@ class RoomSummaryController(private val stringProvider: StringProvider override fun buildModels(viewState: RoomListViewState) { val roomSummaries = viewState.asyncRooms() - - buildRoomCategory(viewState, R.string.room_list_favourites, isFavoriteRoomsExpanded) { + val favourites = roomSummaries?.favourites ?: emptyList() + buildRoomCategory(viewState, favourites, R.string.room_list_favourites, isFavoriteRoomsExpanded) { isFavoriteRoomsExpanded = !isFavoriteRoomsExpanded } if (isFavoriteRoomsExpanded) { - buildRoomModels(roomSummaries?.favourites ?: emptyList(), viewState.selectedRoomId) + buildRoomModels(favourites, viewState.selectedRoomId) } - buildRoomCategory(viewState, R.string.room_list_direct, isDirectRoomsExpanded) { + val directRooms = roomSummaries?.directRooms ?: emptyList() + buildRoomCategory(viewState, directRooms, R.string.room_list_direct, isDirectRoomsExpanded) { isDirectRoomsExpanded = !isDirectRoomsExpanded } if (isDirectRoomsExpanded) { - buildRoomModels(roomSummaries?.directRooms ?: emptyList(), viewState.selectedRoomId) + buildRoomModels(directRooms, viewState.selectedRoomId) } - buildRoomCategory(viewState, R.string.room_list_group, isGroupRoomsExpanded) { + val groupRooms = roomSummaries?.groupRooms ?: emptyList() + buildRoomCategory(viewState, groupRooms, R.string.room_list_group, isGroupRoomsExpanded) { isGroupRoomsExpanded = !isGroupRoomsExpanded } if (isGroupRoomsExpanded) { - buildRoomModels(roomSummaries?.groupRooms ?: emptyList(), viewState.selectedRoomId) + buildRoomModels(groupRooms, viewState.selectedRoomId) } - buildRoomCategory(viewState, R.string.room_list_low_priority, isLowPriorityRoomsExpanded) { + val lowPriorities = roomSummaries?.lowPriorities ?: emptyList() + buildRoomCategory(viewState, lowPriorities, R.string.room_list_low_priority, isLowPriorityRoomsExpanded) { isLowPriorityRoomsExpanded = !isLowPriorityRoomsExpanded } if (isLowPriorityRoomsExpanded) { - buildRoomModels(roomSummaries?.lowPriorities ?: emptyList(), viewState.selectedRoomId) + buildRoomModels(lowPriorities, viewState.selectedRoomId) } - buildRoomCategory(viewState, R.string.room_list_system_alert, isServerNoticeRoomsExpanded) { + val serverNotices = roomSummaries?.serverNotices ?: emptyList() + buildRoomCategory(viewState, serverNotices, R.string.room_list_system_alert, isServerNoticeRoomsExpanded) { isServerNoticeRoomsExpanded = !isServerNoticeRoomsExpanded } if (isServerNoticeRoomsExpanded) { - buildRoomModels(roomSummaries?.serverNotices ?: emptyList(), viewState.selectedRoomId) + buildRoomModels(serverNotices, viewState.selectedRoomId) } } - private fun buildRoomCategory(viewState: RoomListViewState, @StringRes titleRes: Int, isExpanded: Boolean, mutateExpandedState: () -> Unit) { + private fun buildRoomCategory(viewState: RoomListViewState, summaries: List, @StringRes titleRes: Int, isExpanded: Boolean, mutateExpandedState: () -> Unit) { + //TODO should add some business logic later + val unreadCount = summaries.map { it.notificationCount }.reduce { acc, i -> acc + i } + val showHighlighted = summaries.any { it.highlightCount > 0 } RoomCategoryItem( title = stringProvider.getString(titleRes).toUpperCase(), isExpanded = isExpanded, + unreadCount = unreadCount, + showHighlighted = showHighlighted, listener = { mutateExpandedState() setData(viewState) diff --git a/app/src/main/res/layout/item_room.xml b/app/src/main/res/layout/item_room.xml index c0148ee7..c07164d1 100644 --- a/app/src/main/res/layout/item_room.xml +++ b/app/src/main/res/layout/item_room.xml @@ -47,10 +47,12 @@ + + Date: Tue, 29 Jan 2019 16:02:42 +0100 Subject: [PATCH 06/11] Room list : add quick ordering --- .../riotredesign/features/home/HomeModule.kt | 5 ++ .../home/room/list/RoomListViewModel.kt | 15 +++- .../home/room/list/RoomSummaryComparator.kt | 70 +++++++++++++++++++ .../home/room/list/RoomSummaryController.kt | 14 ++-- .../api/session/room/model/RoomSummary.kt | 2 + .../database/mapper/RoomSummaryMapper.kt | 1 + .../database/query/EventEntityQueries.kt | 10 ++- .../session/room/RoomSummaryUpdater.kt | 5 +- 8 files changed, 109 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryComparator.kt diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt index b8370270..3b3bd51d 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt @@ -27,6 +27,7 @@ import im.vector.riotredesign.features.home.room.detail.timeline.TimelineDateFor import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import im.vector.riotredesign.features.home.room.detail.timeline.TimelineItemFactory import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider +import im.vector.riotredesign.features.home.room.list.RoomSummaryComparator import im.vector.riotredesign.features.home.room.list.RoomSummaryController import org.koin.dsl.module.module @@ -90,6 +91,10 @@ class HomeModule { HomePermalinkHandler(get()) } + single { + RoomSummaryComparator() + } + } } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt index f53e1a34..d8edf515 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt @@ -40,7 +40,8 @@ class RoomListViewModel(initialState: RoomListViewState, private val session: Session, private val selectedGroupHolder: SelectedGroupHolder, private val visibleRoomHolder: VisibleRoomHolder, - private val roomSelectionRepository: RoomSelectionRepository) + private val roomSelectionRepository: RoomSelectionRepository, + private val roomSummaryComparator: RoomSummaryComparator) : RiotViewModel(initialState) { companion object : MvRxViewModelFactory { @@ -51,7 +52,8 @@ class RoomListViewModel(initialState: RoomListViewState, val roomSelectionRepository = viewModelContext.activity.get() val selectedGroupHolder = viewModelContext.activity.get() val visibleRoomHolder = viewModelContext.activity.get() - return RoomListViewModel(state, currentSession, selectedGroupHolder, visibleRoomHolder, roomSelectionRepository) + val roomSummaryComparator = viewModelContext.activity.get() + return RoomListViewModel(state, currentSession, selectedGroupHolder, visibleRoomHolder, roomSelectionRepository, roomSummaryComparator) } } @@ -154,7 +156,14 @@ class RoomListViewModel(initialState: RoomListViewState, else -> groupRooms.add(room) } } - return RoomSummaries(favourites, directChats, groupRooms, lowPriorities, serverNotices) + + return RoomSummaries( + favourites = favourites.sortedWith(roomSummaryComparator), + directRooms = directChats.sortedWith(roomSummaryComparator), + groupRooms = groupRooms.sortedWith(roomSummaryComparator), + lowPriorities = lowPriorities.sortedWith(roomSummaryComparator), + serverNotices = serverNotices.sortedWith(roomSummaryComparator) + ) } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryComparator.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryComparator.kt new file mode 100644 index 00000000..8d92b694 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryComparator.kt @@ -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.riotredesign.features.home.room.list + +import im.vector.matrix.android.api.session.room.model.RoomSummary + +class RoomSummaryComparator + : Comparator { + + override fun compare(leftRoomSummary: RoomSummary?, rightRoomSummary: RoomSummary?): Int { + val retValue: Int + var leftHighlightCount = 0 + var rightHighlightCount = 0 + var leftNotificationCount = 0 + var rightNotificationCount = 0 + var rightTimestamp = 0L + var leftTimestamp = 0L + + if (null != leftRoomSummary) { + leftHighlightCount = leftRoomSummary.highlightCount + leftNotificationCount = leftRoomSummary.notificationCount + leftTimestamp = leftRoomSummary.lastMessage?.originServerTs ?: 0 + } + if (null != rightRoomSummary) { + rightHighlightCount = rightRoomSummary.highlightCount + rightNotificationCount = rightRoomSummary.notificationCount + rightTimestamp = rightRoomSummary.lastMessage?.originServerTs ?: 0 + } + + if (leftRoomSummary?.lastMessage == null) { + retValue = 1 + } else if (rightRoomSummary?.lastMessage == null) { + retValue = -1 + } else if (rightHighlightCount > 0 && leftHighlightCount == 0) { + retValue = 1 + } else if (rightHighlightCount == 0 && leftHighlightCount > 0) { + retValue = -1 + } else if (rightNotificationCount > 0 && leftNotificationCount == 0) { + retValue = 1 + } else if (rightNotificationCount == 0 && leftNotificationCount > 0) { + retValue = -1 + } else { + val deltaTimestamp = rightTimestamp - leftTimestamp + if (deltaTimestamp > 0) { + retValue = 1 + } else if (deltaTimestamp < 0) { + retValue = -1 + } else { + retValue = 0 + } + } + return retValue + + } + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt index 931a0f6c..cc69d375 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt @@ -25,11 +25,11 @@ import im.vector.riotredesign.core.resources.StringProvider class RoomSummaryController(private val stringProvider: StringProvider ) : TypedEpoxyController() { - private var isDirectRoomsExpanded = true - private var isGroupRoomsExpanded = true private var isFavoriteRoomsExpanded = true - private var isLowPriorityRoomsExpanded = true - private var isServerNoticeRoomsExpanded = true + private var isDirectRoomsExpanded = false + private var isGroupRoomsExpanded = false + private var isLowPriorityRoomsExpanded = false + private var isServerNoticeRoomsExpanded = false var callback: Callback? = null @@ -79,7 +79,11 @@ class RoomSummaryController(private val stringProvider: StringProvider private fun buildRoomCategory(viewState: RoomListViewState, summaries: List, @StringRes titleRes: Int, isExpanded: Boolean, mutateExpandedState: () -> Unit) { //TODO should add some business logic later - val unreadCount = summaries.map { it.notificationCount }.reduce { acc, i -> acc + i } + val unreadCount = if (summaries.isEmpty()) { + 0 + } else { + summaries.map { it.notificationCount }.reduce { acc, i -> acc + i } + } val showHighlighted = summaries.any { it.highlightCount > 0 } RoomCategoryItem( title = stringProvider.getString(titleRes).toUpperCase(), diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt index 507de556..bda5a54c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.api.session.room.model +import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.model.tag.RoomTag /** @@ -28,6 +29,7 @@ data class RoomSummary( val topic: String = "", val avatarUrl: String = "", val isDirect: Boolean, + val lastMessage: Event? = null, val otherMemberIds: List = emptyList(), var notificationCount: Int = 0, var highlightCount: Int = 0, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt index 5ef8fb48..9243d1de 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt @@ -33,6 +33,7 @@ internal object RoomSummaryMapper { topic = roomSummaryEntity.topic ?: "", avatarUrl = roomSummaryEntity.avatarUrl ?: "", isDirect = roomSummaryEntity.isDirect, + lastMessage = roomSummaryEntity.lastMessage?.asDomain(), otherMemberIds = roomSummaryEntity.otherMemberIds.toList(), highlightCount = roomSummaryEntity.highlightCount, notificationCount = roomSummaryEntity.notificationCount, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt index d6efe178..80e194e3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt @@ -50,9 +50,13 @@ internal fun EventEntity.Companion.where(realm: Realm, } internal fun EventEntity.Companion.latestEvent(realm: Realm, - roomId: String): EventEntity? { - val chunkEntity = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) - return chunkEntity?.events?.where()?.sort(EventEntityFields.DISPLAY_INDEX)?.findFirst() + roomId: String, + excludedTypes: List = emptyList()): EventEntity? { + val query = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.events?.where() + return query + ?.not()?.`in`(EventEntityFields.TYPE, excludedTypes.toTypedArray()) + ?.sort(EventEntityFields.DISPLAY_INDEX) + ?.findFirst() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index bb998328..3e5fe1b1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -28,6 +28,7 @@ import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.query.last +import im.vector.matrix.android.internal.database.query.latestEvent import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.members.RoomMembers @@ -57,7 +58,7 @@ internal class RoomSummaryUpdater(monarchy: Monarchy, val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId) - val lastMessageEvent = EventEntity.where(realm, roomId, EventType.MESSAGE).last() + val lastEvent = EventEntity.latestEvent(realm, roomId) val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).last()?.asDomain() val otherRoomMembers = RoomMembers(realm, roomId).getLoaded().filterKeys { it != credentials.userId } @@ -65,7 +66,7 @@ internal class RoomSummaryUpdater(monarchy: Monarchy, roomSummary.displayName = roomDisplayNameResolver.resolve(context, roomId).toString() roomSummary.avatarUrl = roomAvatarResolver.resolve(roomId) roomSummary.topic = lastTopicEvent?.content.toModel()?.topic - roomSummary.lastMessage = lastMessageEvent + roomSummary.lastMessage = lastEvent roomSummary.otherMemberIds.clear() roomSummary.otherMemberIds.addAll(otherRoomMembers.keys) } From 0e491af8abc8813c27fad05c1ba03e32d40646e1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 29 Jan 2019 18:31:11 +0100 Subject: [PATCH 07/11] Room list : adjust some params --- app/src/main/res/layout/item_room_category.xml | 2 +- .../android/internal/database/query/EventEntityQueries.kt | 7 ++++++- .../android/internal/session/room/RoomSummaryUpdater.kt | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/layout/item_room_category.xml b/app/src/main/res/layout/item_room_category.xml index 29c12dfa..f84f9c42 100644 --- a/app/src/main/res/layout/item_room_category.xml +++ b/app/src/main/res/layout/item_room_category.xml @@ -11,7 +11,7 @@ android:gravity="center_vertical" android:paddingLeft="16dp" android:paddingTop="8dp" - android:paddingRight="16dp" + android:paddingRight="8dp" android:paddingBottom="8dp" tools:background="@color/pale_grey"> diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt index 80e194e3..015a47ac 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt @@ -51,10 +51,15 @@ internal fun EventEntity.Companion.where(realm: Realm, internal fun EventEntity.Companion.latestEvent(realm: Realm, roomId: String, + includedTypes: List = emptyList(), excludedTypes: List = emptyList()): EventEntity? { val query = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.events?.where() + if (includedTypes.isNotEmpty()) { + query?.`in`(EventEntityFields.TYPE, includedTypes.toTypedArray()) + } else if (excludedTypes.isNotEmpty()) { + query?.not()?.`in`(EventEntityFields.TYPE, excludedTypes.toTypedArray()) + } return query - ?.not()?.`in`(EventEntityFields.TYPE, excludedTypes.toTypedArray()) ?.sort(EventEntityFields.DISPLAY_INDEX) ?.findFirst() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index 3e5fe1b1..4d690832 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -58,7 +58,7 @@ internal class RoomSummaryUpdater(monarchy: Monarchy, val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId) - val lastEvent = EventEntity.latestEvent(realm, roomId) + val lastEvent = EventEntity.latestEvent(realm, roomId, includedTypes = listOf(EventType.MESSAGE)) val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).last()?.asDomain() val otherRoomMembers = RoomMembers(realm, roomId).getLoaded().filterKeys { it != credentials.userId } From bfaab52b41f4013d9414f89e6146ac065451c181 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 30 Jan 2019 12:55:35 +0100 Subject: [PATCH 08/11] Add generated versionCode from timestamp --- app/build.gradle | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index bc195796..1271c1ea 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,6 +11,19 @@ androidExtensions { experimental = true } +def versionMajor = 0 +def versionMinor = 1 +def versionPatch = 0 + +def generateVersionCodeFromTimestamp() { + // It's unix timestamp divided by 10: It's incremented by one every 10 seconds. + return (System.currentTimeMillis() / 1_000 / 10).toInteger() +} + +def generateVersionCodeFromVersionName() { + return versionMajor * 10000 + versionMinor * 100 + versionPatch +} + android { compileSdkVersion 28 defaultConfig { @@ -18,8 +31,8 @@ android { minSdkVersion 16 targetSdkVersion 28 multiDexEnabled true - versionCode 1 - versionName "1.0" + versionCode generateVersionCodeFromTimestamp() + versionName "${versionMajor}.${versionMinor}.${versionPatch}" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { From 6113bba703aa76e781d58def8d06e634684b940d Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 30 Jan 2019 18:39:54 +0100 Subject: [PATCH 09/11] Timeline : send read-receipt when scrolling. Still need to handle read marker. --- .../riotredesign/core/epoxy/KotlinModel.kt | 16 ++++- .../home/room/detail/RoomDetailActions.kt | 3 + .../home/room/detail/RoomDetailFragment.kt | 12 +++- .../home/room/detail/RoomDetailViewModel.kt | 37 ++++++++++-- .../timeline/TimelineEventController.kt | 12 +++- .../detail/timeline/TimelineItemFactory.kt | 6 +- .../api/session/room/read/ReadService.kt | 16 ++++- .../android/internal/di/NetworkModule.kt | 9 +-- .../internal/network/UnitConverterFactory.kt | 35 +++++++++++ .../android/internal/session/SessionModule.kt | 4 ++ .../session/room/DefaultRoomService.kt | 4 +- .../android/internal/session/room/RoomAPI.kt | 2 +- .../internal/session/room/RoomFactory.kt | 2 + .../internal/session/room/RoomModule.kt | 8 +-- .../session/room/read/DefaultReadService.kt | 19 +++--- .../session/room/read/SetReadMarkersTask.kt | 58 +++++++++++++++++-- .../matrix/android/internal/util/Monarchy.kt | 4 +- 17 files changed, 198 insertions(+), 49 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/UnitConverterFactory.kt diff --git a/app/src/main/java/im/vector/riotredesign/core/epoxy/KotlinModel.kt b/app/src/main/java/im/vector/riotredesign/core/epoxy/KotlinModel.kt index ef4dd32b..dd651468 100644 --- a/app/src/main/java/im/vector/riotredesign/core/epoxy/KotlinModel.kt +++ b/app/src/main/java/im/vector/riotredesign/core/epoxy/KotlinModel.kt @@ -16,10 +16,11 @@ package im.vector.riotredesign.core.epoxy +import android.view.View import androidx.annotation.IdRes import androidx.annotation.LayoutRes -import android.view.View import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.OnModelVisibilityStateChangedListener import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty @@ -29,6 +30,7 @@ abstract class KotlinModel( private var view: View? = null private var onBindCallback: (() -> Unit)? = null + private var onModelVisibilityStateChangedListener: OnModelVisibilityStateChangedListener? = null abstract fun bind() @@ -47,6 +49,16 @@ abstract class KotlinModel( return this } + override fun onVisibilityStateChanged(visibilityState: Int, view: View) { + onModelVisibilityStateChangedListener?.onVisibilityStateChanged(this, view, visibilityState) + super.onVisibilityStateChanged(visibilityState, view) + } + + fun setOnVisibilityStateChanged(listener: OnModelVisibilityStateChangedListener): KotlinModel { + this.onModelVisibilityStateChangedListener = listener + return this + } + override fun getDefaultLayout() = layoutRes protected fun bind(@IdRes id: Int) = object : ReadOnlyProperty { @@ -56,7 +68,7 @@ abstract class KotlinModel( // be optimized with a map @Suppress("UNCHECKED_CAST") return view?.findViewById(id) as V? - ?: throw IllegalStateException("View ID $id for '${property.name}' not found.") + ?: throw IllegalStateException("View ID $id for '${property.name}' not found.") } } } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt index 7946e89c..f3ca3b3d 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt @@ -16,9 +16,12 @@ package im.vector.riotredesign.features.home.room.detail +import im.vector.matrix.android.api.session.room.timeline.TimelineEvent + sealed class RoomDetailActions { data class SendMessage(val text: String) : RoomDetailActions() object IsDisplayed : RoomDetailActions() + data class EventDisplayed(val event: TimelineEvent, val index: Int) : RoomDetailActions() } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 80d56cbf..76f91c7b 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -23,9 +23,11 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.airbnb.epoxy.EpoxyVisibilityTracker import com.airbnb.mvrx.Success import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel +import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotredesign.R import im.vector.riotredesign.core.platform.RiotFragment import im.vector.riotredesign.core.platform.ToolbarConfigurable @@ -75,7 +77,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { override fun onResume() { super.onResume() - roomDetailViewModel.accept(RoomDetailActions.IsDisplayed) + roomDetailViewModel.process(RoomDetailActions.IsDisplayed) } private fun setupToolbar() { @@ -86,6 +88,8 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { } private fun setupRecyclerView() { + val epoxyVisibilityTracker = EpoxyVisibilityTracker() + epoxyVisibilityTracker.attach(recyclerView) val layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true) scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager) recyclerView.layoutManager = layoutManager @@ -100,7 +104,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { val textMessage = composerEditText.text.toString() if (textMessage.isNotBlank()) { composerEditText.text = null - roomDetailViewModel.accept(RoomDetailActions.SendMessage(textMessage)) + roomDetailViewModel.process(RoomDetailActions.SendMessage(textMessage)) } } } @@ -143,4 +147,8 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { homePermalinkHandler.launch(url) } + override fun onEventVisible(event: TimelineEvent, index: Int) { + roomDetailViewModel.process(RoomDetailActions.EventDisplayed(event, index)) + } + } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt index 57ca3508..abdf1a1c 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -18,6 +18,7 @@ package im.vector.riotredesign.features.home.room.detail import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext +import com.jakewharton.rxrelay2.BehaviorRelay import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session @@ -25,7 +26,9 @@ import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.rx.rx import im.vector.riotredesign.core.platform.RiotViewModel import im.vector.riotredesign.features.home.room.VisibleRoomHolder +import io.reactivex.rxkotlin.subscribeBy import org.koin.android.ext.android.get +import java.util.concurrent.TimeUnit class RoomDetailViewModel(initialState: RoomDetailViewState, private val session: Session, @@ -36,6 +39,8 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, private val roomId = initialState.roomId private val eventId = initialState.eventId + private val displayedEventsObservable = BehaviorRelay.create() + companion object : MvRxViewModelFactory { @JvmStatic @@ -49,14 +54,15 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, init { observeRoomSummary() observeTimeline() + observeDisplayedEvents() room.loadRoomMembersIfNeeded() - room.markLatestAsRead(callback = object : MatrixCallback {}) } - fun accept(action: RoomDetailActions) { + fun process(action: RoomDetailActions) { when (action) { - is RoomDetailActions.SendMessage -> handleSendMessage(action) - is RoomDetailActions.IsDisplayed -> visibleRoomHolder.setVisibleRoom(roomId) + is RoomDetailActions.SendMessage -> handleSendMessage(action) + is RoomDetailActions.IsDisplayed -> handleIsDisplayed() + is RoomDetailActions.EventDisplayed -> handleEventDisplayed(action) } } @@ -66,6 +72,29 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, room.sendTextMessage(action.text, callback = object : MatrixCallback {}) } + private fun handleEventDisplayed(action: RoomDetailActions.EventDisplayed) { + displayedEventsObservable.accept(action) + } + + private fun handleIsDisplayed() { + visibleRoomHolder.setVisibleRoom(roomId) + } + + private fun observeDisplayedEvents() { + // We are buffering scroll events for one second + // and keep the most recent one to set the read receipt on. + displayedEventsObservable.hide() + .buffer(1, TimeUnit.SECONDS) + .filter { it.isNotEmpty() } + .subscribeBy { actions -> + val mostRecentEvent = actions.minBy { it.index } + mostRecentEvent?.event?.root?.eventId?.let { eventId -> + room.setReadReceipt(eventId, callback = object : MatrixCallback {}) + } + } + .disposeOnClear() + } + private fun observeRoomSummary() { room.rx().liveRoomSummary() .execute { async -> diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt index 12dcb895..8fc268c3 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt @@ -16,12 +16,16 @@ package im.vector.riotredesign.features.home.room.detail.timeline +import android.view.View import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.EpoxyAsyncUtil import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.OnModelVisibilityStateChangedListener +import com.airbnb.epoxy.VisibilityState import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineData +import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.riotredesign.core.epoxy.KotlinModel import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.features.home.LoadingItemModel_ import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider @@ -74,6 +78,11 @@ class TimelineEventController(private val roomId: String, timelineItemFactory.create(event, nextEvent, callback)?.also { it.id(event.localId) + it.setOnVisibilityStateChanged(OnModelVisibilityStateChangedListener { model, view, visibilityState -> + if (visibilityState == VisibilityState.VISIBLE) { + callback?.onEventVisible(event, currentPosition) + } + }) epoxyModels.add(it) } if (addDaySeparator) { @@ -98,6 +107,7 @@ class TimelineEventController(private val roomId: String, interface Callback { + fun onEventVisible(event: TimelineEvent, index: Int) fun onUrlClicked(url: String) } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineItemFactory.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineItemFactory.kt index 8c38e2ec..cc30b010 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineItemFactory.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineItemFactory.kt @@ -16,9 +16,9 @@ package im.vector.riotredesign.features.home.room.detail.timeline -import com.airbnb.epoxy.EpoxyModel import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.riotredesign.core.epoxy.KotlinModel class TimelineItemFactory(private val messageItemFactory: MessageItemFactory, private val roomNameItemFactory: RoomNameItemFactory, @@ -28,14 +28,14 @@ class TimelineItemFactory(private val messageItemFactory: MessageItemFactory, fun create(event: TimelineEvent, nextEvent: TimelineEvent?, - callback: TimelineEventController.Callback?): EpoxyModel<*>? { + callback: TimelineEventController.Callback?): KotlinModel? { return when (event.root.type) { EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, callback) EventType.STATE_ROOM_NAME -> roomNameItemFactory.create(event) EventType.STATE_ROOM_TOPIC -> roomTopicItemFactory.create(event) EventType.STATE_ROOM_MEMBER -> roomMemberItemFactory.create(event) - else -> defaultItemFactory.create(event) + else -> defaultItemFactory.create(event) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/read/ReadService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/read/ReadService.kt index 102967e7..b7b78bc8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/read/ReadService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/read/ReadService.kt @@ -18,14 +18,24 @@ package im.vector.matrix.android.api.session.room.read import im.vector.matrix.android.api.MatrixCallback +/** + * This interface defines methods to handle read receipts and read marker in a room. It's implemented at the room level. + */ interface ReadService { - fun markLatestAsRead(callback: MatrixCallback) - + /** + * Force the read marker to be set on the latest event. + */ fun markAllAsRead(callback: MatrixCallback) + /** + * Set the read receipt on the event with provided eventId. + */ fun setReadReceipt(eventId: String, callback: MatrixCallback) - fun setReadMarkers(fullyReadEventId: String, readReceiptEventId: String?, callback: MatrixCallback) + /** + * Set the read marker on the event with provided eventId. + */ + fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback) } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt index 97037a92..d9abf791 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt @@ -18,11 +18,11 @@ package im.vector.matrix.android.internal.di import im.vector.matrix.android.internal.network.AccessTokenInterceptor import im.vector.matrix.android.internal.network.NetworkConnectivityChecker +import im.vector.matrix.android.internal.network.UnitConverterFactory import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import okreplay.OkReplayInterceptor import org.koin.dsl.module.module -import retrofit2.Converter import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import timber.log.Timber @@ -62,10 +62,6 @@ class NetworkModule { MoshiProvider.providesMoshi() } - single { - MoshiConverterFactory.create(get()) as Converter.Factory - } - single { NetworkConnectivityChecker(get()) } @@ -73,7 +69,8 @@ class NetworkModule { factory { Retrofit.Builder() .client(get()) - .addConverterFactory(get()) + .addConverterFactory(UnitConverterFactory) + .addConverterFactory(MoshiConverterFactory.create(get())) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/UnitConverterFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/UnitConverterFactory.kt new file mode 100644 index 00000000..74d9623a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/UnitConverterFactory.kt @@ -0,0 +1,35 @@ +/* + * 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.network + +import okhttp3.ResponseBody +import retrofit2.Converter +import retrofit2.Retrofit +import java.lang.reflect.Type + +object UnitConverterFactory : Converter.Factory() { + override fun responseBodyConverter(type: Type, annotations: Array, + retrofit: Retrofit): Converter? { + return if (type == Unit::class.java) UnitConverter else null + } + + private object UnitConverter : Converter { + override fun convert(value: ResponseBody) { + value.close() + } + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 5655886e..91da7dd5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -47,6 +47,10 @@ internal class SessionModule(private val sessionParams: SessionParams) { sessionParams } + scope(DefaultSession.SCOPE) { + sessionParams.credentials + } + scope(DefaultSession.SCOPE) { val context = get() val childPath = sessionParams.credentials.userId.md5() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt index 0b0bae8d..b1462789 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt @@ -26,13 +26,13 @@ import im.vector.matrix.android.internal.database.model.RoomEntity 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.where -import im.vector.matrix.android.internal.util.fetchManaged +import im.vector.matrix.android.internal.util.fetchCopied internal class DefaultRoomService(private val monarchy: Monarchy, private val roomFactory: RoomFactory) : RoomService { override fun getRoom(roomId: String): Room? { - monarchy.fetchManaged { RoomEntity.where(it, roomId).findFirst() } ?: return null + monarchy.fetchCopied { RoomEntity.where(it, roomId).findFirst() } ?: return null return roomFactory.instantiate(roomId) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt index bba9c134..ecb4d42d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt @@ -112,7 +112,7 @@ internal interface RoomAPI { * @param markers the read markers */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/read_markers") - fun sendReadMarker(@Path("roomId") roomId: String, @Body markers: Map): Call + fun sendReadMarker(@Path("roomId") roomId: String, @Body markers: Map): Call } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt index 82c2f024..6b2c796c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.session.room import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor @@ -34,6 +35,7 @@ import java.util.concurrent.Executors internal class RoomFactory(private val loadRoomMembersTask: LoadRoomMembersTask, private val monarchy: Monarchy, + private val credentials: Credentials, private val paginationTask: PaginationTask, private val contextOfEventTask: GetContextOfEventTask, private val setReadMarkersTask: SetReadMarkersTask, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index df870b23..716fd155 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.internal.session.room -import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.internal.session.DefaultSession import im.vector.matrix.android.internal.session.room.members.DefaultLoadRoomMembersTask import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask @@ -58,16 +57,15 @@ class RoomModule { } scope(DefaultSession.SCOPE) { - DefaultSetReadMarkersTask(get()) as SetReadMarkersTask + DefaultSetReadMarkersTask(get(), get(),get()) as SetReadMarkersTask } scope(DefaultSession.SCOPE) { - val sessionParams = get() - EventFactory(sessionParams.credentials) + EventFactory(get()) } scope(DefaultSession.SCOPE) { - RoomFactory(get(), get(), get(), get(), get(), get(), get()) + RoomFactory(get(), get(), get(), get(), get(), get(), get(), get()) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt index e5d762eb..467e6117 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt @@ -23,22 +23,16 @@ import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.query.latestEvent import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith -import im.vector.matrix.android.internal.util.fetchManaged +import im.vector.matrix.android.internal.util.fetchCopied internal class DefaultReadService(private val roomId: String, private val monarchy: Monarchy, private val setReadMarkersTask: SetReadMarkersTask, private val taskExecutor: TaskExecutor) : ReadService { - override fun markLatestAsRead(callback: MatrixCallback) { - val lastEvent = getLatestEvent() - val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = null, readReceiptEventId = lastEvent?.eventId) - setReadMarkersTask.configureWith(params).executeBy(taskExecutor) - } - override fun markAllAsRead(callback: MatrixCallback) { - val lastEvent = getLatestEvent() - val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = lastEvent?.eventId, readReceiptEventId = null) + val latestEvent = getLatestEvent() + val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = latestEvent?.eventId, readReceiptEventId = latestEvent?.eventId) setReadMarkersTask.configureWith(params).executeBy(taskExecutor) } @@ -47,13 +41,14 @@ internal class DefaultReadService(private val roomId: String, setReadMarkersTask.configureWith(params).executeBy(taskExecutor) } - override fun setReadMarkers(fullyReadEventId: String, readReceiptEventId: String?, callback: MatrixCallback) { - val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = fullyReadEventId, readReceiptEventId = readReceiptEventId) + override fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback) { + val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = fullyReadEventId, readReceiptEventId = null) setReadMarkersTask.configureWith(params).executeBy(taskExecutor) } private fun getLatestEvent(): EventEntity? { - return monarchy.fetchManaged { EventEntity.latestEvent(it, roomId) } + return monarchy.fetchCopied { EventEntity.latestEvent(it, roomId) } } + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt index 2f639377..92e44155 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt @@ -17,11 +17,22 @@ package im.vector.matrix.android.internal.session.room.read import arrow.core.Try +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.auth.data.Credentials +import im.vector.matrix.android.internal.database.model.ChunkEntity +import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.ReadReceiptEntity +import im.vector.matrix.android.internal.database.model.RoomSummaryEntity +import im.vector.matrix.android.internal.database.query.find +import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom +import im.vector.matrix.android.internal.database.query.latestEvent +import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task +import im.vector.matrix.android.internal.util.tryTransactionAsync -internal interface SetReadMarkersTask : Task { +internal interface SetReadMarkersTask : Task { data class Params( val roomId: String, @@ -33,19 +44,54 @@ internal interface SetReadMarkersTask : Task { private const val READ_MARKER = "m.fully_read" private const val READ_RECEIPT = "m.read" -internal class DefaultSetReadMarkersTask(private val roomAPI: RoomAPI +internal class DefaultSetReadMarkersTask(private val roomAPI: RoomAPI, + private val credentials: Credentials, + private val monarchy: Monarchy ) : SetReadMarkersTask { - override fun execute(params: SetReadMarkersTask.Params): Try { + override fun execute(params: SetReadMarkersTask.Params): Try { val markers = HashMap() if (params.fullyReadEventId?.isNotEmpty() == true) { markers[READ_MARKER] = params.fullyReadEventId } - if (params.readReceiptEventId?.isNotEmpty() == true) { + if (params.readReceiptEventId?.isNotEmpty() == true && !isEventRead(params.roomId, params.readReceiptEventId)) { + updateNotificationCountIfNecessary(params.roomId, params.readReceiptEventId) markers[READ_RECEIPT] = params.readReceiptEventId } - return executeRequest { - apiCall = roomAPI.sendReadMarker(params.roomId, markers) + return if (markers.isEmpty()) { + Try.just(Unit) + } else { + executeRequest { + apiCall = roomAPI.sendReadMarker(params.roomId, markers) + } } } + + private fun updateNotificationCountIfNecessary(roomId: String, eventId: String) { + monarchy.tryTransactionAsync { realm -> + val isLatestReceived = EventEntity.latestEvent(realm, eventId)?.eventId == eventId + if (isLatestReceived) { + val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() + ?: return@tryTransactionAsync + roomSummary.notificationCount = 0 + roomSummary.highlightCount = 0 + } + } + } + + private fun isEventRead(roomId: String, eventId: String): Boolean { + var isEventRead = false + monarchy.doWithRealm { + val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst() + ?: return@doWithRealm + val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId) + ?: return@doWithRealm + val readReceiptIndex = liveChunk.events.find(readReceipt.eventId)?.displayIndex + ?: -1 + val eventToCheckIndex = liveChunk.events.find(eventId)?.displayIndex ?: -1 + isEventRead = eventToCheckIndex >= readReceiptIndex + } + return isEventRead + } + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/Monarchy.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/Monarchy.kt index 8750385e..809ba16b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/Monarchy.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/Monarchy.kt @@ -34,10 +34,10 @@ internal fun Monarchy.tryTransactionAsync(transaction: (realm: Realm) -> Unit): } } -fun Monarchy.fetchManaged(query: (Realm) -> T?): T? { +fun Monarchy.fetchCopied(query: (Realm) -> T?): T? { val ref = AtomicReference() doWithRealm { realm -> - val result = query.invoke(realm) + val result = query.invoke(realm)?.let { realm.copyFromRealm(it) } ref.set(result) } return ref.get() From 6c653287e14adc92bed3804b4e4c608277c48470 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 31 Jan 2019 11:45:11 +0100 Subject: [PATCH 10/11] Read receipts : fix read receipt being sent for dummyEvent + scroll appearing index --- .../riotredesign/core/extensions/Iterable.kt | 38 +++++++++++++++++++ .../home/room/detail/RoomDetailViewModel.kt | 7 ++-- .../session/room/read/SetReadMarkersTask.kt | 16 +++++--- 3 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/im/vector/riotredesign/core/extensions/Iterable.kt diff --git a/app/src/main/java/im/vector/riotredesign/core/extensions/Iterable.kt b/app/src/main/java/im/vector/riotredesign/core/extensions/Iterable.kt new file mode 100644 index 00000000..092a94ac --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/core/extensions/Iterable.kt @@ -0,0 +1,38 @@ +/* + * + * * 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.riotredesign.core.extensions + +/** + * Returns the last element yielding the smallest value of the given function or `null` if there are no elements. + */ +public inline fun > Iterable.lastMinBy(selector: (T) -> R): T? { + val iterator = iterator() + if (!iterator.hasNext()) return null + var minElem = iterator.next() + var minValue = selector(minElem) + while (iterator.hasNext()) { + val e = iterator.next() + val v = selector(e) + if (minValue >= v) { + minElem = e + minValue = v + } + } + return minElem +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt index abdf1a1c..3d1d4cf1 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.rx.rx +import im.vector.riotredesign.core.extensions.lastMinBy import im.vector.riotredesign.core.platform.RiotViewModel import im.vector.riotredesign.features.home.room.VisibleRoomHolder import io.reactivex.rxkotlin.subscribeBy @@ -86,12 +87,12 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, displayedEventsObservable.hide() .buffer(1, TimeUnit.SECONDS) .filter { it.isNotEmpty() } - .subscribeBy { actions -> - val mostRecentEvent = actions.minBy { it.index } + .subscribeBy(onNext = { actions -> + val mostRecentEvent = actions.lastMinBy { it.index } mostRecentEvent?.event?.root?.eventId?.let { eventId -> room.setReadReceipt(eventId, callback = object : MatrixCallback {}) } - } + }) .disposeOnClear() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt index 92e44155..f127966d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.room.read import arrow.core.Try import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.MatrixPatterns import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.EventEntity @@ -51,10 +52,13 @@ internal class DefaultSetReadMarkersTask(private val roomAPI: RoomAPI, override fun execute(params: SetReadMarkersTask.Params): Try { val markers = HashMap() - if (params.fullyReadEventId?.isNotEmpty() == true) { + if (params.fullyReadEventId != null && MatrixPatterns.isEventId(params.fullyReadEventId)) { markers[READ_MARKER] = params.fullyReadEventId } - if (params.readReceiptEventId?.isNotEmpty() == true && !isEventRead(params.roomId, params.readReceiptEventId)) { + if (params.readReceiptEventId != null + && MatrixPatterns.isEventId(params.readReceiptEventId) + && !isEventRead(params.roomId, params.readReceiptEventId)) { + updateNotificationCountIfNecessary(params.roomId, params.readReceiptEventId) markers[READ_RECEIPT] = params.readReceiptEventId } @@ -72,7 +76,7 @@ internal class DefaultSetReadMarkersTask(private val roomAPI: RoomAPI, val isLatestReceived = EventEntity.latestEvent(realm, eventId)?.eventId == eventId if (isLatestReceived) { val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() - ?: return@tryTransactionAsync + ?: return@tryTransactionAsync roomSummary.notificationCount = 0 roomSummary.highlightCount = 0 } @@ -83,11 +87,11 @@ internal class DefaultSetReadMarkersTask(private val roomAPI: RoomAPI, var isEventRead = false monarchy.doWithRealm { val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst() - ?: return@doWithRealm + ?: return@doWithRealm val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId) - ?: return@doWithRealm + ?: return@doWithRealm val readReceiptIndex = liveChunk.events.find(readReceipt.eventId)?.displayIndex - ?: -1 + ?: -1 val eventToCheckIndex = liveChunk.events.find(eventId)?.displayIndex ?: -1 isEventRead = eventToCheckIndex >= readReceiptIndex } From 020a2f3923e875757224bbb916417c7e853bb036 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 31 Jan 2019 12:13:51 +0100 Subject: [PATCH 11/11] Room list : add some throttle to avoid refreshing too often. --- .../features/home/room/list/RoomListViewModel.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt index d8edf515..68d41bae 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt @@ -33,6 +33,7 @@ import io.reactivex.Observable import io.reactivex.functions.Function3 import io.reactivex.rxkotlin.subscribeBy import org.koin.android.ext.android.get +import java.util.concurrent.TimeUnit typealias RoomListFilterName = CharSequence @@ -95,9 +96,9 @@ class RoomListViewModel(initialState: RoomListViewState, private fun observeRoomSummaries() { Observable.combineLatest, Option, Option, RoomSummaries>( - session.rx().liveRoomSummaries(), + session.rx().liveRoomSummaries().throttleLast(300, TimeUnit.MILLISECONDS), selectedGroupHolder.selectedGroup(), - roomListFilter, + roomListFilter.throttleLast(300, TimeUnit.MILLISECONDS), Function3 { rooms, selectedGroupOption, filterRoomOption -> val filteredRooms = filterRooms(rooms, filterRoomOption) val selectedGroup = selectedGroupOption.orNull()