Lab setting to load rooms at first unread message
Change-Id: I781e5a32d8557939c51387eadf1387cba0d3b149
This commit is contained in:
parent
401424ff96
commit
2db315219a
@ -66,6 +66,16 @@ interface Timeline {
|
|||||||
*/
|
*/
|
||||||
fun setInitialEventId(eventId: String?)
|
fun setInitialEventId(eventId: String?)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offset for the initial event, e.g. if we want to load the event just below said id
|
||||||
|
*/
|
||||||
|
fun getInitialEventIdOffset(): Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the offset for the initial event, e.g. if we want to load the event just below said id
|
||||||
|
*/
|
||||||
|
fun setInitialEventIdOffset(offset: Int)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the timeline can be enriched by paginating.
|
* Check if the timeline can be enriched by paginating.
|
||||||
* @param direction the direction to check in
|
* @param direction the direction to check in
|
||||||
|
@ -61,6 +61,7 @@ private const val MIN_FETCHING_COUNT = 30
|
|||||||
internal class DefaultTimeline(
|
internal class DefaultTimeline(
|
||||||
private val roomId: String,
|
private val roomId: String,
|
||||||
private var initialEventId: String? = null,
|
private var initialEventId: String? = null,
|
||||||
|
private var initialEventIdOffset: Int = 0,
|
||||||
private val realmConfiguration: RealmConfiguration,
|
private val realmConfiguration: RealmConfiguration,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val contextOfEventTask: GetContextOfEventTask,
|
private val contextOfEventTask: GetContextOfEventTask,
|
||||||
@ -145,7 +146,7 @@ internal class DefaultTimeline(
|
|||||||
|
|
||||||
override fun start() {
|
override fun start() {
|
||||||
if (isStarted.compareAndSet(false, true)) {
|
if (isStarted.compareAndSet(false, true)) {
|
||||||
Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId")
|
Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId (offset $initialEventIdOffset)")
|
||||||
timelineInput.listeners.add(this)
|
timelineInput.listeners.add(this)
|
||||||
BACKGROUND_HANDLER.post {
|
BACKGROUND_HANDLER.post {
|
||||||
eventDecryptor.start()
|
eventDecryptor.start()
|
||||||
@ -195,7 +196,7 @@ internal class DefaultTimeline(
|
|||||||
if (isStarted.compareAndSet(true, false)) {
|
if (isStarted.compareAndSet(true, false)) {
|
||||||
isReady.set(false)
|
isReady.set(false)
|
||||||
timelineInput.listeners.remove(this)
|
timelineInput.listeners.remove(this)
|
||||||
Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId")
|
Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId (offset $initialEventIdOffset)")
|
||||||
cancelableBag.cancel()
|
cancelableBag.cancel()
|
||||||
BACKGROUND_HANDLER.removeCallbacksAndMessages(null)
|
BACKGROUND_HANDLER.removeCallbacksAndMessages(null)
|
||||||
BACKGROUND_HANDLER.post {
|
BACKGROUND_HANDLER.post {
|
||||||
@ -217,6 +218,7 @@ internal class DefaultTimeline(
|
|||||||
override fun restartWithEventId(eventId: String?) {
|
override fun restartWithEventId(eventId: String?) {
|
||||||
dispose()
|
dispose()
|
||||||
initialEventId = eventId
|
initialEventId = eventId
|
||||||
|
initialEventIdOffset = 0
|
||||||
start()
|
start()
|
||||||
postSnapshot()
|
postSnapshot()
|
||||||
}
|
}
|
||||||
@ -229,6 +231,14 @@ internal class DefaultTimeline(
|
|||||||
initialEventId = eventId
|
initialEventId = eventId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getInitialEventIdOffset(): Int {
|
||||||
|
return initialEventIdOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setInitialEventIdOffset(offset: Int) {
|
||||||
|
initialEventIdOffset = offset
|
||||||
|
}
|
||||||
|
|
||||||
override fun getTimelineEventAtIndex(index: Int): TimelineEvent? {
|
override fun getTimelineEventAtIndex(index: Int): TimelineEvent? {
|
||||||
return builtEvents.getOrNull(index)
|
return builtEvents.getOrNull(index)
|
||||||
}
|
}
|
||||||
|
@ -131,11 +131,16 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||||||
Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener {
|
Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener {
|
||||||
|
|
||||||
private val room = session.getRoom(initialState.roomId)!!
|
private val room = session.getRoom(initialState.roomId)!!
|
||||||
private val eventId = initialState.eventId
|
private val eventId = initialState.eventId ?: if (vectorPreferences.loadRoomAtFirstUnread()) room.roomSummary()?.readMarkerId else null
|
||||||
private val invisibleEventsObservable = BehaviorRelay.create<RoomDetailAction.TimelineEventTurnsInvisible>()
|
private val invisibleEventsObservable = BehaviorRelay.create<RoomDetailAction.TimelineEventTurnsInvisible>()
|
||||||
private val visibleEventsObservable = BehaviorRelay.create<RoomDetailAction.TimelineEventTurnsVisible>()
|
private val visibleEventsObservable = BehaviorRelay.create<RoomDetailAction.TimelineEventTurnsVisible>()
|
||||||
private var timelineEvents = PublishRelay.create<List<TimelineEvent>>()
|
private var timelineEvents = PublishRelay.create<List<TimelineEvent>>()
|
||||||
val timeline = timelineFactory.createTimeline(viewModelScope, room, eventId)
|
val timeline = timelineFactory.createTimeline(viewModelScope, room, eventId).apply {
|
||||||
|
// Target the event just below $eventId in case that it is the readMarkerId
|
||||||
|
if (initialState.eventId == null && eventId != null) {
|
||||||
|
setInitialEventIdOffset(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Same lifecycle than the ViewModel (survive to screen rotation)
|
// Same lifecycle than the ViewModel (survive to screen rotation)
|
||||||
val previewUrlRetriever = PreviewUrlRetriever(session, viewModelScope)
|
val previewUrlRetriever = PreviewUrlRetriever(session, viewModelScope)
|
||||||
@ -184,8 +189,10 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||||||
observeActiveRoomWidgets()
|
observeActiveRoomWidgets()
|
||||||
observePowerLevel()
|
observePowerLevel()
|
||||||
room.getRoomSummaryLive()
|
room.getRoomSummaryLive()
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
if (!vectorPreferences.loadRoomAtFirstUnread()) {
|
||||||
tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) }
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Inform the SDK that the room is displayed
|
// Inform the SDK that the room is displayed
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
@ -507,6 +514,9 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||||||
mostRecentDisplayedEvent?.root?.eventId?.also {
|
mostRecentDisplayedEvent?.root?.eventId?.also {
|
||||||
session.coroutineScope.launch(NonCancellable) {
|
session.coroutineScope.launch(NonCancellable) {
|
||||||
tryOrNull { room.setReadMarker(it) }
|
tryOrNull { room.setReadMarker(it) }
|
||||||
|
if (vectorPreferences.loadRoomAtFirstUnread()) {
|
||||||
|
tryOrNull { room.setReadReceipt(it) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mostRecentDisplayedEvent = null
|
mostRecentDisplayedEvent = null
|
||||||
@ -1312,10 +1322,10 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun observeEventDisplayedActions() {
|
private fun observeEventDisplayedActions() {
|
||||||
// We are buffering scroll events for one second
|
// We are buffering scroll events for half a second
|
||||||
// and keep the most recent one to set the read receipt on.
|
// and keep the most recent one to set the read receipt on.
|
||||||
visibleEventsObservable
|
visibleEventsObservable
|
||||||
.buffer(1, TimeUnit.SECONDS)
|
.buffer(500, TimeUnit.MILLISECONDS)
|
||||||
.filter { it.isNotEmpty() }
|
.filter { it.isNotEmpty() }
|
||||||
.subscribeBy(onNext = { actions ->
|
.subscribeBy(onNext = { actions ->
|
||||||
val bufferedMostRecentDisplayedEvent = actions.maxByOrNull { it.event.displayIndex }?.event ?: return@subscribeBy
|
val bufferedMostRecentDisplayedEvent = actions.maxByOrNull { it.event.displayIndex }?.event ?: return@subscribeBy
|
||||||
|
@ -23,6 +23,7 @@ import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
|||||||
import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents
|
import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager,
|
class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager,
|
||||||
private val timelineEventController: TimelineEventController,
|
private val timelineEventController: TimelineEventController,
|
||||||
@ -32,7 +33,6 @@ class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager,
|
|||||||
private var forceScroll = false
|
private var forceScroll = false
|
||||||
var initialForceScroll = false
|
var initialForceScroll = false
|
||||||
var initialForceScrollEventId: String? = null
|
var initialForceScrollEventId: String? = null
|
||||||
get() = field ?: timelineEventController.timeline?.getInitialEventId()
|
|
||||||
|
|
||||||
fun addNewTimelineEventIds(eventIds: List<String>) {
|
fun addNewTimelineEventIds(eventIds: List<String>) {
|
||||||
// Disable initial force scroll
|
// Disable initial force scroll
|
||||||
@ -57,8 +57,10 @@ class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager,
|
|||||||
override fun onInserted(position: Int, count: Int) {
|
override fun onInserted(position: Int, count: Int) {
|
||||||
if (initialForceScroll) {
|
if (initialForceScroll) {
|
||||||
var scrollToEvent = initialForceScrollEventId
|
var scrollToEvent = initialForceScrollEventId
|
||||||
|
var scrollOffset = 0
|
||||||
if (initialForceScrollEventId == null) {
|
if (initialForceScrollEventId == null) {
|
||||||
scrollToEvent = timelineEventController.timeline?.getInitialEventId()
|
scrollToEvent = timelineEventController.timeline?.getInitialEventId()
|
||||||
|
scrollOffset = timelineEventController.timeline?.getInitialEventIdOffset() ?: 0
|
||||||
}
|
}
|
||||||
if (scrollToEvent == null) {
|
if (scrollToEvent == null) {
|
||||||
layoutManager.scrollToPositionWithOffset(0, 0)
|
layoutManager.scrollToPositionWithOffset(0, 0)
|
||||||
@ -67,7 +69,8 @@ class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager,
|
|||||||
// Scroll such that the scrolled-to event is moved down 1/3 of the screen.
|
// Scroll such that the scrolled-to event is moved down 1/3 of the screen.
|
||||||
// To do that, we actually scroll the view above out by 2/3 (since we can only control the distance
|
// To do that, we actually scroll the view above out by 2/3 (since we can only control the distance
|
||||||
// from the bottom of the view, not the top).
|
// from the bottom of the view, not the top).
|
||||||
layoutManager.scrollToPositionWithOffset(it + 1, parentView.measuredHeight * 2 / 3)
|
val scrollToPosition = max(it + scrollOffset + 1, 0)
|
||||||
|
layoutManager.scrollToPositionWithOffset(scrollToPosition, parentView.measuredHeight * 2 / 3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -203,6 +203,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
|||||||
private const val SETTINGS_USER_COLOR_MODE_DM = "SETTINGS_USER_COLOR_MODE_DM"
|
private const val SETTINGS_USER_COLOR_MODE_DM = "SETTINGS_USER_COLOR_MODE_DM"
|
||||||
private const val SETTINGS_USER_COLOR_MODE_DEFAULT = "SETTINGS_USER_COLOR_MODE_DEFAULT"
|
private const val SETTINGS_USER_COLOR_MODE_DEFAULT = "SETTINGS_USER_COLOR_MODE_DEFAULT"
|
||||||
private const val SETTINGS_USER_COLOR_MODE_PUBLIC_ROOM = "SETTINGS_USER_COLOR_MODE_PUBLIC_ROOM"
|
private const val SETTINGS_USER_COLOR_MODE_PUBLIC_ROOM = "SETTINGS_USER_COLOR_MODE_PUBLIC_ROOM"
|
||||||
|
private const val SETTINGS_OPEN_CHATS_AT_FIRST_UNREAD = "SETTINGS_OPEN_CHATS_AT_FIRST_UNREAD"
|
||||||
|
|
||||||
private const val DID_ASK_TO_ENABLE_SESSION_PUSH = "DID_ASK_TO_ENABLE_SESSION_PUSH"
|
private const val DID_ASK_TO_ENABLE_SESSION_PUSH = "DID_ASK_TO_ENABLE_SESSION_PUSH"
|
||||||
|
|
||||||
@ -997,6 +998,11 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
|||||||
}, MatrixItemColorProvider.USER_COLORING_DEFAULT) ?: MatrixItemColorProvider.USER_COLORING_DEFAULT
|
}, MatrixItemColorProvider.USER_COLORING_DEFAULT) ?: MatrixItemColorProvider.USER_COLORING_DEFAULT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SC addition
|
||||||
|
fun loadRoomAtFirstUnread(): Boolean {
|
||||||
|
return defaultPrefs.getBoolean(SETTINGS_OPEN_CHATS_AT_FIRST_UNREAD, false)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user enable protecting app access with pin code.
|
* The user enable protecting app access with pin code.
|
||||||
* Currently we use the pin code store to know if the pin is enabled, so this is not used
|
* Currently we use the pin code store to know if the pin is enabled, so this is not used
|
||||||
|
@ -84,6 +84,9 @@
|
|||||||
<string name="setting_sc_accent_color_dark">Accent color for dark themes</string>
|
<string name="setting_sc_accent_color_dark">Accent color for dark themes</string>
|
||||||
<string name="settings_sc_accent_disclaimer">Note that these color settings only apply to SC themes, and not Element themes.</string>
|
<string name="settings_sc_accent_disclaimer">Note that these color settings only apply to SC themes, and not Element themes.</string>
|
||||||
|
|
||||||
|
<string name="settings_open_chats_at_first_unread">Open at first unread</string>
|
||||||
|
<string name="settings_open_chats_at_first_unread_summary">Open chats at the first unread message instead of at the bottom.</string>
|
||||||
|
|
||||||
<!-- Accent colors -->
|
<!-- Accent colors -->
|
||||||
<string name="sc_accent_greenlight">Light green</string>
|
<string name="sc_accent_greenlight">Light green</string>
|
||||||
<string name="sc_accent_greendark">Dark green</string>
|
<string name="sc_accent_greendark">Dark green</string>
|
||||||
|
@ -34,6 +34,12 @@
|
|||||||
<!--android:summary="@string/settings_labs_enable_send_voice_summary"-->
|
<!--android:summary="@string/settings_labs_enable_send_voice_summary"-->
|
||||||
<!--android:title="@string/settings_labs_enable_send_voice" />-->
|
<!--android:title="@string/settings_labs_enable_send_voice" />-->
|
||||||
|
|
||||||
|
<im.vector.app.core.preference.VectorSwitchPreference
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="SETTINGS_OPEN_CHATS_AT_FIRST_UNREAD"
|
||||||
|
android:title="@string/settings_open_chats_at_first_unread"
|
||||||
|
android:summary="@string/settings_open_chats_at_first_unread_summary" />
|
||||||
|
|
||||||
<im.vector.app.core.preference.VectorSwitchPreference
|
<im.vector.app.core.preference.VectorSwitchPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="SYSTEM_DARK_THEME_PRE_TEN"
|
android:key="SYSTEM_DARK_THEME_PRE_TEN"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user