Allow using the latest user avatar and display name for all messages in the timeline
Signed-off-by: Jorge Martín Espinosa <jorgem@element.io>
This commit is contained in:
parent
f54c865cf4
commit
6a523ccc38
|
@ -0,0 +1 @@
|
|||
Allow using the latest user Avatar and name for all messages in the timeline
|
|
@ -32,6 +32,10 @@ data class TimelineSettings(
|
|||
* The root thread eventId if this is a thread timeline, or null if this is NOT a thread timeline
|
||||
*/
|
||||
val rootThreadEventId: String? = null,
|
||||
/**
|
||||
* If true Sender Info shown in room will get the latest data information (avatar + displayName)
|
||||
*/
|
||||
val useLiveSenderInfo: Boolean = false,
|
||||
) {
|
||||
|
||||
/**
|
||||
|
|
|
@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
|
|||
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
||||
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
|
||||
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
|
||||
import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler
|
||||
import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
|
||||
import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer
|
||||
|
@ -59,6 +60,7 @@ internal class DefaultTimeline(
|
|||
private val settings: TimelineSettings,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val clock: Clock,
|
||||
stateEventDataSource: StateEventDataSource,
|
||||
paginationTask: PaginationTask,
|
||||
getEventTask: GetContextOfEventTask,
|
||||
fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
|
||||
|
@ -106,7 +108,9 @@ internal class DefaultTimeline(
|
|||
onEventsUpdated = this::sendSignalToPostSnapshot,
|
||||
onEventsDeleted = this::onEventsDeleted,
|
||||
onLimitedTimeline = this::onLimitedTimeline,
|
||||
onNewTimelineEvents = this::onNewTimelineEvents
|
||||
onNewTimelineEvents = this::onNewTimelineEvents,
|
||||
stateEventDataSource = stateEventDataSource,
|
||||
matrixCoroutineDispatchers = coroutineDispatchers,
|
||||
)
|
||||
|
||||
private var strategy: LoadTimelineStrategy = buildStrategy(LoadTimelineStrategy.Mode.Live)
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
|||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
||||
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
|
||||
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
|
||||
import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler
|
||||
import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
|
||||
import org.matrix.android.sdk.internal.util.time.Clock
|
||||
|
@ -53,6 +54,7 @@ internal class DefaultTimelineService @AssistedInject constructor(
|
|||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val timelineEventDataSource: TimelineEventDataSource,
|
||||
private val clock: Clock,
|
||||
private val stateEventDataSource: StateEventDataSource,
|
||||
) : TimelineService {
|
||||
|
||||
@AssistedFactory
|
||||
|
@ -78,7 +80,8 @@ internal class DefaultTimelineService @AssistedInject constructor(
|
|||
getEventTask = contextOfEventTask,
|
||||
threadsAwarenessHandler = threadsAwarenessHandler,
|
||||
lightweightSettingsStorage = lightweightSettingsStorage,
|
||||
clock = clock
|
||||
clock = clock,
|
||||
stateEventDataSource = stateEventDataSource,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.timeline
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||
import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent
|
||||
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
|
||||
|
||||
/**
|
||||
* Helper to observe and query the live room state.
|
||||
*/
|
||||
internal class LiveRoomStateListener(
|
||||
roomId: String,
|
||||
stateEventDataSource: StateEventDataSource,
|
||||
private val mainDispatcher: CoroutineDispatcher,
|
||||
) {
|
||||
private val roomStateObserver = Observer<List<Event>> { stateEvents ->
|
||||
stateEvents.map { event ->
|
||||
val memberContent = event.getFixedRoomMemberContent() ?: return@map
|
||||
val stateKey = event.stateKey ?: return@map
|
||||
liveRoomState[stateKey] = memberContent
|
||||
}
|
||||
}
|
||||
private val stateEventsLiveData: LiveData<List<Event>> by lazy {
|
||||
stateEventDataSource.getStateEventsLive(
|
||||
roomId = roomId,
|
||||
eventTypes = setOf(EventType.STATE_ROOM_MEMBER),
|
||||
stateKey = QueryStringValue.NoCondition,
|
||||
)
|
||||
}
|
||||
|
||||
private val liveRoomState = mutableMapOf<String, RoomMemberContent>()
|
||||
|
||||
suspend fun start() = withContext(mainDispatcher) {
|
||||
stateEventsLiveData.observeForever(roomStateObserver)
|
||||
}
|
||||
|
||||
suspend fun stop() = withContext(mainDispatcher) {
|
||||
if (stateEventsLiveData.hasActiveObservers()) {
|
||||
stateEventsLiveData.removeObserver(roomStateObserver)
|
||||
}
|
||||
}
|
||||
|
||||
fun getLiveState(stateKey: String): RoomMemberContent? = liveRoomState[stateKey]
|
||||
}
|
|
@ -23,6 +23,7 @@ import io.realm.RealmConfiguration
|
|||
import io.realm.RealmResults
|
||||
import io.realm.kotlin.createObject
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
|
@ -41,6 +42,7 @@ import org.matrix.android.sdk.internal.database.query.findAllIncludingEvents
|
|||
import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfThread
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
|
||||
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
|
||||
import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
|
||||
import org.matrix.android.sdk.internal.util.time.Clock
|
||||
import timber.log.Timber
|
||||
|
@ -100,7 +102,9 @@ internal class LoadTimelineStrategy constructor(
|
|||
val onEventsUpdated: (Boolean) -> Unit,
|
||||
val onEventsDeleted: () -> Unit,
|
||||
val onLimitedTimeline: () -> Unit,
|
||||
val onNewTimelineEvents: (List<String>) -> Unit
|
||||
val onNewTimelineEvents: (List<String>) -> Unit,
|
||||
val stateEventDataSource: StateEventDataSource,
|
||||
val matrixCoroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
)
|
||||
|
||||
private var getContextLatch: CompletableDeferred<Unit>? = null
|
||||
|
@ -165,7 +169,13 @@ internal class LoadTimelineStrategy constructor(
|
|||
onEventsUpdated = dependencies.onEventsUpdated
|
||||
)
|
||||
|
||||
fun onStart() {
|
||||
private val liveRoomStateListener = LiveRoomStateListener(
|
||||
roomId,
|
||||
dependencies.stateEventDataSource,
|
||||
dependencies.matrixCoroutineDispatchers.main
|
||||
)
|
||||
|
||||
suspend fun onStart() {
|
||||
dependencies.eventDecryptor.start()
|
||||
dependencies.timelineInput.listeners.add(timelineInputListener)
|
||||
val realm = dependencies.realm.get()
|
||||
|
@ -174,9 +184,13 @@ internal class LoadTimelineStrategy constructor(
|
|||
it.addChangeListener(chunkEntityListener)
|
||||
timelineChunk = it.createTimelineChunk()
|
||||
}
|
||||
|
||||
if (dependencies.timelineSettings.useLiveSenderInfo) {
|
||||
liveRoomStateListener.start()
|
||||
}
|
||||
}
|
||||
|
||||
fun onStop() {
|
||||
suspend fun onStop() {
|
||||
dependencies.eventDecryptor.destroy()
|
||||
dependencies.timelineInput.listeners.remove(timelineInputListener)
|
||||
chunkEntity?.removeChangeListener(chunkEntityListener)
|
||||
|
@ -188,6 +202,9 @@ internal class LoadTimelineStrategy constructor(
|
|||
if (mode is Mode.Thread) {
|
||||
clearThreadChunkEntity(dependencies.realm.get(), mode.rootThreadEventId)
|
||||
}
|
||||
if (dependencies.timelineSettings.useLiveSenderInfo) {
|
||||
liveRoomStateListener.stop()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadMore(count: Int, direction: Timeline.Direction, fetchOnServerIfNeeded: Boolean = true): LoadMoreResult {
|
||||
|
@ -222,7 +239,22 @@ internal class LoadTimelineStrategy constructor(
|
|||
}
|
||||
|
||||
fun buildSnapshot(): List<TimelineEvent> {
|
||||
return buildSendingEvents() + timelineChunk?.builtItems(includesNext = true, includesPrev = true).orEmpty()
|
||||
val events = buildSendingEvents() + timelineChunk?.builtItems(includesNext = true, includesPrev = true).orEmpty()
|
||||
return if (dependencies.timelineSettings.useLiveSenderInfo) {
|
||||
events.map(this::applyLiveRoomState)
|
||||
} else {
|
||||
events
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyLiveRoomState(event: TimelineEvent): TimelineEvent {
|
||||
val updatedState = liveRoomStateListener.getLiveState(event.senderInfo.userId)
|
||||
return if (updatedState != null) {
|
||||
val updatedSenderInfo = event.senderInfo.copy(avatarUrl = updatedState.avatarUrl, displayName = updatedState.displayName)
|
||||
event.copy(senderInfo = updatedSenderInfo)
|
||||
} else {
|
||||
event
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildSendingEvents(): List<TimelineEvent> {
|
||||
|
|
|
@ -136,6 +136,7 @@ internal class TimelineChunk(
|
|||
val prevEvents = prevChunk?.builtItems(includesNext = false, includesPrev = true).orEmpty()
|
||||
deepBuiltItems.addAll(prevEvents)
|
||||
}
|
||||
|
||||
return deepBuiltItems
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,8 @@
|
|||
|
||||
<!-- Level 1: Labs -->
|
||||
<bool name="settings_labs_thread_messages_default">false</bool>
|
||||
|
||||
<bool name="settings_timeline_show_live_sender_info_visible">true</bool>
|
||||
<bool name="settings_timeline_show_live_sender_info_default">false</bool>
|
||||
<!-- Level 1: Advanced settings -->
|
||||
|
||||
<!-- Level 1: Help and about -->
|
||||
|
|
|
@ -52,4 +52,8 @@ class UserPreferencesProvider @Inject constructor(private val vectorPreferences:
|
|||
fun areThreadMessagesEnabled(): Boolean {
|
||||
return vectorPreferences.areThreadMessagesEnabled()
|
||||
}
|
||||
|
||||
fun showLiveSenderInfo(): Boolean {
|
||||
return vectorPreferences.showLiveSenderInfo()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,8 @@ class TimelineSettingsFactory @Inject constructor(private val userPreferencesPro
|
|||
return TimelineSettings(
|
||||
initialSize = 30,
|
||||
buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts(),
|
||||
rootThreadEventId = rootThreadEventId
|
||||
rootThreadEventId = rootThreadEventId,
|
||||
useLiveSenderInfo = userPreferencesProvider.showLiveSenderInfo()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -211,6 +211,9 @@ class VectorPreferences @Inject constructor(
|
|||
const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES_FINAL"
|
||||
const val SETTINGS_THREAD_MESSAGES_SYNCED = "SETTINGS_THREAD_MESSAGES_SYNCED"
|
||||
|
||||
// This key will be used to enable user for displaying live user info or not.
|
||||
const val SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO = "SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO"
|
||||
|
||||
// Possible values for TAKE_PHOTO_VIDEO_MODE
|
||||
const val TAKE_PHOTO_VIDEO_MODE_ALWAYS_ASK = 0
|
||||
const val TAKE_PHOTO_VIDEO_MODE_PHOTO = 1
|
||||
|
@ -1039,9 +1042,6 @@ class VectorPreferences @Inject constructor(
|
|||
return defaultPrefs.getBoolean(SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether or not thread messages are enabled
|
||||
*/
|
||||
fun areThreadMessagesEnabled(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES, getDefault(R.bool.settings_labs_thread_messages_default))
|
||||
}
|
||||
|
@ -1091,4 +1091,8 @@ class VectorPreferences @Inject constructor(
|
|||
.putBoolean(SETTINGS_THREAD_MESSAGES_SYNCED, shouldMigrate)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun showLiveSenderInfo(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO, getDefault(R.bool.settings_timeline_show_live_sender_info_default))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2852,6 +2852,8 @@
|
|||
<string name="labs_auto_report_uisi_desc">Your system will automatically send logs when an unable to decrypt error occurs</string>
|
||||
<string name="labs_enable_thread_messages">Enable Thread Messages</string>
|
||||
<string name="labs_enable_thread_messages_desc">Note: app will be restarted</string>
|
||||
<string name="settings_show_latest_profile">Show latest user info</string>
|
||||
<string name="settings_show_latest_profile_description">Show the latest profile info (avatar and display name) for all the messages.</string>
|
||||
|
||||
<string name="user_invites_you">%s invites you</string>
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<!--<im.vector.app.core.preference.VectorPreferenceCategory-->
|
||||
<!--android:key="SETTINGS_LABS_PREFERENCE_KEY"-->
|
||||
|
|
|
@ -88,6 +88,13 @@
|
|||
android:title="@string/message_bubbles"
|
||||
app:isPreferenceVisible="@bool/settings_interface_bubble_visible" />
|
||||
|
||||
<im.vector.app.core.preference.VectorSwitchPreference
|
||||
android:defaultValue="@bool/settings_timeline_show_live_sender_info_default"
|
||||
app:isPreferenceVisible="@bool/settings_timeline_show_live_sender_info_visible"
|
||||
android:key="SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO"
|
||||
android:summary="@string/settings_show_latest_profile_description"
|
||||
android:title="@string/settings_show_latest_profile" />
|
||||
|
||||
<im.vector.app.core.preference.VectorSwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="SETTINGS_SHOW_URL_PREVIEW_KEY"
|
||||
|
|
Loading…
Reference in New Issue