Setting for unread badge / room summary event visibility

- All previewable like in Element
- Exclude member state changes
- Exclude member state changes and reactions

Replaces commit 72d2b1c9f1.

Also fix marking rooms as unread when no previewable event is available
compared to above commit.

Related issue:
https://github.com/vector-im/element-android/issues/588

Change-Id: Id76b73829a719de88f9c07c492e78736e1f3d7db
This commit is contained in:
SpiritCroc 2020-08-23 21:17:10 +02:00
parent 5b5cfa5bf1
commit b1d96ebba9
15 changed files with 192 additions and 10 deletions

View file

@ -40,10 +40,14 @@ data class RoomSummary constructor(
val joinedMembersCount: Int? = 0,
val invitedMembersCount: Int? = 0,
val latestPreviewableEvent: TimelineEvent? = null,
val latestPreviewableContentEvent: TimelineEvent? = null,
val latestPreviewableOriginalContentEvent: TimelineEvent? = null,
val otherMemberIds: List<String> = emptyList(),
val notificationCount: Int = 0,
val highlightCount: Int = 0,
val hasUnreadMessages: Boolean = false,
val hasUnreadContentMessages: Boolean = false,
val hasUnreadOriginalContentMessages: Boolean = false,
val tags: List<RoomTag> = emptyList(),
val membership: Membership = Membership.NONE,
val versioningState: VersioningState = VersioningState.NONE,
@ -70,7 +74,42 @@ data class RoomSummary constructor(
val canStartCall: Boolean
get() = joinedMembersCount == 2
fun scHasUnreadMessages(preferenceProvider: RoomSummaryPreferenceProvider?): Boolean {
if (preferenceProvider == null) {
// Fallback to default
return hasUnreadMessages
}
return when(preferenceProvider.getUnreadKind()) {
UNREAD_KIND_ORIGINAL_CONTENT -> hasUnreadOriginalContentMessages
UNREAD_KIND_CONTENT -> hasUnreadContentMessages
// UNREAD_KIND_DEFAULT
else -> hasUnreadMessages
}
}
fun scLatestPreviewableEvent(preferenceProvider: RoomSummaryPreferenceProvider?): TimelineEvent? {
if (preferenceProvider == null) {
// Fallback to default
return latestPreviewableEvent
}
return when(preferenceProvider.getUnreadKind()) {
UNREAD_KIND_ORIGINAL_CONTENT -> latestPreviewableOriginalContentEvent
UNREAD_KIND_CONTENT -> latestPreviewableContentEvent
// UNREAD_KIND_DEFAULT
else -> latestPreviewableEvent
}
}
companion object {
const val NOT_IN_BREADCRUMBS = -1
// SC addition
const val UNREAD_KIND_DEFAULT = 0
const val UNREAD_KIND_CONTENT = 1
const val UNREAD_KIND_ORIGINAL_CONTENT = 2
}
// SC addition
interface RoomSummaryPreferenceProvider {
fun getUnreadKind(): Int
}
}

View file

@ -34,6 +34,12 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
val latestEvent = roomSummaryEntity.latestPreviewableEvent?.let {
timelineEventMapper.map(it, buildReadReceipts = false)
}
val latestContentEvent = roomSummaryEntity.latestPreviewableContentEvent?.let {
timelineEventMapper.map(it, buildReadReceipts = false)
}
val latestOriginalContentEvent = roomSummaryEntity.latestPreviewableOriginalContentEvent?.let {
timelineEventMapper.map(it, buildReadReceipts = false)
}
// typings are updated through the sync where room summary entity gets updated no matter what, so it's ok get there
val typingUsers = typingUsersTracker.getTypingUsers(roomSummaryEntity.roomId)
@ -45,12 +51,16 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
avatarUrl = roomSummaryEntity.avatarUrl ?: "",
isDirect = roomSummaryEntity.isDirect,
latestPreviewableEvent = latestEvent,
latestPreviewableContentEvent = latestContentEvent,
latestPreviewableOriginalContentEvent = latestOriginalContentEvent,
joinedMembersCount = roomSummaryEntity.joinedMembersCount,
invitedMembersCount = roomSummaryEntity.invitedMembersCount,
otherMemberIds = roomSummaryEntity.otherMemberIds.toList(),
highlightCount = roomSummaryEntity.highlightCount,
notificationCount = roomSummaryEntity.notificationCount,
hasUnreadMessages = roomSummaryEntity.hasUnreadMessages,
hasUnreadContentMessages = roomSummaryEntity.hasUnreadContentMessages,
hasUnreadOriginalContentMessages = roomSummaryEntity.hasUnreadOriginalContentMessages,
tags = tags,
typingUsers = typingUsers,
membership = roomSummaryEntity.membership,

View file

@ -32,6 +32,8 @@ internal open class RoomSummaryEntity(
var name: String? = "",
var topic: String? = "",
var latestPreviewableEvent: TimelineEventEntity? = null,
var latestPreviewableContentEvent: TimelineEventEntity? = null,
var latestPreviewableOriginalContentEvent: TimelineEventEntity? = null,
var heroes: RealmList<String> = RealmList(),
var joinedMembersCount: Int? = 0,
var invitedMembersCount: Int? = 0,
@ -42,6 +44,8 @@ internal open class RoomSummaryEntity(
var highlightCount: Int = 0,
var readMarkerId: String? = null,
var hasUnreadMessages: Boolean = false,
var hasUnreadContentMessages: Boolean = false,
var hasUnreadOriginalContentMessages: Boolean = false,
var tags: RealmList<RoomTagEntity> = RealmList(),
var userDrafts: UserDraftsEntity? = null,
var breadcrumbsIndex: Int = RoomSummary.NOT_IN_BREADCRUMBS,

View file

@ -126,6 +126,8 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
roomSummary.notificationCount = 0
roomSummary.highlightCount = 0
roomSummary.hasUnreadMessages = false
roomSummary.hasUnreadContentMessages = false
roomSummary.hasUnreadOriginalContentMessages = false
}
}
}

