Skip to content

Commit

Permalink
Merge pull request #5817 from vector-im/arb/pse-329
Browse files Browse the repository at this point in the history
Using the same User Avatar and display name for all messages in the timeline
  • Loading branch information
jmartinesp authored May 12, 2022
2 parents f54c865 + 6a523cc commit 3f8ddbe
Show file tree
Hide file tree
Showing 14 changed files with 145 additions and 14 deletions.
1 change: 1 addition & 0 deletions changelog.d/5932.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow using the latest user Avatar and name for all messages in the timeline
Original file line number Diff line number Diff line change
Expand Up @@ -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,
) {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -78,7 +80,8 @@ internal class DefaultTimelineService @AssistedInject constructor(
getEventTask = contextOfEventTask,
threadsAwarenessHandler = threadsAwarenessHandler,
lightweightSettingsStorage = lightweightSettingsStorage,
clock = clock
clock = clock,
stateEventDataSource = stateEventDataSource,
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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]
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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)
Expand All @@ -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 {
Expand Down Expand Up @@ -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> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ internal class TimelineChunk(
val prevEvents = prevChunk?.builtItems(includesNext = false, includesPrev = true).orEmpty()
deepBuiltItems.addAll(prevEvents)
}

return deepBuiltItems
}

Expand Down
3 changes: 2 additions & 1 deletion vector-config/src/main/res/values/config-settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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 -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,8 @@ class UserPreferencesProvider @Inject constructor(private val vectorPreferences:
fun areThreadMessagesEnabled(): Boolean {
return vectorPreferences.areThreadMessagesEnabled()
}

fun showLiveSenderInfo(): Boolean {
return vectorPreferences.showLiveSenderInfo()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
}
Expand Down Expand Up @@ -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))
}
}
2 changes: 2 additions & 0 deletions vector/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down
5 changes: 3 additions & 2 deletions vector/src/main/res/xml/vector_settings_labs.xml
Original file line number Diff line number Diff line change
@@ -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"-->
Expand Down Expand Up @@ -68,4 +69,4 @@
android:key="SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE"
android:title="@string/labs_render_locations_in_timeline" />

</androidx.preference.PreferenceScreen>
</androidx.preference.PreferenceScreen>
9 changes: 8 additions & 1 deletion vector/src/main/res/xml/vector_settings_preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -218,4 +225,4 @@
android:title="@string/settings_room_directory_show_all_rooms" />

</im.vector.app.core.preference.VectorPreferenceCategory>
</androidx.preference.PreferenceScreen>
</androidx.preference.PreferenceScreen>

0 comments on commit 3f8ddbe

Please sign in to comment.