Read marker: final refact [WIP]

This commit is contained in:
ganfra 2019-09-24 22:52:43 +02:00
parent c6d01fbcf4
commit 63b43de4b8
17 changed files with 152 additions and 115 deletions

View file

@ -37,7 +37,7 @@ class ReadMarkerView @JvmOverloads constructor(
) : View(context, attrs, defStyleAttr) { ) : View(context, attrs, defStyleAttr) {
interface Callback { interface Callback {
fun onReadMarkerLongBound() fun onReadMarkerLongBound(isDisplayed: Boolean)
} }
private var eventId: String? = null private var eventId: String? = null
@ -57,7 +57,7 @@ class ReadMarkerView @JvmOverloads constructor(
if (hasReadMarker) { if (hasReadMarker) {
callbackDispatcherJob = GlobalScope.launch(Dispatchers.Main) { callbackDispatcherJob = GlobalScope.launch(Dispatchers.Main) {
delay(DELAY_IN_MS) delay(DELAY_IN_MS)
callback?.onReadMarkerLongBound() callback?.onReadMarkerLongBound(displayReadMarker)
} }
} }
} }

View file

@ -30,10 +30,25 @@ class ReadMarkerHelper @Inject constructor() {
lateinit var layoutManager: LinearLayoutManager lateinit var layoutManager: LinearLayoutManager
var callback: Callback? = null var callback: Callback? = null
private var onReadMarkerLongDisplayed = false
private var readMarkerVisible: Boolean = true
private var state: RoomDetailViewState? = null private var state: RoomDetailViewState? = null
fun updateState(state: RoomDetailViewState) { fun readMarkerVisible(): Boolean {
this.state = state return readMarkerVisible
}
fun onResume() {
onReadMarkerLongDisplayed = false
}
fun onReadMarkerLongDisplayed() {
onReadMarkerLongDisplayed = true
}
fun updateWith(newState: RoomDetailViewState) {
state = newState
checkReadMarkerVisibility()
checkJumpToReadMarkerVisibility() checkJumpToReadMarkerVisibility()
} }
@ -41,34 +56,47 @@ class ReadMarkerHelper @Inject constructor() {
checkJumpToReadMarkerVisibility() checkJumpToReadMarkerVisibility()
} }
private fun checkReadMarkerVisibility() {
val nonNullState = this.state ?: return
val firstVisibleItem = layoutManager.findFirstVisibleItemPosition()
val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
readMarkerVisible = if (!onReadMarkerLongDisplayed) {
true
} else {
if (nonNullState.timeline?.isLive == false) {
true
} else {
!(firstVisibleItem == 0 && lastVisibleItem > 0)
}
}
}
private fun checkJumpToReadMarkerVisibility() { private fun checkJumpToReadMarkerVisibility() {
val nonNullState = this.state ?: return val nonNullState = this.state ?: return
val lastVisibleItem = layoutManager.findLastVisibleItemPosition() val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
val readMarkerId = nonNullState.asyncRoomSummary()?.readMarkerId val readMarkerId = nonNullState.asyncRoomSummary()?.readMarkerId
if (readMarkerId == null) { if (readMarkerId == null) {
callback?.onVisibilityUpdated(false, null) callback?.onJumpToReadMarkerVisibilityUpdate(false, null)
} }
val positionOfReadMarker = timelineEventController.searchPositionOfEvent(readMarkerId) val positionOfReadMarker = timelineEventController.searchPositionOfEvent(readMarkerId)
Timber.v("Position of readMarker: $positionOfReadMarker")
Timber.v("Position of lastVisibleItem: $lastVisibleItem")
if (positionOfReadMarker == null) { if (positionOfReadMarker == null) {
if (nonNullState.timeline?.isLive == true && lastVisibleItem > 0) { if (nonNullState.timeline?.isLive == true && lastVisibleItem > 0) {
callback?.onVisibilityUpdated(true, readMarkerId) callback?.onJumpToReadMarkerVisibilityUpdate(true, readMarkerId)
} else { } else {
callback?.onVisibilityUpdated(false, readMarkerId) callback?.onJumpToReadMarkerVisibilityUpdate(false, readMarkerId)
} }
} else { } else {
if (positionOfReadMarker > lastVisibleItem) { if (positionOfReadMarker > lastVisibleItem) {
callback?.onVisibilityUpdated(true, readMarkerId) callback?.onJumpToReadMarkerVisibilityUpdate(true, readMarkerId)
} else { } else {
callback?.onVisibilityUpdated(false, readMarkerId) callback?.onJumpToReadMarkerVisibilityUpdate(false, readMarkerId)
} }
} }
} }
interface Callback { interface Callback {
fun onVisibilityUpdated(show: Boolean, readMarkerId: String?) fun onJumpToReadMarkerVisibilityUpdate(show: Boolean, readMarkerId: String?)
} }