View file

@ -64,6 +64,25 @@ internal class RoomSummaryUpdater @Inject constructor(
companion object {
// TODO: maybe allow user of SDK to give that list
val PREVIEWABLE_TYPES = listOf(
// TODO filter message type (KEY_VERIFICATION_READY, etc.)
EventType.MESSAGE,
EventType.STATE_ROOM_NAME,
EventType.STATE_ROOM_TOPIC,
EventType.STATE_ROOM_AVATAR,
EventType.STATE_ROOM_MEMBER,
EventType.STATE_ROOM_HISTORY_VISIBILITY,
EventType.CALL_INVITE,
EventType.CALL_HANGUP,
EventType.CALL_ANSWER,
EventType.ENCRYPTED,
EventType.STATE_ROOM_ENCRYPTION,
EventType.STATE_ROOM_THIRD_PARTY_INVITE,
EventType.STICKER,
EventType.REACTION,
EventType.STATE_ROOM_CREATE
)
// Same as PREVIEWABLE_CONTENT_TYPES without member state changes // SC feature
val PREVIEWABLE_CONTENT_TYPES = listOf(
// TODO filter message type (KEY_VERIFICATION_READY, etc.)
EventType.MESSAGE,
/*
@ -85,6 +104,31 @@ internal class RoomSummaryUpdater @Inject constructor(
EventType.REACTION,
EventType.STATE_ROOM_CREATE
)
// Same as PREVIEWABLE_ORIGINAL_CONTENT_TYPES without reactions // SC feature
val PREVIEWABLE_ORIGINAL_CONTENT_TYPES = listOf(
// TODO filter message type (KEY_VERIFICATION_READY, etc.)
EventType.MESSAGE,
/*
EventType.STATE_ROOM_NAME,
EventType.STATE_ROOM_TOPIC,
EventType.STATE_ROOM_AVATAR,
EventType.STATE_ROOM_MEMBER,
EventType.STATE_ROOM_HISTORY_VISIBILITY,
*/
EventType.CALL_INVITE,
EventType.CALL_HANGUP,
EventType.CALL_ANSWER,
EventType.ENCRYPTED,
/*
EventType.STATE_ROOM_ENCRYPTION,
EventType.STATE_ROOM_THIRD_PARTY_INVITE,
*/
EventType.STICKER,
/*
EventType.REACTION,
*/
EventType.STATE_ROOM_CREATE
)
}
fun update(realm: Realm,
@ -116,6 +160,10 @@ internal class RoomSummaryUpdater @Inject constructor(
val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true,
filterTypes = PREVIEWABLE_TYPES, filterContentRelation = true)
val latestPreviewableContentEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true,
filterTypes = PREVIEWABLE_CONTENT_TYPES, filterContentRelation = true)
val latestPreviewableOriginalContentEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true,
filterTypes = PREVIEWABLE_ORIGINAL_CONTENT_TYPES, filterContentRelation = true)
val lastNameEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_NAME, stateKey = "")?.root
val lastTopicEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_TOPIC, stateKey = "")?.root
@ -130,12 +178,22 @@ internal class RoomSummaryUpdater @Inject constructor(
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
// avoid this call if we are sure there are unread events
|| !isEventRead(realm.configuration, userId, roomId, latestPreviewableEvent?.eventId)
roomSummaryEntity.hasUnreadContentMessages = roomSummaryEntity.notificationCount > 0
// avoid this call if we are sure there are unread events
|| (latestPreviewableContentEvent != null
&& !isEventRead(realm.configuration, userId, roomId, latestPreviewableContentEvent.eventId))
roomSummaryEntity.hasUnreadOriginalContentMessages = roomSummaryEntity.notificationCount > 0
// avoid this call if we are sure there are unread events
|| (latestPreviewableOriginalContentEvent != null
&& !isEventRead(realm.configuration, userId, roomId, latestPreviewableOriginalContentEvent.eventId))
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId).toString()
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId)
roomSummaryEntity.name = ContentMapper.map(lastNameEvent?.content).toModel<RoomNameContent>()?.name
roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel<RoomTopicContent>()?.topic
roomSummaryEntity.latestPreviewableEvent = latestPreviewableEvent
roomSummaryEntity.latestPreviewableContentEvent = latestPreviewableContentEvent
roomSummaryEntity.latestPreviewableOriginalContentEvent = latestPreviewableOriginalContentEvent
roomSummaryEntity.canonicalAlias = ContentMapper.map(lastCanonicalAliasEvent?.content).toModel<RoomCanonicalAliasContent>()
?.canonicalAlias

