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