View file

@ -28,12 +28,7 @@ import android.os.Parcelable
import android.text.Editable import android.text.Editable
import android.text.Spannable import android.text.Spannable
import android.text.TextUtils import android.text.TextUtils
import android.view.HapticFeedbackConstants import android.view.*
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.Window
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
@ -51,13 +46,7 @@ import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView import butterknife.BindView
import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.EpoxyVisibilityTracker import com.airbnb.epoxy.EpoxyVisibilityTracker
import com.airbnb.mvrx.Async import com.airbnb.mvrx.*
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import com.github.piasy.biv.BigImageViewer import com.github.piasy.biv.BigImageViewer
import com.github.piasy.biv.loader.ImageLoader import com.github.piasy.biv.loader.ImageLoader
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
@ -71,13 +60,7 @@ import im.vector.matrix.android.api.permalinks.PermalinkFactory
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent import im.vector.matrix.android.api.session.room.model.message.*
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
@ -124,17 +107,8 @@ import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewState import im.vector.riotx.features.home.room.detail.composer.TextComposerViewState
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotx.features.home.room.detail.timeline.action.ActionsHandler import im.vector.riotx.features.home.room.detail.timeline.action.*
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet import im.vector.riotx.features.home.room.detail.timeline.item.*
import im.vector.riotx.features.home.room.detail.timeline.action.SimpleAction
import im.vector.riotx.features.home.room.detail.timeline.action.ViewEditHistoryBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.action.ViewReactionBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.item.AbsMessageItem
import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem
import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
import im.vector.riotx.features.html.EventHtmlRenderer import im.vector.riotx.features.html.EventHtmlRenderer
import im.vector.riotx.features.html.PillImageSpan import im.vector.riotx.features.html.PillImageSpan
import im.vector.riotx.features.invite.VectorInviteView import im.vector.riotx.features.invite.VectorInviteView
@ -432,8 +406,8 @@ class RoomDetailFragment :
} }
override fun onResume() { override fun onResume() {
readMarkerHelper.onResume()
super.onResume() super.onResume()
notificationDrawerManager.setCurrentRoom(roomDetailArgs.roomId) notificationDrawerManager.setCurrentRoom(roomDetailArgs.roomId)
} }
@ -483,7 +457,7 @@ class RoomDetailFragment :
readMarkerHelper.timelineEventController = timelineEventController readMarkerHelper.timelineEventController = timelineEventController
readMarkerHelper.layoutManager = layoutManager readMarkerHelper.layoutManager = layoutManager
readMarkerHelper.callback = object : ReadMarkerHelper.Callback { readMarkerHelper.callback = object : ReadMarkerHelper.Callback {
override fun onVisibilityUpdated(show: Boolean, readMarkerId: String?) { override fun onJumpToReadMarkerVisibilityUpdate(show: Boolean, readMarkerId: String?) {
jumpToReadMarkerView.render(show, readMarkerId) jumpToReadMarkerView.render(show, readMarkerId)
} }
} }
@ -707,13 +681,13 @@ class RoomDetailFragment :
} }
private fun renderState(state: RoomDetailViewState) { private fun renderState(state: RoomDetailViewState) {
readMarkerHelper.updateState(state) readMarkerHelper.updateWith(state)
renderRoomSummary(state) renderRoomSummary(state)
val summary = state.asyncRoomSummary() val summary = state.asyncRoomSummary()
val inviter = state.asyncInviter() val inviter = state.asyncInviter()
if (summary?.membership == Membership.JOIN) { if (summary?.membership == Membership.JOIN) {
scrollOnHighlightedEventCallback.timeline = state.timeline scrollOnHighlightedEventCallback.timeline = state.timeline
timelineEventController.update(state) timelineEventController.update(state, readMarkerHelper.readMarkerVisible())
inviteView.visibility = View.GONE inviteView.visibility = View.GONE
val uid = session.myUserId val uid = session.myUserId
val meMember = session.getRoom(state.roomId)?.getRoomMember(uid) val meMember = session.getRoom(state.roomId)?.getRoomMember(uid)
@ -974,7 +948,10 @@ class RoomDetailFragment :
.show(requireActivity().supportFragmentManager, "DISPLAY_READ_RECEIPTS") .show(requireActivity().supportFragmentManager, "DISPLAY_READ_RECEIPTS")
} }
override fun onReadMarkerLongDisplayed() = withState(roomDetailViewModel) { state -> override fun onReadMarkerLongBound(isDisplayed: Boolean) {
if (isDisplayed) {
readMarkerHelper.onReadMarkerLongDisplayed()
}
val firstVisibleItem = layoutManager.findFirstVisibleItemPosition() val firstVisibleItem = layoutManager.findFirstVisibleItemPosition()
val nextReadMarkerId = timelineEventController.searchEventIdAtPosition(firstVisibleItem) val nextReadMarkerId = timelineEventController.searchEventIdAtPosition(firstVisibleItem)
if (nextReadMarkerId != null) { if (nextReadMarkerId != null) {
@ -982,6 +959,7 @@ class RoomDetailFragment :
} }
} }
// AutocompleteUserPresenter.Callback // AutocompleteUserPresenter.Callback
override fun onQueryUsers(query: CharSequence?) { override fun onQueryUsers(query: CharSequence?) {

View file

@ -21,8 +21,6 @@ import android.text.TextUtils
import androidx.annotation.IdRes import androidx.annotation.IdRes
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import arrow.core.Option
import arrow.core.getOrElse
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
@ -43,13 +41,11 @@ import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.model.message.getFileUrl import im.vector.matrix.android.api.session.room.model.message.getFileUrl
import im.vector.matrix.android.api.session.room.model.relation.ReactionContent
import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent
import im.vector.matrix.android.api.session.room.send.UserDraft import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
@ -62,11 +58,11 @@ import im.vector.riotx.core.utils.LiveEvent
import im.vector.riotx.core.utils.subscribeLogError import im.vector.riotx.core.utils.subscribeLogError
import im.vector.riotx.features.command.CommandParser import im.vector.riotx.features.command.CommandParser
import im.vector.riotx.features.command.ParsedCommand import im.vector.riotx.features.command.ParsedCommand
import im.vector.riotx.features.home.room.detail.timeline.TimelineLayoutManagerHolder
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.settings.VectorPreferences
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.functions.BiFunction import io.reactivex.functions.BiFunction
import io.reactivex.functions.Function3
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
import org.commonmark.parser.Parser import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer import org.commonmark.renderer.html.HtmlRenderer
@ -76,6 +72,7 @@ import java.util.concurrent.TimeUnit
class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: RoomDetailViewState, class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: RoomDetailViewState,
private val timelineLayoutManagerHolder: TimelineLayoutManagerHolder,
private val userPreferencesProvider: UserPreferencesProvider, private val userPreferencesProvider: UserPreferencesProvider,
private val vectorPreferences: VectorPreferences, private val vectorPreferences: VectorPreferences,
private val session: Session private val session: Session
@ -119,8 +116,9 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
observeRoomSummary() observeRoomSummary()
observeEventDisplayedActions() observeEventDisplayedActions()
observeSummaryState() observeSummaryState()
observeReadMarkerVisibility()
observeDrafts() observeDrafts()
observeReadMarkerVisibility()
observeOwnState()
room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear() room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear()
timeline.start() timeline.start()
setState { copy(timeline = this@RoomDetailViewModel.timeline) } setState { copy(timeline = this@RoomDetailViewModel.timeline) }
@ -711,23 +709,6 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
} }
} }
private fun observeReadMarkerVisibility() {
Observable
.combineLatest(
room.rx().liveReadMarker(),
room.rx().liveReadReceipt(),
BiFunction<Optional<String>, Optional<String>, Boolean> { readMarker, readReceipt ->
readMarker.getOrNull() == readReceipt.getOrNull()
}
)
.startWith(false)
.subscribe {
setState { copy(hideReadMarker = it) }
}
.disposeOnClear()
}
private fun observeSummaryState() { private fun observeSummaryState() {
asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary -> asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary ->
if (summary.membership == Membership.INVITE) { if (summary.membership == Membership.INVITE) {
@ -743,6 +724,22 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
} }
} }
private fun observeReadMarkerVisibility() {
Observable
.combineLatest(
room.rx().liveReadMarker(),
room.rx().liveReadReceipt(),
BiFunction<Optional<String>, Optional<String>, Boolean> { readMarker, readReceipt ->
readMarker.getOrNull() != readReceipt.getOrNull()
}
)
.subscribe {
setState { copy(readMarkerVisible = it) }
}
.disposeOnClear()
}
override fun onCleared() { override fun onCleared() {
timeline.dispose() timeline.dispose()
super.onCleared() super.onCleared()

View file

@ -53,7 +53,7 @@ data class RoomDetailViewState(
val tombstoneEventHandling: Async<String> = Uninitialized, val tombstoneEventHandling: Async<String> = Uninitialized,
val syncState: SyncState = SyncState.IDLE, val syncState: SyncState = SyncState.IDLE,
val highlightedEventId: String? = null, val highlightedEventId: String? = null,
val hideReadMarker: Boolean = false val readMarkerVisible: Boolean = false
) : MvRxState { ) : MvRxState {
constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId) constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId)

View file

@ -34,7 +34,10 @@ import im.vector.riotx.core.extensions.localDateTime
import im.vector.riotx.features.home.room.detail.RoomDetailViewState import im.vector.riotx.features.home.room.detail.RoomDetailViewState
import im.vector.riotx.features.home.room.detail.timeline.factory.MergedHeaderItemFactory import im.vector.riotx.features.home.room.detail.timeline.factory.MergedHeaderItemFactory
import im.vector.riotx.features.home.room.detail.timeline.factory.TimelineItemFactory import im.vector.riotx.features.home.room.detail.timeline.factory.TimelineItemFactory
import im.vector.riotx.features.home.room.detail.timeline.helper.* import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineEventVisibilityStateChangedListener
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.riotx.features.home.room.detail.timeline.helper.nextOrNull
import im.vector.riotx.features.home.room.detail.timeline.item.* import im.vector.riotx.features.home.room.detail.timeline.item.*
import im.vector.riotx.features.media.ImageContentRenderer import im.vector.riotx.features.media.ImageContentRenderer
import im.vector.riotx.features.media.VideoContentRenderer import im.vector.riotx.features.media.VideoContentRenderer
@ -79,7 +82,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
interface ReadReceiptsCallback { interface ReadReceiptsCallback {
fun onReadReceiptsClicked(readReceipts: List<ReadReceiptData>) fun onReadReceiptsClicked(readReceipts: List<ReadReceiptData>)
fun onReadMarkerLongDisplayed() fun onReadMarkerLongBound(isDisplayed: Boolean)
} }
interface UrlClickCallback { interface UrlClickCallback {
@ -141,7 +144,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
requestModelBuild() requestModelBuild()
} }
fun update(viewState: RoomDetailViewState) { fun update(viewState: RoomDetailViewState, readMarkerVisible: Boolean) {
if (timeline != viewState.timeline) { if (timeline != viewState.timeline) {
timeline = viewState.timeline timeline = viewState.timeline
timeline?.listener = this timeline?.listener = this
@ -166,8 +169,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
eventIdToHighlight = viewState.highlightedEventId eventIdToHighlight = viewState.highlightedEventId
requestModelBuild = true requestModelBuild = true
} }
if (hideReadMarker != viewState.hideReadMarker) { if (this.readMarkerVisible != readMarkerVisible) {
hideReadMarker = viewState.hideReadMarker this.readMarkerVisible = readMarkerVisible
requestModelBuild = true requestModelBuild = true
} }
if (requestModelBuild) { if (requestModelBuild) {
@ -175,7 +178,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
} }
} }
private var hideReadMarker: Boolean = false private var readMarkerVisible: Boolean = false
private var eventIdToHighlight: String? = null private var eventIdToHighlight: String? = null
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
@ -255,11 +258,19 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
val date = event.root.localDateTime() val date = event.root.localDateTime()
val nextDate = nextEvent?.root?.localDateTime() val nextDate = nextEvent?.root?.localDateTime()
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate() val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
val eventModel = timelineItemFactory.create(event, nextEvent, eventIdToHighlight, hideReadMarker, callback).also { val eventModel = timelineItemFactory.create(event, nextEvent, eventIdToHighlight, readMarkerVisible, callback).also {
it.id(event.localId) it.id(event.localId)
it.setOnVisibilityStateChanged(TimelineEventVisibilityStateChangedListener(callback, event)) it.setOnVisibilityStateChanged(TimelineEventVisibilityStateChangedListener(callback, event))
} }
val mergedHeaderModel = mergedHeaderItemFactory.create(event, nextEvent, items, addDaySeparator, currentPosition, eventIdToHighlight, callback) { val mergedHeaderModel = mergedHeaderItemFactory.create(event,
nextEvent = nextEvent,
items = items,
addDaySeparator = addDaySeparator,
readMarkerVisible = readMarkerVisible,
currentPosition = currentPosition,
eventIdToHighlight = eventIdToHighlight,
callback = callback
) {
requestModelBuild() requestModelBuild()
} }
val daySeparatorItem = buildDaySeparatorItem(addDaySeparator, date) val daySeparatorItem = buildDaySeparatorItem(addDaySeparator, date)

View file

@ -0,0 +1,29 @@
/*
* Copyright 2019 New Vector Ltd
*
* 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 im.vector.riotx.features.home.room.detail.timeline
import androidx.recyclerview.widget.LinearLayoutManager
import im.vector.riotx.core.di.ScreenScope
import javax.inject.Inject
@ScreenScope
class TimelineLayoutManagerHolder @Inject constructor() {
lateinit var layoutManager: LinearLayoutManager
}

View file

@ -31,7 +31,7 @@ class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: Ava
fun create(event: TimelineEvent, fun create(event: TimelineEvent,
highlight: Boolean, highlight: Boolean,
hideReadMarker: Boolean, readMarkerVisible: Boolean,
callback: TimelineEventController.Callback?, callback: TimelineEventController.Callback?,
exception: Exception? = null): DefaultItem? { exception: Exception? = null): DefaultItem? {
val text = if (exception == null) { val text = if (exception == null) {
@ -40,7 +40,7 @@ class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: Ava
"an exception occurred when rendering the event ${event.root.eventId}" "an exception occurred when rendering the event ${event.root.eventId}"
} }
val informationData = informationDataFactory.create(event, null, hideReadMarker) val informationData = informationDataFactory.create(event, null, readMarkerVisible)
return DefaultItem_() return DefaultItem_()
.leftGuideline(avatarSizeProvider.leftGuideline) .leftGuideline(avatarSizeProvider.leftGuideline)

View file

@ -41,7 +41,7 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
fun create(event: TimelineEvent, fun create(event: TimelineEvent,
nextEvent: TimelineEvent?, nextEvent: TimelineEvent?,
highlight: Boolean, highlight: Boolean,
hideReadMarker: Boolean, readMarkerVisible: Boolean,
callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? { callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? {
event.root.eventId ?: return null event.root.eventId ?: return null
@ -65,7 +65,7 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
// TODO This is not correct format for error, change it // TODO This is not correct format for error, change it
val informationData = messageInformationDataFactory.create(event, nextEvent, hideReadMarker) val informationData = messageInformationDataFactory.create(event, nextEvent, readMarkerVisible)
val attributes = attributesFactory.create(null, informationData, callback) val attributes = attributesFactory.create(null, informationData, callback)
return MessageTextItem_() return MessageTextItem_()
.leftGuideline(avatarSizeProvider.leftGuideline) .leftGuideline(avatarSizeProvider.leftGuideline)

View file

@ -18,15 +18,9 @@ package im.vector.riotx.features.home.room.detail.timeline.factory
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.extensions.displayReadMarker
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.riotx.features.home.room.detail.timeline.helper.*
import im.vector.riotx.features.home.room.detail.timeline.helper.MergedTimelineEventVisibilityStateChangedListener
import im.vector.riotx.features.home.room.detail.timeline.helper.canBeMerged
import im.vector.riotx.features.home.room.detail.timeline.helper.prevSameTypeEvents
import im.vector.riotx.features.home.room.detail.timeline.helper.senderAvatar
import im.vector.riotx.features.home.room.detail.timeline.helper.senderName
import im.vector.riotx.features.home.room.detail.timeline.item.MergedHeaderItem import im.vector.riotx.features.home.room.detail.timeline.item.MergedHeaderItem
import im.vector.riotx.features.home.room.detail.timeline.item.MergedHeaderItem_ import im.vector.riotx.features.home.room.detail.timeline.item.MergedHeaderItem_
import javax.inject.Inject import javax.inject.Inject
@ -42,6 +36,7 @@ class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: Act
nextEvent: TimelineEvent?, nextEvent: TimelineEvent?,
items: List<TimelineEvent>, items: List<TimelineEvent>,
addDaySeparator: Boolean, addDaySeparator: Boolean,
readMarkerVisible: Boolean,
currentPosition: Int, currentPosition: Int,
eventIdToHighlight: String?, eventIdToHighlight: String?,
callback: TimelineEventController.Callback?, callback: TimelineEventController.Callback?,
@ -67,7 +62,7 @@ class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: Act
if (readMarkerId == null && mergedEvent.hasReadMarker) { if (readMarkerId == null && mergedEvent.hasReadMarker) {
readMarkerId = mergedEvent.root.eventId readMarkerId = mergedEvent.root.eventId
} }
if (!showReadMarker && mergedEvent.displayReadMarker(sessionHolder.getActiveSession().myUserId)) { if (!showReadMarker && mergedEvent.hasReadMarker && readMarkerVisible) {
showReadMarker = true showReadMarker = true
} }
val senderAvatar = mergedEvent.senderAvatar() val senderAvatar = mergedEvent.senderAvatar()

View file

@ -76,12 +76,12 @@ class MessageItemFactory @Inject constructor(
fun create(event: TimelineEvent, fun create(event: TimelineEvent,
nextEvent: TimelineEvent?, nextEvent: TimelineEvent?,
highlight: Boolean, highlight: Boolean,
hideReadMarker: Boolean, readMarkerVisible: Boolean,
callback: TimelineEventController.Callback? callback: TimelineEventController.Callback?
): VectorEpoxyModel<*>? { ): VectorEpoxyModel<*>? {
event.root.eventId ?: return null event.root.eventId ?: return null
val informationData = messageInformationDataFactory.create(event, nextEvent, hideReadMarker) val informationData = messageInformationDataFactory.create(event, nextEvent, readMarkerVisible)
if (event.root.isRedacted()) { if (event.root.isRedacted()) {
//message is redacted //message is redacted
@ -98,7 +98,7 @@ class MessageItemFactory @Inject constructor(
|| event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE || event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE
) { ) {
// This is an edit event, we should it when debugging as a notice event // This is an edit event, we should it when debugging as a notice event
return noticeItemFactory.create(event, highlight, hideReadMarker, callback) return noticeItemFactory.create(event, highlight, readMarkerVisible, callback)
} }
val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback) val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback)

View file

@ -34,11 +34,11 @@ class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEv
fun create(event: TimelineEvent, fun create(event: TimelineEvent,
highlight: Boolean, highlight: Boolean,
hideReadMarker: Boolean, readMarkerVisible: Boolean,
callback: TimelineEventController.Callback?): NoticeItem? { callback: TimelineEventController.Callback?): NoticeItem? {
val formattedText = eventFormatter.format(event) ?: return null val formattedText = eventFormatter.format(event) ?: return null
val informationData = informationDataFactory.create(event, null, hideReadMarker) val informationData = informationDataFactory.create(event, null, readMarkerVisible)
val attributes = NoticeItem.Attributes( val attributes = NoticeItem.Attributes(
avatarRenderer = avatarRenderer, avatarRenderer = avatarRenderer,
informationData = informationData, informationData = informationData,

View file

@ -33,14 +33,14 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
fun create(event: TimelineEvent, fun create(event: TimelineEvent,
nextEvent: TimelineEvent?, nextEvent: TimelineEvent?,
eventIdToHighlight: String?, eventIdToHighlight: String?,
hideReadMarker: Boolean, readMarkerVisible: Boolean,
callback: TimelineEventController.Callback?): VectorEpoxyModel<*> { callback: TimelineEventController.Callback?): VectorEpoxyModel<*> {
val highlight = event.root.eventId == eventIdToHighlight val highlight = event.root.eventId == eventIdToHighlight
val computedModel = try { val computedModel = try {
when (event.root.getClearType()) { when (event.root.getClearType()) {
EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, highlight, hideReadMarker, callback) EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, highlight, readMarkerVisible, callback)
// State and call // State and call
EventType.STATE_ROOM_TOMBSTONE, EventType.STATE_ROOM_TOMBSTONE,
EventType.STATE_ROOM_NAME, EventType.STATE_ROOM_NAME,
@ -52,22 +52,22 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
EventType.CALL_ANSWER, EventType.CALL_ANSWER,
EventType.REACTION, EventType.REACTION,
EventType.REDACTION, EventType.REDACTION,
EventType.ENCRYPTION -> noticeItemFactory.create(event, highlight, hideReadMarker, callback) EventType.ENCRYPTION -> noticeItemFactory.create(event, highlight, readMarkerVisible, callback)
// State room create // State room create
EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback) EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback)
// Crypto // Crypto
EventType.ENCRYPTED -> { EventType.ENCRYPTED -> {
if (event.root.isRedacted()) { if (event.root.isRedacted()) {
// Redacted event, let the MessageItemFactory handle it // Redacted event, let the MessageItemFactory handle it
messageItemFactory.create(event, nextEvent, highlight, hideReadMarker, callback) messageItemFactory.create(event, nextEvent, highlight, readMarkerVisible, callback)
} else { } else {
encryptedItemFactory.create(event, nextEvent, highlight, hideReadMarker, callback) encryptedItemFactory.create(event, nextEvent, highlight, readMarkerVisible, callback)
} }
} }
// Unhandled event types (yet) // Unhandled event types (yet)
EventType.STATE_ROOM_THIRD_PARTY_INVITE, EventType.STATE_ROOM_THIRD_PARTY_INVITE,
EventType.STICKER -> defaultItemFactory.create(event, highlight, hideReadMarker, callback) EventType.STICKER -> defaultItemFactory.create(event, highlight, readMarkerVisible, callback)
else -> { else -> {
Timber.v("Type ${event.root.getClearType()} not handled") Timber.v("Type ${event.root.getClearType()} not handled")
null null
@ -75,7 +75,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
} }
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "failed to create message item") Timber.e(e, "failed to create message item")
defaultItemFactory.create(event, highlight, hideReadMarker, callback, e) defaultItemFactory.create(event, highlight, readMarkerVisible, callback, e)
} }
return (computedModel ?: EmptyItem_()) return (computedModel ?: EmptyItem_())
} }

View file

@ -24,10 +24,8 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited
import im.vector.riotx.core.extensions.localDateTime import im.vector.riotx.core.extensions.localDateTime
import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.utils.isSingleEmoji
import im.vector.riotx.features.home.getColorFromUserId import im.vector.riotx.features.home.getColorFromUserId
import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.core.date.VectorDateFormatter
import im.vector.riotx.core.extensions.displayReadMarker
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotx.features.home.room.detail.timeline.item.ReactionInfoData import im.vector.riotx.features.home.room.detail.timeline.item.ReactionInfoData
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
@ -41,7 +39,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
private val dateFormatter: VectorDateFormatter, private val dateFormatter: VectorDateFormatter,
private val colorProvider: ColorProvider) { private val colorProvider: ColorProvider) {
fun create(event: TimelineEvent, nextEvent: TimelineEvent?, hideReadMarker: Boolean): MessageInformationData { fun create(event: TimelineEvent, nextEvent: TimelineEvent?, readMarkerVisible: Boolean): MessageInformationData {
// Non nullability has been tested before // Non nullability has been tested before
val eventId = event.root.eventId!! val eventId = event.root.eventId!!
@ -65,7 +63,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId ?: "")) textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId ?: ""))
} }
val displayReadMarker = !hideReadMarker && event.displayReadMarker(session.myUserId) val displayReadMarker = readMarkerVisible && event.hasReadMarker
return MessageInformationData( return MessageInformationData(
eventId = eventId, eventId = eventId,

View file

@ -57,8 +57,8 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
private val _readMarkerCallback = object : ReadMarkerView.Callback { private val _readMarkerCallback = object : ReadMarkerView.Callback {
override fun onReadMarkerLongBound() { override fun onReadMarkerLongBound(isDisplayed: Boolean) {
attributes.readReceiptsCallback?.onReadMarkerLongDisplayed() attributes.readReceiptsCallback?.onReadMarkerLongBound(isDisplayed)
} }
} }

View file

@ -41,8 +41,8 @@ abstract class MergedHeaderItem : BaseEventItem<MergedHeaderItem.Holder>() {
private val _readMarkerCallback = object : ReadMarkerView.Callback { private val _readMarkerCallback = object : ReadMarkerView.Callback {
override fun onReadMarkerLongBound() { override fun onReadMarkerLongBound(isDisplayed: Boolean) {
attributes.readReceiptsCallback?.onReadMarkerLongDisplayed() attributes.readReceiptsCallback?.onReadMarkerLongBound(isDisplayed)
} }
} }

View file

@ -38,8 +38,9 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
}) })
private val _readMarkerCallback = object : ReadMarkerView.Callback { private val _readMarkerCallback = object : ReadMarkerView.Callback {
override fun onReadMarkerLongBound() {
attributes.readReceiptsCallback?.onReadMarkerLongDisplayed() override fun onReadMarkerLongBound(isDisplayed: Boolean) {
attributes.readReceiptsCallback?.onReadMarkerLongBound(isDisplayed)
} }
} }