View file

@ -0,0 +1,15 @@
package im.vector.app.features.home.room
import android.content.Context
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import javax.inject.Inject
class ScSdkPreferences @Inject constructor(private val vectorPreferences: VectorPreferences?): RoomSummary.RoomSummaryPreferenceProvider {
constructor(context: Context?) : this(vectorPreferences = context?.let { VectorPreferences(it) })
override fun getUnreadKind(): Int {
return vectorPreferences?.roomUnreadKind() ?: RoomSummary.UNREAD_KIND_DEFAULT
}
}

View file

@ -21,11 +21,13 @@ import com.airbnb.epoxy.EpoxyController
import im.vector.app.core.epoxy.zeroItem
import im.vector.app.core.utils.DebouncedClickListener
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.ScSdkPreferences
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
class BreadcrumbsController @Inject constructor(
private val avatarRenderer: AvatarRenderer
private val avatarRenderer: AvatarRenderer,
private val scSdkPreferences: ScSdkPreferences
) : EpoxyController() {
var listener: Listener? = null
@ -62,7 +64,7 @@ class BreadcrumbsController @Inject constructor(
matrixItem(it.toMatrixItem())
unreadNotificationCount(it.notificationCount)
showHighlighted(it.highlightCount > 0)
hasUnreadMessage(it.hasUnreadMessages)
hasUnreadMessage(it.scHasUnreadMessages(scSdkPreferences))
hasDraft(it.userDrafts.isNotEmpty())
itemClickListener(
DebouncedClickListener(View.OnClickListener { _ ->

View file

@ -120,6 +120,7 @@ import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivit
import im.vector.app.features.crypto.util.toImageRes
import im.vector.app.features.crypto.verification.VerificationBottomSheet
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.ScSdkPreferences
import im.vector.app.features.home.room.detail.composer.TextComposerView
import im.vector.app.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
@ -1022,7 +1023,7 @@ class RoomDetailFragment @Inject constructor(
val inviter = state.asyncInviter()
if (summary?.membership == Membership.JOIN) {
jumpToBottomView.count = summary.notificationCount
jumpToBottomView.drawBadge = summary.hasUnreadMessages
jumpToBottomView.drawBadge = summary.scHasUnreadMessages(ScSdkPreferences(context))
scrollOnHighlightedEventCallback.timeline = roomDetailViewModel.timeline
timelineEventController.update(state)
inviteView.visibility = View.GONE

View file

@ -25,6 +25,7 @@ import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.app.R
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.ScSdkPreferences
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary
@ -41,7 +42,8 @@ data class RoomListViewState(
val isGroupRoomsExpanded: Boolean = true,
val isCombinedRoomsExpanded: Boolean = true,
val isLowPriorityRoomsExpanded: Boolean = true,
val isServerNoticeRoomsExpanded: Boolean = true
val isServerNoticeRoomsExpanded: Boolean = true,
val scSdkPreferences: ScSdkPreferences? = null
) : MvRxState {
companion object {
@ -57,12 +59,15 @@ data class RoomListViewState(
return ROOM_LIST_ROOM_EXPANDED_LOW_PRIORITY_PREFIX + displayMode.toString()
}
// SC addition
fun initWithContext(context: Context, displayMode: RoomListDisplayMode): RoomListViewState {
val sp = getSharedPreferences(context)
val pref = getRoomListExpandedLowPriorityPref(displayMode)
return copy(isLowPriorityRoomsExpanded = sp.getBoolean(pref, isLowPriorityRoomsExpanded))
val scSdkPreferences = ScSdkPreferences(context)
return copy(isLowPriorityRoomsExpanded = sp.getBoolean(pref, isLowPriorityRoomsExpanded), scSdkPreferences = scSdkPreferences)
}
// SC addition
fun persistWithContext(context: Context, displayMode: RoomListDisplayMode) {
val sp = getSharedPreferences(context)
val pref = getRoomListExpandedLowPriorityPref(displayMode)
@ -97,7 +102,7 @@ data class RoomListViewState(
get() = asyncFilteredRooms.invoke()
?.flatMap { it.value }
?.filter { it.membership == Membership.JOIN }
?.any { it.hasUnreadMessages }
?.any { it.scHasUnreadMessages(scSdkPreferences) }
?: false
}

View file

@ -25,6 +25,7 @@ import im.vector.app.core.resources.DateProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.DebouncedClickListener
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.ScSdkPreferences
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
import im.vector.app.features.home.room.typing.TypingHelper
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
@ -37,7 +38,8 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
private val dateFormatter: VectorDateFormatter,
private val stringProvider: StringProvider,
private val typingHelper: TypingHelper,
private val avatarRenderer: AvatarRenderer) {
private val avatarRenderer: AvatarRenderer,
private val scSdkPreferences: ScSdkPreferences) {
fun create(roomSummary: RoomSummary,
roomChangeMembershipStates: Map<String, ChangeMembershipState>,
@ -85,7 +87,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
val showSelected = selectedRoomIds.contains(roomSummary.roomId)
var latestFormattedEvent: CharSequence = ""
var latestEventTime: CharSequence = ""
val latestEvent = roomSummary.latestPreviewableEvent
val latestEvent = roomSummary.scLatestPreviewableEvent(scSdkPreferences)
if (latestEvent != null) {
val date = latestEvent.root.localDateTime()
val currentDate = DateProvider.currentLocalDateTime()
@ -111,7 +113,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
.showSelected(showSelected)
.hasFailedSending(roomSummary.hasFailedSending)
.unreadNotificationCount(unreadCount)
.hasUnreadMessage(roomSummary.hasUnreadMessages)
.hasUnreadMessage(roomSummary.scHasUnreadMessages(scSdkPreferences))
.hasDraft(roomSummary.userDrafts.isNotEmpty())
.itemLongClickListener { _ ->
onLongClick?.invoke(roomSummary) ?: false

View file

@ -29,6 +29,7 @@ import im.vector.app.R
import im.vector.app.features.homeserver.ServerUrlsRepository
import im.vector.app.features.themes.ThemeUtils
import org.matrix.android.sdk.api.extensions.tryThis
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import timber.log.Timber
import javax.inject.Inject
@ -96,7 +97,6 @@ class VectorPreferences @Inject constructor(private val context: Context) {
private const val SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY = "SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY"
private const val SETTINGS_VIBRATE_ON_MENTION_KEY = "SETTINGS_VIBRATE_ON_MENTION_KEY"
private const val SETTINGS_SEND_MESSAGE_WITH_ENTER = "SETTINGS_SEND_MESSAGE_WITH_ENTER"
const val SETTINGS_SINGLE_OVERVIEW = "SETTINGS_SINGLE_OVERVIEW"
// Help
private const val SETTINGS_SHOULD_SHOW_HELP_ON_ROOM_LIST_KEY = "SETTINGS_SHOULD_SHOW_HELP_ON_ROOM_LIST_KEY"
@ -174,6 +174,10 @@ class VectorPreferences @Inject constructor(private val context: Context) {
private const val DID_ASK_TO_USE_ANALYTICS_TRACKING_KEY = "DID_ASK_TO_USE_ANALYTICS_TRACKING_KEY"
private const val SETTINGS_DISPLAY_ALL_EVENTS_KEY = "SETTINGS_DISPLAY_ALL_EVENTS_KEY"
// SC additions
private const val SETTINGS_SINGLE_OVERVIEW = "SETTINGS_SINGLE_OVERVIEW"
private const val SETTINGS_ROOM_UNREAD_KIND = "SETTINGS_ROOM_UNREAD_KIND"
private const val DID_ASK_TO_ENABLE_SESSION_PUSH = "DID_ASK_TO_ENABLE_SESSION_PUSH"
private const val MEDIA_SAVING_3_DAYS = 0
@ -825,10 +829,21 @@ class VectorPreferences @Inject constructor(private val context: Context) {
return defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_FLAG_SECURE, false)
}
// SC addition
fun singleOverview(): Boolean {
return defaultPrefs.getBoolean(SETTINGS_SINGLE_OVERVIEW, true)
}
// SC addition
fun roomUnreadKind(): Int {
val kind = defaultPrefs.getString(SETTINGS_ROOM_UNREAD_KIND, RoomSummary.UNREAD_KIND_CONTENT.toString())
return try {
Integer.parseInt(kind!!)
} catch (e: Exception) {
RoomSummary.UNREAD_KIND_CONTENT
}
}
/**
* The user enable protecting app access with pin code
*/

View file

@ -4,6 +4,11 @@
<string name="settings_single_overview">Vereinte Chat-Übersicht</string>
<string name="settings_single_overview_summary">Zeige sowohl Direktnachrichten als auch Gruppenchats in einer gemeinsamen Übersicht</string>
<string name="settings_room_unread_kind">Chat-Vorschau</string>
<string name="settings_room_unread_kind_default">All Ereignisse</string>
<string name="settings_room_unread_kind_content">Verstecke Teilnehmerveränderungen</string>
<string name="settings_room_unread_kind_original_content">Verstecke Teilnehmerveränderungen und Reaktionen</string>
<string name="settings_dark_theme">Nacht-Design</string>
<string name="sc_light_theme">SC Hell</string>
<string name="sc_theme">SC Schwarz</string>

View file

@ -12,4 +12,15 @@
<item>both</item>
</string-array>
<string-array name="room_unread_kind_entries" translatable="false">
<item>@string/settings_room_unread_kind_default</item>
<item>@string/settings_room_unread_kind_content</item>
<item>@string/settings_room_unread_kind_original_content</item>
</string-array>
<string-array name="room_unread_kind_values" translatable="false">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
</resources>

View file

@ -4,6 +4,11 @@
<string name="settings_single_overview">Combined chat overview</string>
<string name="settings_single_overview_summary">View both direct messages and rooms on one page</string>
<string name="settings_room_unread_kind">Chat preview content</string>
<string name="settings_room_unread_kind_default">Show all events</string>
<string name="settings_room_unread_kind_content">Hide member changes</string>
<string name="settings_room_unread_kind_original_content">Hide member changes and reactions</string>
<string name="settings_dark_theme">Night Theme</string>
<string name="sc_light_theme">SC Light</string>
<string name="sc_theme">SC Black</string>

View file

@ -45,6 +45,14 @@
android:summary="@string/settings_single_overview_summary"
android:title="@string/settings_single_overview" />
<im.vector.app.core.preference.VectorListPreference
android:defaultValue="1"
android:entries="@array/room_unread_kind_entries"
android:entryValues="@array/room_unread_kind_values"
android:key="SETTINGS_ROOM_UNREAD_KIND"
android:summary="%s"
android:title="@string/settings_room_unread_kind" />
<im.vector.app.core.preference.VectorPreference
android:dialogTitle="@string/font_size"
android:key="SETTINGS_INTERFACE_TEXT_SIZE_KEY"