Skip to content

Commit 3f8ddbe

Browse files
authored
Merge pull request #5817 from vector-im/arb/pse-329
Using the same User Avatar and display name for all messages in the timeline
2 parents f54c865 + 6a523cc commit 3f8ddbe

File tree

14 files changed

+145
-14
lines changed

14 files changed

+145
-14
lines changed

changelog.d/5932.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow using the latest user Avatar and name for all messages in the timeline

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt

+4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ data class TimelineSettings(
3232
* The root thread eventId if this is a thread timeline, or null if this is NOT a thread timeline
3333
*/
3434
val rootThreadEventId: String? = null,
35+
/**
36+
* If true Sender Info shown in room will get the latest data information (avatar + displayName)
37+
*/
38+
val useLiveSenderInfo: Boolean = false,
3539
) {
3640

3741
/**

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt

+5-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
3939
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
4040
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
4141
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
42+
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
4243
import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler
4344
import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
4445
import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer
@@ -59,6 +60,7 @@ internal class DefaultTimeline(
5960
private val settings: TimelineSettings,
6061
private val coroutineDispatchers: MatrixCoroutineDispatchers,
6162
private val clock: Clock,
63+
stateEventDataSource: StateEventDataSource,
6264
paginationTask: PaginationTask,
6365
getEventTask: GetContextOfEventTask,
6466
fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
@@ -106,7 +108,9 @@ internal class DefaultTimeline(
106108
onEventsUpdated = this::sendSignalToPostSnapshot,
107109
onEventsDeleted = this::onEventsDeleted,
108110
onLimitedTimeline = this::onLimitedTimeline,
109-
onNewTimelineEvents = this::onNewTimelineEvents
111+
onNewTimelineEvents = this::onNewTimelineEvents,
112+
stateEventDataSource = stateEventDataSource,
113+
matrixCoroutineDispatchers = coroutineDispatchers,
110114
)
111115

112116
private var strategy: LoadTimelineStrategy = buildStrategy(LoadTimelineStrategy.Mode.Live)

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt

+4-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
3232
import org.matrix.android.sdk.internal.di.SessionDatabase
3333
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
3434
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
35+
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
3536
import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler
3637
import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
3738
import org.matrix.android.sdk.internal.util.time.Clock
@@ -53,6 +54,7 @@ internal class DefaultTimelineService @AssistedInject constructor(
5354
private val coroutineDispatchers: MatrixCoroutineDispatchers,
5455
private val timelineEventDataSource: TimelineEventDataSource,
5556
private val clock: Clock,
57+
private val stateEventDataSource: StateEventDataSource,
5658
) : TimelineService {
5759

5860
@AssistedFactory
@@ -78,7 +80,8 @@ internal class DefaultTimelineService @AssistedInject constructor(
7880
getEventTask = contextOfEventTask,
7981
threadsAwarenessHandler = threadsAwarenessHandler,
8082
lightweightSettingsStorage = lightweightSettingsStorage,
81-
clock = clock
83+
clock = clock,
84+
stateEventDataSource = stateEventDataSource,
8285
)
8386
}
8487

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.matrix.android.sdk.internal.session.room.timeline
18+
19+
import androidx.lifecycle.LiveData
20+
import androidx.lifecycle.Observer
21+
import kotlinx.coroutines.CoroutineDispatcher
22+
import kotlinx.coroutines.withContext
23+
import org.matrix.android.sdk.api.query.QueryStringValue
24+
import org.matrix.android.sdk.api.session.events.model.Event
25+
import org.matrix.android.sdk.api.session.events.model.EventType
26+
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
27+
import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent
28+
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
29+
30+
/**
31+
* Helper to observe and query the live room state.
32+
*/
33+
internal class LiveRoomStateListener(
34+
roomId: String,
35+
stateEventDataSource: StateEventDataSource,
36+
private val mainDispatcher: CoroutineDispatcher,
37+
) {
38+
private val roomStateObserver = Observer<List<Event>> { stateEvents ->
39+
stateEvents.map { event ->
40+
val memberContent = event.getFixedRoomMemberContent() ?: return@map
41+
val stateKey = event.stateKey ?: return@map
42+
liveRoomState[stateKey] = memberContent
43+
}
44+
}
45+
private val stateEventsLiveData: LiveData<List<Event>> by lazy {
46+
stateEventDataSource.getStateEventsLive(
47+
roomId = roomId,
48+
eventTypes = setOf(EventType.STATE_ROOM_MEMBER),
49+
stateKey = QueryStringValue.NoCondition,
50+
)
51+
}
52+
53+
private val liveRoomState = mutableMapOf<String, RoomMemberContent>()
54+
55+
suspend fun start() = withContext(mainDispatcher) {
56+
stateEventsLiveData.observeForever(roomStateObserver)
57+
}
58+
59+
suspend fun stop() = withContext(mainDispatcher) {
60+
if (stateEventsLiveData.hasActiveObservers()) {
61+
stateEventsLiveData.removeObserver(roomStateObserver)
62+
}
63+
}
64+
65+
fun getLiveState(stateKey: String): RoomMemberContent? = liveRoomState[stateKey]
66+
}

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt

+36-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import io.realm.RealmConfiguration
2323
import io.realm.RealmResults
2424
import io.realm.kotlin.createObject
2525
import kotlinx.coroutines.CompletableDeferred
26+
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
2627
import org.matrix.android.sdk.api.extensions.orFalse
2728
import org.matrix.android.sdk.api.failure.Failure
2829
import org.matrix.android.sdk.api.failure.MatrixError
@@ -41,6 +42,7 @@ import org.matrix.android.sdk.internal.database.query.findAllIncludingEvents
4142
import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfThread
4243
import org.matrix.android.sdk.internal.database.query.where
4344
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
45+
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
4446
import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
4547
import org.matrix.android.sdk.internal.util.time.Clock
4648
import timber.log.Timber
@@ -100,7 +102,9 @@ internal class LoadTimelineStrategy constructor(
100102
val onEventsUpdated: (Boolean) -> Unit,
101103
val onEventsDeleted: () -> Unit,
102104
val onLimitedTimeline: () -> Unit,
103-
val onNewTimelineEvents: (List<String>) -> Unit
105+
val onNewTimelineEvents: (List<String>) -> Unit,
106+
val stateEventDataSource: StateEventDataSource,
107+
val matrixCoroutineDispatchers: MatrixCoroutineDispatchers,
104108
)
105109

106110
private var getContextLatch: CompletableDeferred<Unit>? = null
@@ -165,7 +169,13 @@ internal class LoadTimelineStrategy constructor(
165169
onEventsUpdated = dependencies.onEventsUpdated
166170
)
167171

168-
fun onStart() {
172+
private val liveRoomStateListener = LiveRoomStateListener(
173+
roomId,
174+
dependencies.stateEventDataSource,
175+
dependencies.matrixCoroutineDispatchers.main
176+
)
177+
178+
suspend fun onStart() {
169179
dependencies.eventDecryptor.start()
170180
dependencies.timelineInput.listeners.add(timelineInputListener)
171181
val realm = dependencies.realm.get()
@@ -174,9 +184,13 @@ internal class LoadTimelineStrategy constructor(
174184
it.addChangeListener(chunkEntityListener)
175185
timelineChunk = it.createTimelineChunk()
176186
}
187+
188+
if (dependencies.timelineSettings.useLiveSenderInfo) {
189+
liveRoomStateListener.start()
190+
}
177191
}
178192

179-
fun onStop() {
193+
suspend fun onStop() {
180194
dependencies.eventDecryptor.destroy()
181195
dependencies.timelineInput.listeners.remove(timelineInputListener)
182196
chunkEntity?.removeChangeListener(chunkEntityListener)
@@ -188,6 +202,9 @@ internal class LoadTimelineStrategy constructor(
188202
if (mode is Mode.Thread) {
189203
clearThreadChunkEntity(dependencies.realm.get(), mode.rootThreadEventId)
190204
}
205+
if (dependencies.timelineSettings.useLiveSenderInfo) {
206+
liveRoomStateListener.stop()
207+
}
191208
}
192209

193210
suspend fun loadMore(count: Int, direction: Timeline.Direction, fetchOnServerIfNeeded: Boolean = true): LoadMoreResult {
@@ -222,7 +239,22 @@ internal class LoadTimelineStrategy constructor(
222239
}
223240

224241
fun buildSnapshot(): List<TimelineEvent> {
225-
return buildSendingEvents() + timelineChunk?.builtItems(includesNext = true, includesPrev = true).orEmpty()
242+
val events = buildSendingEvents() + timelineChunk?.builtItems(includesNext = true, includesPrev = true).orEmpty()
243+
return if (dependencies.timelineSettings.useLiveSenderInfo) {
244+
events.map(this::applyLiveRoomState)
245+
} else {
246+
events
247+
}
248+
}
249+
250+
private fun applyLiveRoomState(event: TimelineEvent): TimelineEvent {
251+
val updatedState = liveRoomStateListener.getLiveState(event.senderInfo.userId)
252+
return if (updatedState != null) {
253+
val updatedSenderInfo = event.senderInfo.copy(avatarUrl = updatedState.avatarUrl, displayName = updatedState.displayName)
254+
event.copy(senderInfo = updatedSenderInfo)
255+
} else {
256+
event
257+
}
226258
}
227259

228260
private fun buildSendingEvents(): List<TimelineEvent> {

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt

+1
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ internal class TimelineChunk(
136136
val prevEvents = prevChunk?.builtItems(includesNext = false, includesPrev = true).orEmpty()
137137
deepBuiltItems.addAll(prevEvents)
138138
}
139+
139140
return deepBuiltItems
140141
}
141142

vector-config/src/main/res/values/config-settings.xml

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838

3939
<!-- Level 1: Labs -->
4040
<bool name="settings_labs_thread_messages_default">false</bool>
41-
41+
<bool name="settings_timeline_show_live_sender_info_visible">true</bool>
42+
<bool name="settings_timeline_show_live_sender_info_default">false</bool>
4243
<!-- Level 1: Advanced settings -->
4344

4445
<!-- Level 1: Help and about -->

vector/src/main/java/im/vector/app/core/resources/UserPreferencesProvider.kt

+4
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,8 @@ class UserPreferencesProvider @Inject constructor(private val vectorPreferences:
5252
fun areThreadMessagesEnabled(): Boolean {
5353
return vectorPreferences.areThreadMessagesEnabled()
5454
}
55+
56+
fun showLiveSenderInfo(): Boolean {
57+
return vectorPreferences.showLiveSenderInfo()
58+
}
5559
}

vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ class TimelineSettingsFactory @Inject constructor(private val userPreferencesPro
2626
return TimelineSettings(
2727
initialSize = 30,
2828
buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts(),
29-
rootThreadEventId = rootThreadEventId
29+
rootThreadEventId = rootThreadEventId,
30+
useLiveSenderInfo = userPreferencesProvider.showLiveSenderInfo()
3031
)
3132
}
3233
}

vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt

+7-3
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,9 @@ class VectorPreferences @Inject constructor(
211211
const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES_FINAL"
212212
const val SETTINGS_THREAD_MESSAGES_SYNCED = "SETTINGS_THREAD_MESSAGES_SYNCED"
213213

214+
// This key will be used to enable user for displaying live user info or not.
215+
const val SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO = "SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO"
216+
214217
// Possible values for TAKE_PHOTO_VIDEO_MODE
215218
const val TAKE_PHOTO_VIDEO_MODE_ALWAYS_ASK = 0
216219
const val TAKE_PHOTO_VIDEO_MODE_PHOTO = 1
@@ -1039,9 +1042,6 @@ class VectorPreferences @Inject constructor(
10391042
return defaultPrefs.getBoolean(SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE, true)
10401043
}
10411044

1042-
/**
1043-
* Indicates whether or not thread messages are enabled
1044-
*/
10451045
fun areThreadMessagesEnabled(): Boolean {
10461046
return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES, getDefault(R.bool.settings_labs_thread_messages_default))
10471047
}
@@ -1091,4 +1091,8 @@ class VectorPreferences @Inject constructor(
10911091
.putBoolean(SETTINGS_THREAD_MESSAGES_SYNCED, shouldMigrate)
10921092
.apply()
10931093
}
1094+
1095+
fun showLiveSenderInfo(): Boolean {
1096+
return defaultPrefs.getBoolean(SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO, getDefault(R.bool.settings_timeline_show_live_sender_info_default))
1097+
}
10941098
}

vector/src/main/res/values/strings.xml

+2
Original file line numberDiff line numberDiff line change
@@ -2852,6 +2852,8 @@
28522852
<string name="labs_auto_report_uisi_desc">Your system will automatically send logs when an unable to decrypt error occurs</string>
28532853
<string name="labs_enable_thread_messages">Enable Thread Messages</string>
28542854
<string name="labs_enable_thread_messages_desc">Note: app will be restarted</string>
2855+
<string name="settings_show_latest_profile">Show latest user info</string>
2856+
<string name="settings_show_latest_profile_description">Show the latest profile info (avatar and display name) for all the messages.</string>
28552857

28562858
<string name="user_invites_you">%s invites you</string>
28572859

vector/src/main/res/xml/vector_settings_labs.xml

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
2+
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto">
34

45
<!--<im.vector.app.core.preference.VectorPreferenceCategory-->
56
<!--android:key="SETTINGS_LABS_PREFERENCE_KEY"-->
@@ -68,4 +69,4 @@
6869
android:key="SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE"
6970
android:title="@string/labs_render_locations_in_timeline" />
7071

71-
</androidx.preference.PreferenceScreen>
72+
</androidx.preference.PreferenceScreen>

vector/src/main/res/xml/vector_settings_preferences.xml

+8-1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@
8888
android:title="@string/message_bubbles"
8989
app:isPreferenceVisible="@bool/settings_interface_bubble_visible" />
9090

91+
<im.vector.app.core.preference.VectorSwitchPreference
92+
android:defaultValue="@bool/settings_timeline_show_live_sender_info_default"
93+
app:isPreferenceVisible="@bool/settings_timeline_show_live_sender_info_visible"
94+
android:key="SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO"
95+
android:summary="@string/settings_show_latest_profile_description"
96+
android:title="@string/settings_show_latest_profile" />
97+
9198
<im.vector.app.core.preference.VectorSwitchPreference
9299
android:defaultValue="true"
93100
android:key="SETTINGS_SHOW_URL_PREVIEW_KEY"
@@ -218,4 +225,4 @@
218225
android:title="@string/settings_room_directory_show_all_rooms" />
219226

220227
</im.vector.app.core.preference.VectorPreferenceCategory>
221-
</androidx.preference.PreferenceScreen>
228+
</androidx.preference.PreferenceScreen>

0 commit comments

Comments
 (0)