Possibility to mark rooms as unread
Using com.famedly.marked_unnread, as per MSC2867 (https://github.com/matrix-org/matrix-doc/pull/2867) TODO: - Currently, when upgrading from an older version, already existing unread flags are ignored until cache is cleared manually Change-Id: I3b66fadb134c96f0eb428afd673035d790c16340
This commit is contained in:
parent
3defe44eff
commit
3fb2df76c2
|
@ -35,6 +35,7 @@ object EventType {
|
|||
const val PLUMBING = "m.room.plumbing"
|
||||
const val BOT_OPTIONS = "m.room.bot.options"
|
||||
const val PREVIEW_URLS = "org.matrix.room.preview_urls"
|
||||
const val MARKED_UNREAD = "com.famedly.marked_unread"
|
||||
|
||||
// State Events
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ data class RoomSummary constructor(
|
|||
val hasUnreadMessages: Boolean = false,
|
||||
val hasUnreadContentMessages: Boolean = false,
|
||||
val hasUnreadOriginalContentMessages: Boolean = false,
|
||||
val markedUnread: Boolean = false,
|
||||
val tags: List<RoomTag> = emptyList(),
|
||||
val membership: Membership = Membership.NONE,
|
||||
val versioningState: VersioningState = VersioningState.NONE,
|
||||
|
@ -78,6 +79,10 @@ data class RoomSummary constructor(
|
|||
val canStartCall: Boolean
|
||||
get() = joinedMembersCount == 2
|
||||
|
||||
fun scIsUnread(preferenceProvider: RoomSummaryPreferenceProvider?): Boolean {
|
||||
return markedUnread || scHasUnreadMessages(preferenceProvider)
|
||||
}
|
||||
|
||||
fun scHasUnreadMessages(preferenceProvider: RoomSummaryPreferenceProvider?): Boolean {
|
||||
if (preferenceProvider == null) {
|
||||
// Fallback to default
|
||||
|
|
|
@ -47,6 +47,11 @@ interface ReadService {
|
|||
*/
|
||||
fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>)
|
||||
|
||||
/**
|
||||
* Mark a room as unread, or remove an existing unread marker.
|
||||
*/
|
||||
fun setMarkedUnread(markedUnread: Boolean, callback: MatrixCallback<Unit>)
|
||||
|
||||
/**
|
||||
* Check if an event is already read, ie. your read receipt is set on a more recent event.
|
||||
*/
|
||||
|
|
|
@ -27,10 +27,20 @@ import javax.inject.Inject
|
|||
class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
|
||||
companion object {
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 5L
|
||||
// SC-specific DB changes on top of Element
|
||||
// 1: added markedUnread field
|
||||
const val SESSION_STORE_SCHEMA_SC_VERSION = 1L
|
||||
const val SESSION_STORE_SCHEMA_SC_VERSION_OFFSET = (1L shl 12)
|
||||
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 5L +
|
||||
SESSION_STORE_SCHEMA_SC_VERSION * SESSION_STORE_SCHEMA_SC_VERSION_OFFSET
|
||||
|
||||
}
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
override fun migrate(realm: DynamicRealm, combinedOldVersion: Long, newVersion: Long) {
|
||||
val oldVersion = combinedOldVersion % SESSION_STORE_SCHEMA_SC_VERSION_OFFSET
|
||||
val oldScVersion = combinedOldVersion / SESSION_STORE_SCHEMA_SC_VERSION_OFFSET
|
||||
|
||||
Timber.v("Migrating Realm Session from $oldVersion to $newVersion")
|
||||
|
||||
if (oldVersion <= 0) migrateTo1(realm)
|
||||
|
@ -38,8 +48,19 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
|||
if (oldVersion <= 2) migrateTo3(realm)
|
||||
if (oldVersion <= 3) migrateTo4(realm)
|
||||
if (oldVersion <= 4) migrateTo5(realm)
|
||||
|
||||
if (oldScVersion <= 0) migrateToSc1(realm)
|
||||
}
|
||||
|
||||
// SC Version 1L added markedUnread
|
||||
private fun migrateToSc1(realm: DynamicRealm) {
|
||||
Timber.d("Step SC 0 -> 1")
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.MARKED_UNREAD, Boolean::class.java)
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun migrateTo1(realm: DynamicRealm) {
|
||||
Timber.d("Step 0 -> 1")
|
||||
// Add hasFailedSending in RoomSummary and a small warning icon on room list
|
||||
|
|
|
@ -60,6 +60,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
|
|||
hasUnreadMessages = roomSummaryEntity.hasUnreadMessages,
|
||||
hasUnreadContentMessages = roomSummaryEntity.hasUnreadContentMessages,
|
||||
hasUnreadOriginalContentMessages = roomSummaryEntity.hasUnreadOriginalContentMessages,
|
||||
markedUnread = roomSummaryEntity.markedUnread,
|
||||
tags = tags,
|
||||
typingUsers = typingUsers,
|
||||
membership = roomSummaryEntity.membership,
|
||||
|
|
|
@ -45,6 +45,7 @@ internal open class RoomSummaryEntity(
|
|||
var hasUnreadMessages: Boolean = false,
|
||||
var hasUnreadContentMessages: Boolean = false,
|
||||
var hasUnreadOriginalContentMessages: Boolean = false,
|
||||
var markedUnread: Boolean = false,
|
||||
var tags: RealmList<RoomTagEntity> = RealmList(),
|
||||
var userDrafts: UserDraftsEntity? = null,
|
||||
var breadcrumbsIndex: Int = RoomSummary.NOT_IN_BREADCRUMBS,
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity
|
|||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||
|
||||
internal fun isEventRead(realmConfiguration: RealmConfiguration,
|
||||
userId: String?,
|
||||
|
@ -75,3 +76,13 @@ internal fun isReadMarkerMoreRecent(realmConfiguration: RealmConfiguration,
|
|||
}
|
||||
}
|
||||
}
|
||||
internal fun isMarkedUnread(realmConfiguration: RealmConfiguration,
|
||||
roomId: String?): Boolean {
|
||||
if (roomId.isNullOrBlank()) {
|
||||
return false
|
||||
}
|
||||
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||
roomSummary?.markedUnread ?: false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,8 +49,10 @@ import org.matrix.android.sdk.internal.session.room.membership.leaving.LeaveRoom
|
|||
import org.matrix.android.sdk.internal.session.room.membership.threepid.DefaultInviteThreePidTask
|
||||
import org.matrix.android.sdk.internal.session.room.membership.threepid.InviteThreePidTask
|
||||
import org.matrix.android.sdk.internal.session.room.read.DefaultMarkAllRoomsReadTask
|
||||
import org.matrix.android.sdk.internal.session.room.read.DefaultSetMarkedUnreadTask
|
||||
import org.matrix.android.sdk.internal.session.room.read.DefaultSetReadMarkersTask
|
||||
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
|
||||
import org.matrix.android.sdk.internal.session.room.read.SetMarkedUnreadTask
|
||||
import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask
|
||||
import org.matrix.android.sdk.internal.session.room.relation.DefaultFetchEditHistoryTask
|
||||
import org.matrix.android.sdk.internal.session.room.relation.DefaultFindReactionEventForUndoTask
|
||||
|
@ -154,6 +156,9 @@ internal abstract class RoomModule {
|
|||
@Binds
|
||||
abstract fun bindMarkAllRoomsReadTask(task: DefaultMarkAllRoomsReadTask): MarkAllRoomsReadTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindSetMarkedUnreadTask(task: DefaultSetMarkedUnreadTask): SetMarkedUnreadTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindFindReactionEventForUndoTask(task: DefaultFindReactionEventForUndoTask): FindReactionEventForUndoTask
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ internal class DefaultReadService @AssistedInject constructor(
|
|||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val setReadMarkersTask: SetReadMarkersTask,
|
||||
private val setMarkedUnreadTask: SetMarkedUnreadTask,
|
||||
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
|
||||
@UserId private val userId: String
|
||||
) : ReadService {
|
||||
|
@ -62,6 +63,8 @@ internal class DefaultReadService @AssistedInject constructor(
|
|||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
// Automatically unset unread marker
|
||||
setMarkedUnreadFlag(false, callback)
|
||||
}
|
||||
|
||||
override fun setReadReceipt(eventId: String, callback: MatrixCallback<Unit>) {
|
||||
|
@ -82,6 +85,25 @@ internal class DefaultReadService @AssistedInject constructor(
|
|||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun setMarkedUnread(markedUnread: Boolean, callback: MatrixCallback<Unit>) {
|
||||
if (markedUnread) {
|
||||
setMarkedUnreadFlag(true, callback)
|
||||
} else {
|
||||
// We want to both remove unread marker and update read receipt position,
|
||||
// i.e., we want what markAsRead does
|
||||
markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT, callback)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setMarkedUnreadFlag(markedUnread: Boolean, callback: MatrixCallback<Unit>) {
|
||||
val params = SetMarkedUnreadTask.Params(roomId, markedUnread = markedUnread)
|
||||
setMarkedUnreadTask
|
||||
.configureWith(params) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun isEventRead(eventId: String): Boolean {
|
||||
return isEventRead(monarchy.realmConfiguration, userId, roomId, eventId)
|
||||
}
|
||||
|
|
|
@ -25,11 +25,14 @@ internal interface MarkAllRoomsReadTask : Task<MarkAllRoomsReadTask.Params, Unit
|
|||
)
|
||||
}
|
||||
|
||||
internal class DefaultMarkAllRoomsReadTask @Inject constructor(private val readMarkersTask: SetReadMarkersTask) : MarkAllRoomsReadTask {
|
||||
internal class DefaultMarkAllRoomsReadTask @Inject constructor(private val readMarkersTask: SetReadMarkersTask, private val markUnreadTask: SetMarkedUnreadTask) : MarkAllRoomsReadTask {
|
||||
|
||||
override suspend fun execute(params: MarkAllRoomsReadTask.Params) {
|
||||
params.roomIds.forEach { roomId ->
|
||||
readMarkersTask.execute(SetReadMarkersTask.Params(roomId, forceReadMarker = true, forceReadReceipt = true))
|
||||
}
|
||||
params.roomIds.forEach { roomId ->
|
||||
markUnreadTask.execute(SetMarkedUnreadTask.Params(roomId, markedUnread = false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.room.read
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MarkedUnreadContent(
|
||||
@Json(name = "unread") val markedUnread: Boolean
|
||||
)
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.room.read
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.internal.database.query.isMarkedUnread
|
||||
import org.matrix.android.sdk.internal.session.sync.RoomMarkedUnreadHandler
|
||||
import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataAPI
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface SetMarkedUnreadTask : Task<SetMarkedUnreadTask.Params, Unit> {
|
||||
|
||||
data class Params(
|
||||
val roomId: String,
|
||||
val markedUnread: Boolean,
|
||||
val markedUnreadContent: MarkedUnreadContent = MarkedUnreadContent(markedUnread)
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultSetMarkedUnreadTask @Inject constructor(
|
||||
private val accountDataApi: AccountDataAPI,
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val roomMarkedUnreadHandler: RoomMarkedUnreadHandler,
|
||||
@UserId private val userId: String,
|
||||
private val eventBus: EventBus
|
||||
) : SetMarkedUnreadTask {
|
||||
|
||||
override suspend fun execute(params: SetMarkedUnreadTask.Params) {
|
||||
Timber.v("Execute set marked unread with params: $params")
|
||||
|
||||
if (isMarkedUnread(monarchy.realmConfiguration, params.roomId) != params.markedUnread) {
|
||||
updateDatabase(params.roomId, params.markedUnread)
|
||||
executeRequest<Unit>(eventBus) {
|
||||
isRetryable = true
|
||||
apiCall = accountDataApi.setRoomAccountData(userId, params.roomId, EventType.MARKED_UNREAD, params.markedUnreadContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateDatabase(roomId: String, markedUnread: Boolean) {
|
||||
monarchy.awaitTransaction { realm ->
|
||||
roomMarkedUnreadHandler.handle(realm, roomId, MarkedUnreadContent(markedUnread))
|
||||
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||
?: return@awaitTransaction
|
||||
roomSummary.markedUnread = markedUnread
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.sync
|
||||
|
||||
import org.matrix.android.sdk.internal.session.room.read.MarkedUnreadContent
|
||||
import io.realm.Realm
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class RoomMarkedUnreadHandler @Inject constructor() {
|
||||
|
||||
fun handle(realm: Realm, roomId: String, content: MarkedUnreadContent?) {
|
||||
if (content == null) {
|
||||
return
|
||||
}
|
||||
Timber.v("Handle for roomId: $roomId markedUnread: ${content.markedUnread}")
|
||||
|
||||
RoomSummaryEntity.getOrCreate(realm, roomId).apply {
|
||||
markedUnread = content.markedUnread
|
||||
}
|
||||
}
|
||||
}
|
|
@ -54,6 +54,7 @@ import org.matrix.android.sdk.internal.session.mapWithProgress
|
|||
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
|
||||
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberEventHandler
|
||||
import org.matrix.android.sdk.internal.session.room.read.FullyReadContent
|
||||
import org.matrix.android.sdk.internal.session.room.read.MarkedUnreadContent
|
||||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
|
||||
import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimeline
|
||||
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
|
||||
|
@ -70,6 +71,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
private val roomSummaryUpdater: RoomSummaryUpdater,
|
||||
private val roomTagHandler: RoomTagHandler,
|
||||
private val roomFullyReadHandler: RoomFullyReadHandler,
|
||||
private val roomMarkedUnreadHandler: RoomMarkedUnreadHandler,
|
||||
private val cryptoService: DefaultCryptoService,
|
||||
private val roomMemberEventHandler: RoomMemberEventHandler,
|
||||
private val roomTypingUsersHandler: RoomTypingUsersHandler,
|
||||
|
@ -407,6 +409,9 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
} else if (eventType == EventType.FULLY_READ) {
|
||||
val content = event.getClearContent().toModel<FullyReadContent>()
|
||||
roomFullyReadHandler.handle(realm, roomId, content)
|
||||
} else if (eventType == EventType.MARKED_UNREAD) {
|
||||
val content = event.getClearContent().toModel<MarkedUnreadContent>()
|
||||
roomMarkedUnreadHandler.handle(realm, roomId, content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,4 +35,18 @@ interface AccountDataAPI {
|
|||
fun setAccountData(@Path("userId") userId: String,
|
||||
@Path("type") type: String,
|
||||
@Body params: Any): Call<Unit>
|
||||
|
||||
/**
|
||||
* Set some room account_data for the client.
|
||||
*
|
||||
* @param userId the user id
|
||||
* @param roomId the room id
|
||||
* @param type the type
|
||||
* @param params the put params
|
||||
*/
|
||||
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/account_data/{type}")
|
||||
fun setRoomAccountData(@Path("userId") userId: String,
|
||||
@Path("roomId") roomId: String,
|
||||
@Path("type") type: String,
|
||||
@Body params: Any): Call<Unit>
|
||||
}
|
||||
|
|
|
@ -63,8 +63,9 @@ class BreadcrumbsController @Inject constructor(
|
|||
avatarRenderer(avatarRenderer)
|
||||
matrixItem(it.toMatrixItem())
|
||||
unreadNotificationCount(it.notificationCount)
|
||||
markedUnread(it.markedUnread)
|
||||
showHighlighted(it.highlightCount > 0)
|
||||
hasUnreadMessage(it.scHasUnreadMessages(scSdkPreferences))
|
||||
hasUnreadMessage(it.scIsUnread(scSdkPreferences))
|
||||
hasDraft(it.userDrafts.isNotEmpty())
|
||||
itemClickListener(
|
||||
DebouncedClickListener(View.OnClickListener { _ ->
|
||||
|
|
|
@ -37,6 +37,7 @@ abstract class BreadcrumbsItem : VectorEpoxyModel<BreadcrumbsItem.Holder>() {
|
|||
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||
@EpoxyAttribute var unreadNotificationCount: Int = 0
|
||||
@EpoxyAttribute var unreadMessages: Int = 0 // SC addition
|
||||
@EpoxyAttribute var markedUnread: Boolean = false
|
||||
@EpoxyAttribute var showHighlighted: Boolean = false
|
||||
@EpoxyAttribute var hasUnreadMessage: Boolean = false
|
||||
@EpoxyAttribute var hasDraft: Boolean = false
|
||||
|
@ -47,7 +48,7 @@ abstract class BreadcrumbsItem : VectorEpoxyModel<BreadcrumbsItem.Holder>() {
|
|||
holder.rootView.setOnClickListener(itemClickListener)
|
||||
holder.unreadIndentIndicator.isVisible = hasUnreadMessage
|
||||
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted, unreadMessages))
|
||||
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted, unreadMessages, markedUnread))
|
||||
holder.draftIndentIndicator.isVisible = hasDraft
|
||||
holder.typingIndicator.isVisible = hasTypingUsers
|
||||
}
|
||||
|
|
|
@ -1217,7 +1217,7 @@ class RoomDetailFragment @Inject constructor(
|
|||
val inviter = state.asyncInviter()
|
||||
if (summary?.membership == Membership.JOIN) {
|
||||
jumpToBottomView.count = summary.notificationCount
|
||||
jumpToBottomView.drawBadge = summary.scHasUnreadMessages(ScSdkPreferences(context))
|
||||
jumpToBottomView.drawBadge = summary.scIsUnread(ScSdkPreferences(context))
|
||||
scrollOnHighlightedEventCallback.timeline = roomDetailViewModel.timeline
|
||||
timelineEventController.update(state)
|
||||
inviteView.visibility = View.GONE
|
||||
|
|
|
@ -35,6 +35,7 @@ abstract class RoomCategoryItem : VectorEpoxyModel<RoomCategoryItem.Holder>() {
|
|||
@EpoxyAttribute var unreadNotificationCount: Int = 0
|
||||
@EpoxyAttribute var unreadMessages: Int = 0
|
||||
@EpoxyAttribute var showHighlighted: Boolean = false
|
||||
@EpoxyAttribute var markedUnread: Boolean = false
|
||||
@EpoxyAttribute var listener: (() -> Unit)? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
|
@ -44,7 +45,7 @@ abstract class RoomCategoryItem : VectorEpoxyModel<RoomCategoryItem.Holder>() {
|
|||
val expandedArrowDrawable = ContextCompat.getDrawable(holder.rootView.context, expandedArrowDrawableRes)?.also {
|
||||
DrawableCompat.setTint(it, tintColor)
|
||||
}
|
||||
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted, unreadMessages))
|
||||
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted, unreadMessages, markedUnread))
|
||||
holder.titleView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null)
|
||||
holder.titleView.text = title
|
||||
holder.rootView.setOnClickListener { listener?.invoke() }
|
||||
|
|
|
@ -29,5 +29,6 @@ sealed class RoomListAction : VectorViewModelAction {
|
|||
data class ChangeRoomNotificationState(val roomId: String, val notificationState: RoomNotificationState) : RoomListAction()
|
||||
data class ToggleTag(val roomId: String, val tag: String) : RoomListAction()
|
||||
data class LeaveRoom(val roomId: String) : RoomListAction()
|
||||
data class SetMarkedUnread(val roomId: String, val markedUnread: Boolean) : RoomListAction()
|
||||
object MarkAllRoomsRead : RoomListAction()
|
||||
}
|
||||
|
|
|
@ -262,6 +262,12 @@ class RoomListFragment @Inject constructor(
|
|||
is RoomListQuickActionsSharedAction.LowPriority -> {
|
||||
roomListViewModel.handle(RoomListAction.ToggleTag(quickAction.roomId, RoomTag.ROOM_TAG_LOW_PRIORITY))
|
||||
}
|
||||
is RoomListQuickActionsSharedAction.MarkUnread -> {
|
||||
roomListViewModel.handle(RoomListAction.SetMarkedUnread(quickAction.roomId, true))
|
||||
}
|
||||
is RoomListQuickActionsSharedAction.MarkRead -> {
|
||||
roomListViewModel.handle(RoomListAction.SetMarkedUnread(quickAction.roomId, false))
|
||||
}
|
||||
is RoomListQuickActionsSharedAction.Leave -> {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.room_participants_leave_prompt_title)
|
||||
|
|
|
@ -84,6 +84,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
|||
is RoomListAction.LeaveRoom -> handleLeaveRoom(action)
|
||||
is RoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
|
||||
is RoomListAction.ToggleTag -> handleToggleTag(action)
|
||||
is RoomListAction.SetMarkedUnread -> handleSetMarkedUnread(action)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
|
@ -222,6 +223,18 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleSetMarkedUnread(action: RoomListAction.SetMarkedUnread) {
|
||||
session.getRoom(action.roomId)?.setMarkedUnread(action.markedUnread, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
_viewEvents.post(RoomListViewEvents.Done)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_viewEvents.post(RoomListViewEvents.Failure(failure))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun handleLeaveRoom(action: RoomListAction.LeaveRoom) {
|
||||
_viewEvents.post(RoomListViewEvents.Loading(null))
|
||||
session.getRoom(action.roomId)?.leave(null, object : MatrixCallback<Unit> {
|
||||
|
|
|
@ -130,7 +130,7 @@ data class RoomListViewState(
|
|||
get() = asyncFilteredRooms.invoke()
|
||||
?.flatMap { it.value }
|
||||
?.filter { it.membership == Membership.JOIN }
|
||||
?.any { it.scHasUnreadMessages(scSdkPreferences) }
|
||||
?.any { it.scIsUnread(scSdkPreferences) }
|
||||
?: false
|
||||
}
|
||||
|
||||
|
|
|
@ -134,14 +134,20 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
|
|||
val unreadCount = if (summaries.isEmpty()) {
|
||||
0
|
||||
} else {
|
||||
summaries.map { it.notificationCount }.sumBy { i -> i }
|
||||
// Count notifications + number of chats with no notifications marked as unread
|
||||
summaries.map { it }.sumBy { x -> if (x.notificationCount > 0) x.notificationCount else if (x.markedUnread) 1 else 0 }
|
||||
}
|
||||
val markedUnread = if (summaries.isEmpty()) {
|
||||
false
|
||||
} else {
|
||||
summaries.map { it.markedUnread }.sumBy { b -> if (b) 1 else 0 } > 0
|
||||
}
|
||||
// SC addition
|
||||
val unreadMessages = if (summaries.isEmpty() || !userPreferencesProvider.shouldShowUnimportantCounterBadge()) {
|
||||
0
|
||||
} else {
|
||||
// TODO actual sum of events instead of sum of chats?
|
||||
summaries.map { it.scHasUnreadMessages(scSdkPreferences) }.sumBy { b -> if (b) 1 else 0 }
|
||||
summaries.map { it.scIsUnread(scSdkPreferences) }.sumBy { b -> if (b) 1 else 0 }
|
||||
}
|
||||
val showHighlighted = summaries.any { it.highlightCount > 0 }
|
||||
roomCategoryItem {
|
||||
|
@ -151,6 +157,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
|
|||
unreadNotificationCount(unreadCount)
|
||||
unreadMessages(unreadMessages)
|
||||
showHighlighted(showHighlighted)
|
||||
markedUnread(markedUnread)
|
||||
listener {
|
||||
mutateExpandedState()
|
||||
update(viewState)
|
||||
|
|
|
@ -53,6 +53,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
|
|||
@EpoxyAttribute var encryptionTrustLevel: RoomEncryptionTrustLevel? = null
|
||||
@EpoxyAttribute var unreadNotificationCount: Int = 0
|
||||
@EpoxyAttribute var hasUnreadMessage: Boolean = false
|
||||
@EpoxyAttribute var markedUnread: Boolean = false
|
||||
@EpoxyAttribute var hasDraft: Boolean = false
|
||||
@EpoxyAttribute var showHighlighted: Boolean = false
|
||||
@EpoxyAttribute var hasFailedSending: Boolean = false
|
||||
|
@ -71,7 +72,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
|
|||
holder.lastEventTimeView.text = lastEventTime
|
||||
holder.lastEventView.text = lastFormattedEvent
|
||||
// SC-TODO: once we count unimportant unread messages, pass that as counter - for now, unreadIndentIndicator is enough, so pass 0 to the badge instead
|
||||
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted, 0))
|
||||
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted, 0, markedUnread))
|
||||
holder.unreadIndentIndicator.isVisible = hasUnreadMessage
|
||||
holder.draftView.isVisible = hasDraft
|
||||
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||
|
|
|
@ -105,7 +105,8 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
|||
.showSelected(showSelected)
|
||||
.hasFailedSending(roomSummary.hasFailedSending)
|
||||
.unreadNotificationCount(unreadCount)
|
||||
.hasUnreadMessage(roomSummary.scHasUnreadMessages(scSdkPreferences))
|
||||
.hasUnreadMessage(roomSummary.scIsUnread(scSdkPreferences))
|
||||
.markedUnread(roomSummary.markedUnread)
|
||||
.hasDraft(roomSummary.userDrafts.isNotEmpty())
|
||||
.itemLongClickListener { _ ->
|
||||
onLongClick?.invoke(roomSummary) ?: false
|
||||
|
|
|
@ -30,11 +30,11 @@ class UnreadCounterBadgeView : AppCompatTextView {
|
|||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||
|
||||
fun render(state: State) {
|
||||
if (state.count == 0 && state.unread == 0) {
|
||||
if (state.count == 0 && state.unread == 0 && !state.markedUnread) {
|
||||
visibility = View.INVISIBLE
|
||||
} else {
|
||||
visibility = View.VISIBLE
|
||||
val bgRes = if (state.count > 0) {
|
||||
val bgRes = if (state.count > 0 || state.markedUnread) {
|
||||
if (state.highlighted) {
|
||||
R.drawable.bg_unread_highlight
|
||||
} else {
|
||||
|
@ -44,7 +44,12 @@ class UnreadCounterBadgeView : AppCompatTextView {
|
|||
R.drawable.bg_unread_unimportant
|
||||
}
|
||||
setBackgroundResource(bgRes)
|
||||
text = RoomSummaryFormatter.formatUnreadMessagesCounter(if (state.count > 0) state.count else state.unread)
|
||||
text = if (state.count == 0 && state.markedUnread)
|
||||
// Centered star (instead of "*")
|
||||
//"\u2217"
|
||||
"!"
|
||||
else
|
||||
RoomSummaryFormatter.formatUnreadMessagesCounter(if (state.count > 0) state.count else state.unread)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,6 +57,7 @@ class UnreadCounterBadgeView : AppCompatTextView {
|
|||
val count: Int,
|
||||
val highlighted: Boolean,
|
||||
// SC addition
|
||||
val unread: Int
|
||||
val unread: Int,
|
||||
val markedUnread: Boolean
|
||||
)
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ import im.vector.app.core.epoxy.bottomsheet.bottomSheetRoomPreviewItem
|
|||
import im.vector.app.core.epoxy.dividerItem
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.ScSdkPreferences
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import javax.inject.Inject
|
||||
|
@ -31,7 +33,8 @@ import javax.inject.Inject
|
|||
*/
|
||||
class RoomListQuickActionsEpoxyController @Inject constructor(
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val stringProvider: StringProvider
|
||||
private val stringProvider: StringProvider,
|
||||
private val scSdkPreferences: ScSdkPreferences
|
||||
) : TypedEpoxyController<RoomListQuickActionsState>() {
|
||||
|
||||
var listener: Listener? = null
|
||||
|
@ -54,6 +57,16 @@ class RoomListQuickActionsEpoxyController @Inject constructor(
|
|||
lowPriorityClickListener { listener?.didSelectMenuAction(RoomListQuickActionsSharedAction.LowPriority(roomSummary.roomId)) }
|
||||
}
|
||||
|
||||
// Mark read/unread
|
||||
dividerItem {
|
||||
id("mark_unread_separator")
|
||||
}
|
||||
if (roomSummary.scIsUnread(scSdkPreferences)) {
|
||||
RoomListQuickActionsSharedAction.MarkRead(roomSummary.roomId).toBottomSheetItem(-1)
|
||||
} else {
|
||||
RoomListQuickActionsSharedAction.MarkUnread(roomSummary.roomId).toBottomSheetItem(-1)
|
||||
}
|
||||
|
||||
// Notifications
|
||||
dividerItem {
|
||||
id("notifications_separator")
|
||||
|
|
|
@ -27,6 +27,16 @@ sealed class RoomListQuickActionsSharedAction(
|
|||
val destructive: Boolean = false)
|
||||
: VectorSharedAction {
|
||||
|
||||
data class MarkUnread(val roomId: String) : RoomListQuickActionsSharedAction(
|
||||
R.string.room_list_quick_actions_mark_room_unread,
|
||||
R.drawable.ic_room_actions_mark_room_unread
|
||||
)
|
||||
|
||||
data class MarkRead(val roomId: String) : RoomListQuickActionsSharedAction(
|
||||
R.string.room_list_quick_actions_mark_room_read,
|
||||
R.drawable.ic_room_actions_mark_room_read
|
||||
)
|
||||
|
||||
data class NotificationsAllNoisy(val roomId: String) : RoomListQuickActionsSharedAction(
|
||||
R.string.room_list_quick_actions_notifications_all_noisy,
|
||||
R.drawable.ic_room_actions_notifications_all_noisy
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9M12,17A5,5 0 0,1 7,12A5,5 0 0,1 12,7A5,5 0 0,1 17,12A5,5 0 0,1 12,17M12,4.5C7,4.5 2.73,7.61 1,12C2.73,16.39 7,19.5 12,19.5C17,19.5 21.27,16.39 23,12C21.27,7.61 17,4.5 12,4.5Z" />
|
||||
</vector>
|
|
@ -0,0 +1,7 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M23,12L20.56,9.22L20.9,5.54L17.29,4.72L15.4,1.54L12,3L8.6,1.54L6.71,4.72L3.1,5.53L3.44,9.21L1,12L3.44,14.78L3.1,18.47L6.71,19.29L8.6,22.47L12,21L15.4,22.46L17.29,19.28L20.9,18.46L20.56,14.78L23,12M13,17H11V15H13V17M13,13H11V7H13V13Z" />
|
||||
</vector>
|
|
@ -59,5 +59,7 @@
|
|||
|
||||
<string name="show_room_info_sc">Rauminfo</string>
|
||||
<string name="show_participants_sc">Teilnehmer</string>
|
||||
<string name="room_list_quick_actions_mark_room_unread">Als ungelesen markieren</string>
|
||||
<string name="room_list_quick_actions_mark_room_read">Als gelesen markieren</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -59,5 +59,7 @@
|
|||
|
||||
<string name="show_room_info_sc">Room details</string>
|
||||
<string name="show_participants_sc">Participants</string>
|
||||
<string name="room_list_quick_actions_mark_room_unread">Mark as unread</string>
|
||||
<string name="room_list_quick_actions_mark_room_read">Mark as read</string>
|
||||
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue