Move TypingView into the timeline as another item (#7565)
* Typing view as item in list * Don't show TypingItem if we're showing a forward loader
This commit is contained in:
parent
b47dabba58
commit
008432af36
|
@ -0,0 +1 @@
|
||||||
|
Move TypingView inside the timeline items.
|
|
@ -48,9 +48,4 @@ class TypingMessageView @JvmOverloads constructor(
|
||||||
views.typingUserText.text = typingHelper.getNotificationTypingMessage(typingUsers)
|
views.typingUserText.text = typingHelper.getNotificationTypingMessage(typingUsers)
|
||||||
views.typingUserAvatars.render(typingUsers, avatarRenderer)
|
views.typingUserAvatars.render(typingUsers, avatarRenderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromWindow() {
|
|
||||||
super.onDetachedFromWindow()
|
|
||||||
removeAllViews()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1154,7 +1154,6 @@ class TimelineFragment :
|
||||||
}
|
}
|
||||||
val summary = mainState.asyncRoomSummary()
|
val summary = mainState.asyncRoomSummary()
|
||||||
renderToolbar(summary)
|
renderToolbar(summary)
|
||||||
renderTypingMessageNotification(summary, mainState)
|
|
||||||
views.removeJitsiWidgetView.render(mainState)
|
views.removeJitsiWidgetView.render(mainState)
|
||||||
if (mainState.hasFailedSending) {
|
if (mainState.hasFailedSending) {
|
||||||
lazyLoadedViews.failedMessagesWarningView(inflateIfNeeded = true, createFailedMessagesWarningCallback())?.isVisible = true
|
lazyLoadedViews.failedMessagesWarningView(inflateIfNeeded = true, createFailedMessagesWarningCallback())?.isVisible = true
|
||||||
|
@ -1230,17 +1229,6 @@ class TimelineFragment :
|
||||||
voiceMessageRecorderContainer.isVisible = false
|
voiceMessageRecorderContainer.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderTypingMessageNotification(roomSummary: RoomSummary?, state: RoomDetailViewState) {
|
|
||||||
if (!isThreadTimeLine() && roomSummary != null) {
|
|
||||||
views.typingMessageView.isInvisible = state.typingUsers.isNullOrEmpty()
|
|
||||||
state.typingUsers
|
|
||||||
?.take(MAX_TYPING_MESSAGE_USERS_COUNT)
|
|
||||||
?.let { senders -> views.typingMessageView.render(senders, avatarRenderer) }
|
|
||||||
} else {
|
|
||||||
views.typingMessageView.isInvisible = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun renderToolbar(roomSummary: RoomSummary?) {
|
private fun renderToolbar(roomSummary: RoomSummary?) {
|
||||||
when {
|
when {
|
||||||
isLocalRoom() -> {
|
isLocalRoom() -> {
|
||||||
|
|
|
@ -32,6 +32,7 @@ import im.vector.app.core.extensions.localDateTime
|
||||||
import im.vector.app.core.extensions.nextOrNull
|
import im.vector.app.core.extensions.nextOrNull
|
||||||
import im.vector.app.core.extensions.prevOrNull
|
import im.vector.app.core.extensions.prevOrNull
|
||||||
import im.vector.app.core.time.Clock
|
import im.vector.app.core.time.Clock
|
||||||
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import im.vector.app.features.home.room.detail.JitsiState
|
import im.vector.app.features.home.room.detail.JitsiState
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailAction
|
import im.vector.app.features.home.room.detail.RoomDetailAction
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailViewState
|
import im.vector.app.features.home.room.detail.RoomDetailViewState
|
||||||
|
@ -57,6 +58,7 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageInformationD
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryEvents
|
import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryEvents
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
|
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem
|
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.TypingItem_
|
||||||
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
|
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
|
||||||
import im.vector.app.features.media.AttachmentData
|
import im.vector.app.features.media.AttachmentData
|
||||||
import im.vector.app.features.media.ImageContentRenderer
|
import im.vector.app.features.media.ImageContentRenderer
|
||||||
|
@ -94,6 +96,7 @@ class TimelineEventController @Inject constructor(
|
||||||
private val readReceiptsItemFactory: ReadReceiptsItemFactory,
|
private val readReceiptsItemFactory: ReadReceiptsItemFactory,
|
||||||
private val reactionListFactory: ReactionsSummaryFactory,
|
private val reactionListFactory: ReactionsSummaryFactory,
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
|
private val avatarRenderer: AvatarRenderer,
|
||||||
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener, EpoxyController.Interceptor {
|
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener, EpoxyController.Interceptor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -104,7 +107,7 @@ class TimelineEventController @Inject constructor(
|
||||||
val highlightedEventId: String? = null,
|
val highlightedEventId: String? = null,
|
||||||
val jitsiState: JitsiState = JitsiState(),
|
val jitsiState: JitsiState = JitsiState(),
|
||||||
val roomSummary: RoomSummary? = null,
|
val roomSummary: RoomSummary? = null,
|
||||||
val rootThreadEventId: String? = null
|
val rootThreadEventId: String? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
constructor(state: RoomDetailViewState) : this(
|
constructor(state: RoomDetailViewState) : this(
|
||||||
|
@ -112,7 +115,7 @@ class TimelineEventController @Inject constructor(
|
||||||
highlightedEventId = state.highlightedEventId,
|
highlightedEventId = state.highlightedEventId,
|
||||||
jitsiState = state.jitsiState,
|
jitsiState = state.jitsiState,
|
||||||
roomSummary = state.asyncRoomSummary(),
|
roomSummary = state.asyncRoomSummary(),
|
||||||
rootThreadEventId = state.rootThreadEventId
|
rootThreadEventId = state.rootThreadEventId,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun isFromThreadTimeline(): Boolean = rootThreadEventId != null
|
fun isFromThreadTimeline(): Boolean = rootThreadEventId != null
|
||||||
|
@ -286,7 +289,7 @@ class TimelineEventController @Inject constructor(
|
||||||
|
|
||||||
private val interceptorHelper = TimelineControllerInterceptorHelper(
|
private val interceptorHelper = TimelineControllerInterceptorHelper(
|
||||||
::positionOfReadMarker,
|
::positionOfReadMarker,
|
||||||
adapterPositionMapping
|
adapterPositionMapping,
|
||||||
)
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -334,6 +337,12 @@ class TimelineEventController @Inject constructor(
|
||||||
.setVisibilityStateChangedListener(Timeline.Direction.FORWARDS)
|
.setVisibilityStateChangedListener(Timeline.Direction.FORWARDS)
|
||||||
.addWhenLoading(Timeline.Direction.FORWARDS)
|
.addWhenLoading(Timeline.Direction.FORWARDS)
|
||||||
|
|
||||||
|
if (!showingForwardLoader) {
|
||||||
|
val typingUsers = partialState.roomSummary?.typingUsers.orEmpty()
|
||||||
|
val typingItem = TypingItem_().id("typing_view").avatarRenderer(avatarRenderer).users(typingUsers)
|
||||||
|
add(typingItem)
|
||||||
|
}
|
||||||
|
|
||||||
val timelineModels = getModels()
|
val timelineModels = getModels()
|
||||||
add(timelineModels)
|
add(timelineModels)
|
||||||
if (hasReachedInvite && hasUTD) {
|
if (hasReachedInvite && hasUTD) {
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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.app.features.home.room.detail.timeline.item
|
||||||
|
|
||||||
|
import androidx.core.view.isInvisible
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import com.airbnb.epoxy.EpoxyModelWithHolder
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.app.core.ui.views.TypingMessageView
|
||||||
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
|
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
||||||
|
|
||||||
|
@EpoxyModelClass
|
||||||
|
abstract class TypingItem : EpoxyModelWithHolder<TypingItem.TypingHolder>() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val MAX_TYPING_MESSAGE_USERS_COUNT = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||||
|
lateinit var avatarRenderer: AvatarRenderer
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var users: List<SenderInfo> = emptyList()
|
||||||
|
|
||||||
|
override fun getDefaultLayout(): Int = R.layout.item_typing_users
|
||||||
|
|
||||||
|
override fun bind(holder: TypingHolder) {
|
||||||
|
super.bind(holder)
|
||||||
|
|
||||||
|
val typingUsers = users.take(MAX_TYPING_MESSAGE_USERS_COUNT)
|
||||||
|
holder.typingView.apply {
|
||||||
|
animate().cancel()
|
||||||
|
val duration = 100L
|
||||||
|
if (typingUsers.isEmpty()) {
|
||||||
|
animate().translationY(height.toFloat())
|
||||||
|
.alpha(0f)
|
||||||
|
.setDuration(duration)
|
||||||
|
.withEndAction {
|
||||||
|
isInvisible = true
|
||||||
|
}.start()
|
||||||
|
} else {
|
||||||
|
isVisible = true
|
||||||
|
|
||||||
|
translationY = height.toFloat()
|
||||||
|
alpha = 0f
|
||||||
|
render(typingUsers, avatarRenderer)
|
||||||
|
animate().translationY(0f)
|
||||||
|
.alpha(1f)
|
||||||
|
.setDuration(duration)
|
||||||
|
.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TypingHolder : VectorEpoxyHolder() {
|
||||||
|
val typingView by bind<TypingMessageView>(R.id.typingMessageView)
|
||||||
|
}
|
||||||
|
}
|
|
@ -85,7 +85,7 @@
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:overScrollMode="always"
|
android:overScrollMode="always"
|
||||||
app:layout_constraintBottom_toTopOf="@id/typingMessageView"
|
app:layout_constraintBottom_toTopOf="@id/bottomBarrier"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView"
|
app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView"
|
||||||
|
@ -107,18 +107,6 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView" />
|
app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView" />
|
||||||
|
|
||||||
<im.vector.app.core.ui.views.TypingMessageView
|
|
||||||
android:id="@+id/typingMessageView"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="20dp"
|
|
||||||
android:paddingEnd="20dp"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/bottomBarrier"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/timelineRecyclerView" />
|
|
||||||
|
|
||||||
<im.vector.app.core.ui.views.NotificationAreaView
|
<im.vector.app.core.ui.views.NotificationAreaView
|
||||||
android:id="@+id/notificationAreaView"
|
android:id="@+id/notificationAreaView"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<im.vector.app.core.ui.views.TypingMessageView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/typingMessageView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingHorizontal="20dp"
|
||||||
|
android:visibility="invisible" />
|
Loading…
Reference in New Issue