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:
Jorge Martin Espinosa 2022-11-10 18:28:03 +01:00 committed by GitHub
parent b47dabba58
commit 008432af36
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 98 additions and 33 deletions

1
changelog.d/7496.feature Normal file
View file

@ -0,0 +1 @@
Move TypingView inside the timeline items.

View file

@ -48,9 +48,4 @@ class TypingMessageView @JvmOverloads constructor(
views.typingUserText.text = typingHelper.getNotificationTypingMessage(typingUsers)
views.typingUserAvatars.render(typingUsers, avatarRenderer)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
removeAllViews()
}
}

View file

@ -1154,7 +1154,6 @@ class TimelineFragment :
}
val summary = mainState.asyncRoomSummary()
renderToolbar(summary)
renderTypingMessageNotification(summary, mainState)
views.removeJitsiWidgetView.render(mainState)
if (mainState.hasFailedSending) {
lazyLoadedViews.failedMessagesWarningView(inflateIfNeeded = true, createFailedMessagesWarningCallback())?.isVisible = true
@ -1230,17 +1229,6 @@ class TimelineFragment :
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?) {
when {
isLocalRoom() -> {

View file

@ -32,6 +32,7 @@ import im.vector.app.core.extensions.localDateTime
import im.vector.app.core.extensions.nextOrNull
import im.vector.app.core.extensions.prevOrNull
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.RoomDetailAction
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.ReadReceiptData
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.media.AttachmentData
import im.vector.app.features.media.ImageContentRenderer
@ -94,6 +96,7 @@ class TimelineEventController @Inject constructor(
private val readReceiptsItemFactory: ReadReceiptsItemFactory,
private val reactionListFactory: ReactionsSummaryFactory,
private val clock: Clock,
private val avatarRenderer: AvatarRenderer,
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener, EpoxyController.Interceptor {
/**
@ -104,7 +107,7 @@ class TimelineEventController @Inject constructor(
val highlightedEventId: String? = null,
val jitsiState: JitsiState = JitsiState(),
val roomSummary: RoomSummary? = null,
val rootThreadEventId: String? = null
val rootThreadEventId: String? = null,
) {
constructor(state: RoomDetailViewState) : this(
@ -112,7 +115,7 @@ class TimelineEventController @Inject constructor(
highlightedEventId = state.highlightedEventId,
jitsiState = state.jitsiState,
roomSummary = state.asyncRoomSummary(),
rootThreadEventId = state.rootThreadEventId
rootThreadEventId = state.rootThreadEventId,
)
fun isFromThreadTimeline(): Boolean = rootThreadEventId != null
@ -286,7 +289,7 @@ class TimelineEventController @Inject constructor(
private val interceptorHelper = TimelineControllerInterceptorHelper(
::positionOfReadMarker,
adapterPositionMapping
adapterPositionMapping,
)
init {
@ -334,6 +337,12 @@ class TimelineEventController @Inject constructor(
.setVisibilityStateChangedListener(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()
add(timelineModels)
if (hasReachedInvite && hasUTD) {

View file

@ -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)
}
}

View file

@ -85,7 +85,7 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:overScrollMode="always"
app:layout_constraintBottom_toTopOf="@id/typingMessageView"
app:layout_constraintBottom_toTopOf="@id/bottomBarrier"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView"
@ -107,18 +107,6 @@
app:layout_constraintStart_toStartOf="parent"
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
android:id="@+id/notificationAreaView"
android:layout_width="0dp"

View file

@ -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" />