mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-25 19:05:56 +03:00
room summary now has constant height (#7145)
This commit is contained in:
parent
0fea172154
commit
830e5ffa9f
17 changed files with 222 additions and 84 deletions
1
changelog.d/7079.bugfix
Normal file
1
changelog.d/7079.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Fixed problem when room list's scroll did jump after rooms placeholders were replaced with rooms summary items
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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.core.utils
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This observer detects when item was added or moved to the first position of the adapter, while recyclerView is scrolled to the top. This is necessary
|
||||||
|
* to force recycler to scroll to the top to make such item visible, because by default it will keep items on screen, while adding new item to the top,
|
||||||
|
* outside of the viewport
|
||||||
|
* @param layoutManager - [LinearLayoutManager] of the recycler view, which displays items
|
||||||
|
* @property onItemUpdated - callback to be called, when observer detects event
|
||||||
|
*/
|
||||||
|
class FirstItemUpdatedObserver(
|
||||||
|
layoutManager: LinearLayoutManager,
|
||||||
|
private val onItemUpdated: () -> Unit
|
||||||
|
) : RecyclerView.AdapterDataObserver() {
|
||||||
|
|
||||||
|
val layoutManager: LinearLayoutManager? by weak(layoutManager)
|
||||||
|
|
||||||
|
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
|
||||||
|
if ((toPosition == 0 || fromPosition == 0) && layoutManager?.findFirstCompletelyVisibleItemPosition() == 0) {
|
||||||
|
onItemUpdated.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||||
|
if (positionStart == 0 && layoutManager?.findFirstCompletelyVisibleItemPosition() == 0) {
|
||||||
|
onItemUpdated.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -103,6 +103,9 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>(R.layo
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var showSelected: Boolean = false
|
var showSelected: Boolean = false
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var useSingleLineForLastEvent: Boolean = false
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
|
|
||||||
|
@ -122,6 +125,10 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>(R.layo
|
||||||
holder.roomAvatarFailSendingImageView.isVisible = hasFailedSending
|
holder.roomAvatarFailSendingImageView.isVisible = hasFailedSending
|
||||||
renderSelection(holder, showSelected)
|
renderSelection(holder, showSelected)
|
||||||
holder.roomAvatarPresenceImageView.render(showPresence, userPresence)
|
holder.roomAvatarPresenceImageView.render(showPresence, userPresence)
|
||||||
|
|
||||||
|
if (useSingleLineForLastEvent) {
|
||||||
|
holder.subtitleView.setLines(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderDisplayMode(holder: Holder) = when (displayMode) {
|
private fun renderDisplayMode(holder: Holder) = when (displayMode) {
|
||||||
|
|
|
@ -51,7 +51,8 @@ class RoomSummaryItemFactory @Inject constructor(
|
||||||
roomChangeMembershipStates: Map<String, ChangeMembershipState>,
|
roomChangeMembershipStates: Map<String, ChangeMembershipState>,
|
||||||
selectedRoomIds: Set<String>,
|
selectedRoomIds: Set<String>,
|
||||||
displayMode: RoomListDisplayMode,
|
displayMode: RoomListDisplayMode,
|
||||||
listener: RoomListListener?
|
listener: RoomListListener?,
|
||||||
|
singleLineLastEvent: Boolean = false
|
||||||
): VectorEpoxyModel<*> {
|
): VectorEpoxyModel<*> {
|
||||||
return when (roomSummary.membership) {
|
return when (roomSummary.membership) {
|
||||||
Membership.INVITE -> {
|
Membership.INVITE -> {
|
||||||
|
@ -59,7 +60,7 @@ class RoomSummaryItemFactory @Inject constructor(
|
||||||
createInvitationItem(roomSummary, changeMembershipState, listener)
|
createInvitationItem(roomSummary, changeMembershipState, listener)
|
||||||
}
|
}
|
||||||
else -> createRoomItem(
|
else -> createRoomItem(
|
||||||
roomSummary, selectedRoomIds, displayMode, listener?.let { it::onRoomClicked }, listener?.let { it::onRoomLongClicked }
|
roomSummary, selectedRoomIds, displayMode, singleLineLastEvent, listener?.let { it::onRoomClicked }, listener?.let { it::onRoomLongClicked }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,8 +119,9 @@ class RoomSummaryItemFactory @Inject constructor(
|
||||||
roomSummary: RoomSummary,
|
roomSummary: RoomSummary,
|
||||||
selectedRoomIds: Set<String>,
|
selectedRoomIds: Set<String>,
|
||||||
displayMode: RoomListDisplayMode,
|
displayMode: RoomListDisplayMode,
|
||||||
|
singleLineLastEvent: Boolean,
|
||||||
onClick: ((RoomSummary) -> Unit)?,
|
onClick: ((RoomSummary) -> Unit)?,
|
||||||
onLongClick: ((RoomSummary) -> Boolean)?
|
onLongClick: ((RoomSummary) -> Boolean)?,
|
||||||
): VectorEpoxyModel<*> {
|
): VectorEpoxyModel<*> {
|
||||||
val subtitle = getSearchResultSubtitle(roomSummary)
|
val subtitle = getSearchResultSubtitle(roomSummary)
|
||||||
val unreadCount = roomSummary.notificationCount
|
val unreadCount = roomSummary.notificationCount
|
||||||
|
@ -140,7 +142,7 @@ class RoomSummaryItemFactory @Inject constructor(
|
||||||
} else {
|
} else {
|
||||||
createRoomSummaryItem(
|
createRoomSummaryItem(
|
||||||
roomSummary, displayMode, subtitle, latestEventTime, typingMessage,
|
roomSummary, displayMode, subtitle, latestEventTime, typingMessage,
|
||||||
latestFormattedEvent, showHighlighted, showSelected, unreadCount, onClick, onLongClick
|
latestFormattedEvent, showHighlighted, showSelected, unreadCount, singleLineLastEvent, onClick, onLongClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,6 +157,7 @@ class RoomSummaryItemFactory @Inject constructor(
|
||||||
showHighlighted: Boolean,
|
showHighlighted: Boolean,
|
||||||
showSelected: Boolean,
|
showSelected: Boolean,
|
||||||
unreadCount: Int,
|
unreadCount: Int,
|
||||||
|
singleLineLastEvent: Boolean,
|
||||||
onClick: ((RoomSummary) -> Unit)?,
|
onClick: ((RoomSummary) -> Unit)?,
|
||||||
onLongClick: ((RoomSummary) -> Boolean)?
|
onLongClick: ((RoomSummary) -> Boolean)?
|
||||||
) = RoomSummaryItem_()
|
) = RoomSummaryItem_()
|
||||||
|
@ -177,6 +180,7 @@ class RoomSummaryItemFactory @Inject constructor(
|
||||||
.unreadNotificationCount(unreadCount)
|
.unreadNotificationCount(unreadCount)
|
||||||
.hasUnreadMessage(roomSummary.hasUnreadMessages)
|
.hasUnreadMessage(roomSummary.hasUnreadMessages)
|
||||||
.hasDraft(roomSummary.userDrafts.isNotEmpty())
|
.hasDraft(roomSummary.userDrafts.isNotEmpty())
|
||||||
|
.useSingleLineForLastEvent(singleLineLastEvent)
|
||||||
.itemLongClickListener { _ -> onLongClick?.invoke(roomSummary) ?: false }
|
.itemLongClickListener { _ -> onLongClick?.invoke(roomSummary) ?: false }
|
||||||
.itemClickListener { onClick?.invoke(roomSummary) }
|
.itemClickListener { onClick?.invoke(roomSummary) }
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package im.vector.app.features.home.room.list
|
package im.vector.app.features.home.room.list
|
||||||
|
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||||
|
@ -23,5 +25,18 @@ import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
|
|
||||||
@EpoxyModelClass
|
@EpoxyModelClass
|
||||||
abstract class RoomSummaryItemPlaceHolder : VectorEpoxyModel<RoomSummaryItemPlaceHolder.Holder>(R.layout.item_room_placeholder) {
|
abstract class RoomSummaryItemPlaceHolder : VectorEpoxyModel<RoomSummaryItemPlaceHolder.Holder>(R.layout.item_room_placeholder) {
|
||||||
class Holder : VectorEpoxyHolder()
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var useSingleLineForLastEvent: Boolean = false
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
if (useSingleLineForLastEvent) {
|
||||||
|
holder.subtitleView.setLines(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val subtitleView by bind<TextView>(R.id.subtitleView)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,18 +17,26 @@
|
||||||
package im.vector.app.features.home.room.list
|
package im.vector.app.features.home.room.list
|
||||||
|
|
||||||
import im.vector.app.features.home.RoomListDisplayMode
|
import im.vector.app.features.home.RoomListDisplayMode
|
||||||
|
import im.vector.app.features.settings.FontScalePreferences
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
|
||||||
class RoomSummaryListController(
|
class RoomSummaryListController(
|
||||||
private val roomSummaryItemFactory: RoomSummaryItemFactory,
|
private val roomSummaryItemFactory: RoomSummaryItemFactory,
|
||||||
private val displayMode: RoomListDisplayMode
|
private val displayMode: RoomListDisplayMode,
|
||||||
|
fontScalePreferences: FontScalePreferences
|
||||||
) : CollapsableTypedEpoxyController<List<RoomSummary>>() {
|
) : CollapsableTypedEpoxyController<List<RoomSummary>>() {
|
||||||
|
|
||||||
var listener: RoomListListener? = null
|
var listener: RoomListListener? = null
|
||||||
|
private val shouldUseSingleLine: Boolean
|
||||||
|
|
||||||
|
init {
|
||||||
|
val fontScale = fontScalePreferences.getResolvedFontScaleValue()
|
||||||
|
shouldUseSingleLine = fontScale.scale > FontScalePreferences.SCALE_LARGE
|
||||||
|
}
|
||||||
|
|
||||||
override fun buildModels(data: List<RoomSummary>?) {
|
override fun buildModels(data: List<RoomSummary>?) {
|
||||||
data?.forEach {
|
data?.forEach {
|
||||||
add(roomSummaryItemFactory.create(it, emptyMap(), emptySet(), displayMode, listener))
|
add(roomSummaryItemFactory.create(it, emptyMap(), emptySet(), displayMode, listener, shouldUseSingleLine))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,18 +20,26 @@ import com.airbnb.epoxy.EpoxyModel
|
||||||
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
||||||
import im.vector.app.core.utils.createUIHandler
|
import im.vector.app.core.utils.createUIHandler
|
||||||
import im.vector.app.features.home.RoomListDisplayMode
|
import im.vector.app.features.home.RoomListDisplayMode
|
||||||
|
import im.vector.app.features.settings.FontScalePreferences
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
|
||||||
class RoomSummaryPagedController(
|
class RoomSummaryPagedController(
|
||||||
private val roomSummaryItemFactory: RoomSummaryItemFactory,
|
private val roomSummaryItemFactory: RoomSummaryItemFactory,
|
||||||
private val displayMode: RoomListDisplayMode
|
private val displayMode: RoomListDisplayMode,
|
||||||
|
fontScalePreferences: FontScalePreferences
|
||||||
) : PagedListEpoxyController<RoomSummary>(
|
) : PagedListEpoxyController<RoomSummary>(
|
||||||
// Important it must match the PageList builder notify Looper
|
// Important it must match the PageList builder notify Looper
|
||||||
modelBuildingHandler = createUIHandler()
|
modelBuildingHandler = createUIHandler()
|
||||||
), CollapsableControllerExtension {
|
), CollapsableControllerExtension {
|
||||||
|
|
||||||
var listener: RoomListListener? = null
|
var listener: RoomListListener? = null
|
||||||
|
private val shouldUseSingleLine: Boolean
|
||||||
|
|
||||||
|
init {
|
||||||
|
val fontScale = fontScalePreferences.getResolvedFontScaleValue()
|
||||||
|
shouldUseSingleLine = fontScale.scale > FontScalePreferences.SCALE_LARGE
|
||||||
|
}
|
||||||
|
|
||||||
var roomChangeMembershipStates: Map<String, ChangeMembershipState>? = null
|
var roomChangeMembershipStates: Map<String, ChangeMembershipState>? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
|
@ -57,8 +65,14 @@ class RoomSummaryPagedController(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
|
override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
|
||||||
// for place holder if enabled
|
return if (item == null) {
|
||||||
item ?: return RoomSummaryItemPlaceHolder_().apply { id(currentPosition) }
|
val host = this
|
||||||
return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), displayMode, listener)
|
RoomSummaryItemPlaceHolder_().apply {
|
||||||
|
id(currentPosition)
|
||||||
|
useSingleLineForLastEvent(host.shouldUseSingleLine)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), displayMode, listener, shouldUseSingleLine)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,18 +17,20 @@
|
||||||
package im.vector.app.features.home.room.list
|
package im.vector.app.features.home.room.list
|
||||||
|
|
||||||
import im.vector.app.features.home.RoomListDisplayMode
|
import im.vector.app.features.home.RoomListDisplayMode
|
||||||
|
import im.vector.app.features.settings.FontScalePreferences
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomSummaryPagedControllerFactory @Inject constructor(
|
class RoomSummaryPagedControllerFactory @Inject constructor(
|
||||||
private val roomSummaryItemFactory: RoomSummaryItemFactory
|
private val roomSummaryItemFactory: RoomSummaryItemFactory,
|
||||||
|
private val fontScalePreferences: FontScalePreferences
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun createRoomSummaryPagedController(displayMode: RoomListDisplayMode): RoomSummaryPagedController {
|
fun createRoomSummaryPagedController(displayMode: RoomListDisplayMode): RoomSummaryPagedController {
|
||||||
return RoomSummaryPagedController(roomSummaryItemFactory, displayMode)
|
return RoomSummaryPagedController(roomSummaryItemFactory, displayMode, fontScalePreferences)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createRoomSummaryListController(displayMode: RoomListDisplayMode): RoomSummaryListController {
|
fun createRoomSummaryListController(displayMode: RoomListDisplayMode): RoomSummaryListController {
|
||||||
return RoomSummaryListController(roomSummaryItemFactory, displayMode)
|
return RoomSummaryListController(roomSummaryItemFactory, displayMode, fontScalePreferences)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createSuggestedRoomListController(): SuggestedRoomListController {
|
fun createSuggestedRoomListController(): SuggestedRoomListController {
|
||||||
|
|
|
@ -24,12 +24,14 @@ import im.vector.app.features.home.RoomListDisplayMode
|
||||||
import im.vector.app.features.home.room.list.RoomListListener
|
import im.vector.app.features.home.room.list.RoomListListener
|
||||||
import im.vector.app.features.home.room.list.RoomSummaryItemFactory
|
import im.vector.app.features.home.room.list.RoomSummaryItemFactory
|
||||||
import im.vector.app.features.home.room.list.RoomSummaryItemPlaceHolder_
|
import im.vector.app.features.home.room.list.RoomSummaryItemPlaceHolder_
|
||||||
|
import im.vector.app.features.settings.FontScalePreferences
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class HomeFilteredRoomsController @Inject constructor(
|
class HomeFilteredRoomsController @Inject constructor(
|
||||||
private val roomSummaryItemFactory: RoomSummaryItemFactory,
|
private val roomSummaryItemFactory: RoomSummaryItemFactory,
|
||||||
|
fontScalePreferences: FontScalePreferences
|
||||||
) : PagedListEpoxyController<RoomSummary>(
|
) : PagedListEpoxyController<RoomSummary>(
|
||||||
// Important it must match the PageList builder notify Looper
|
// Important it must match the PageList builder notify Looper
|
||||||
modelBuildingHandler = createUIHandler()
|
modelBuildingHandler = createUIHandler()
|
||||||
|
@ -47,6 +49,13 @@ class HomeFilteredRoomsController @Inject constructor(
|
||||||
private var emptyStateData: StateView.State.Empty? = null
|
private var emptyStateData: StateView.State.Empty? = null
|
||||||
private var currentState: StateView.State = StateView.State.Content
|
private var currentState: StateView.State = StateView.State.Content
|
||||||
|
|
||||||
|
private val shouldUseSingleLine: Boolean
|
||||||
|
|
||||||
|
init {
|
||||||
|
val fontScale = fontScalePreferences.getResolvedFontScaleValue()
|
||||||
|
shouldUseSingleLine = fontScale.scale > FontScalePreferences.SCALE_LARGE
|
||||||
|
}
|
||||||
|
|
||||||
override fun addModels(models: List<EpoxyModel<*>>) {
|
override fun addModels(models: List<EpoxyModel<*>>) {
|
||||||
if (models.isEmpty() && emptyStateData != null) {
|
if (models.isEmpty() && emptyStateData != null) {
|
||||||
emptyStateData?.let { emptyState ->
|
emptyStateData?.let { emptyState ->
|
||||||
|
@ -67,7 +76,14 @@ class HomeFilteredRoomsController @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
|
override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
|
||||||
item ?: return RoomSummaryItemPlaceHolder_().apply { id(currentPosition) }
|
return if (item == null) {
|
||||||
return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), RoomListDisplayMode.ROOMS, listener)
|
val host = this
|
||||||
|
RoomSummaryItemPlaceHolder_().apply {
|
||||||
|
id(currentPosition)
|
||||||
|
useSingleLineForLastEvent(host.shouldUseSingleLine)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), RoomListDisplayMode.ROOMS, listener, shouldUseSingleLine)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ import android.view.ViewGroup
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.ConcatAdapter
|
import androidx.recyclerview.widget.ConcatAdapter
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.airbnb.epoxy.OnModelBuildFinishedListener
|
import com.airbnb.epoxy.OnModelBuildFinishedListener
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
|
@ -36,6 +35,7 @@ import im.vector.app.core.extensions.cleanup
|
||||||
import im.vector.app.core.platform.StateView
|
import im.vector.app.core.platform.StateView
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.resources.UserPreferencesProvider
|
import im.vector.app.core.resources.UserPreferencesProvider
|
||||||
|
import im.vector.app.core.utils.FirstItemUpdatedObserver
|
||||||
import im.vector.app.databinding.FragmentRoomListBinding
|
import im.vector.app.databinding.FragmentRoomListBinding
|
||||||
import im.vector.app.features.analytics.plan.ViewRoom
|
import im.vector.app.features.analytics.plan.ViewRoom
|
||||||
import im.vector.app.features.home.room.list.RoomListAnimator
|
import im.vector.app.features.home.room.list.RoomListAnimator
|
||||||
|
@ -66,6 +66,7 @@ class HomeRoomListFragment :
|
||||||
private val roomListViewModel: HomeRoomListViewModel by fragmentViewModel()
|
private val roomListViewModel: HomeRoomListViewModel by fragmentViewModel()
|
||||||
private lateinit var sharedQuickActionsViewModel: RoomListQuickActionsSharedActionViewModel
|
private lateinit var sharedQuickActionsViewModel: RoomListQuickActionsSharedActionViewModel
|
||||||
private var concatAdapter = ConcatAdapter()
|
private var concatAdapter = ConcatAdapter()
|
||||||
|
private lateinit var firstItemObserver: FirstItemUpdatedObserver
|
||||||
private var modelBuildListener: OnModelBuildFinishedListener? = null
|
private var modelBuildListener: OnModelBuildFinishedListener? = null
|
||||||
|
|
||||||
private lateinit var stateRestorer: LayoutManagerStateRestorer
|
private lateinit var stateRestorer: LayoutManagerStateRestorer
|
||||||
|
@ -130,6 +131,9 @@ class HomeRoomListFragment :
|
||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
val layoutManager = LinearLayoutManager(context)
|
val layoutManager = LinearLayoutManager(context)
|
||||||
|
firstItemObserver = FirstItemUpdatedObserver(layoutManager) {
|
||||||
|
layoutManager.scrollToPosition(0)
|
||||||
|
}
|
||||||
stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
|
stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
|
||||||
views.roomListView.layoutManager = layoutManager
|
views.roomListView.layoutManager = layoutManager
|
||||||
views.roomListView.itemAnimator = RoomListAnimator()
|
views.roomListView.itemAnimator = RoomListAnimator()
|
||||||
|
@ -158,14 +162,7 @@ class HomeRoomListFragment :
|
||||||
|
|
||||||
views.roomListView.adapter = concatAdapter
|
views.roomListView.adapter = concatAdapter
|
||||||
|
|
||||||
// we need to force scroll when recents/filter tabs are added to make them visible
|
concatAdapter.registerAdapterDataObserver(firstItemObserver)
|
||||||
concatAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
|
||||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
|
||||||
if (positionStart == 0) {
|
|
||||||
layoutManager.scrollToPosition(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun invalidate() = withState(roomListViewModel) { state ->
|
override fun invalidate() = withState(roomListViewModel) { state ->
|
||||||
|
@ -233,6 +230,8 @@ class HomeRoomListFragment :
|
||||||
|
|
||||||
roomsController.listener = null
|
roomsController.listener = null
|
||||||
|
|
||||||
|
concatAdapter.unregisterAdapterDataObserver(firstItemObserver)
|
||||||
|
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ package im.vector.app.features.home.room.list.home.header
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.airbnb.epoxy.Carousel
|
import com.airbnb.epoxy.Carousel
|
||||||
import com.airbnb.epoxy.CarouselModelBuilder
|
import com.airbnb.epoxy.CarouselModelBuilder
|
||||||
import com.airbnb.epoxy.EpoxyController
|
import com.airbnb.epoxy.EpoxyController
|
||||||
|
@ -27,6 +27,7 @@ import com.airbnb.epoxy.carousel
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.core.utils.FirstItemUpdatedObserver
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import im.vector.app.features.home.room.list.RoomListListener
|
import im.vector.app.features.home.room.list.RoomListListener
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
@ -47,22 +48,7 @@ class HomeRoomsHeadersController @Inject constructor(
|
||||||
|
|
||||||
private var carousel: Carousel? = null
|
private var carousel: Carousel? = null
|
||||||
|
|
||||||
private val carouselAdapterObserver = object : RecyclerView.AdapterDataObserver() {
|
private var carouselAdapterObserver: FirstItemUpdatedObserver? = null
|
||||||
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
|
|
||||||
if (toPosition == 0 || fromPosition == 0) {
|
|
||||||
carousel?.post {
|
|
||||||
carousel?.layoutManager?.scrollToPosition(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.onItemRangeMoved(fromPosition, toPosition, itemCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
|
||||||
if (positionStart == 0) {
|
|
||||||
carousel?.layoutManager?.scrollToPosition(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val recentsHPadding = TypedValue.applyDimension(
|
private val recentsHPadding = TypedValue.applyDimension(
|
||||||
TypedValue.COMPLEX_UNIT_DIP,
|
TypedValue.COMPLEX_UNIT_DIP,
|
||||||
|
@ -113,25 +99,16 @@ class HomeRoomsHeadersController @Inject constructor(
|
||||||
)
|
)
|
||||||
onBind { _, view, _ ->
|
onBind { _, view, _ ->
|
||||||
host.carousel = view
|
host.carousel = view
|
||||||
|
host.unsubscribeAdapterObserver()
|
||||||
|
host.subscribeAdapterObserver()
|
||||||
|
|
||||||
val colorSurface = MaterialColors.getColor(view, R.attr.vctr_toolbar_background)
|
val colorSurface = MaterialColors.getColor(view, R.attr.vctr_toolbar_background)
|
||||||
view.setBackgroundColor(colorSurface)
|
view.setBackgroundColor(colorSurface)
|
||||||
|
|
||||||
try {
|
|
||||||
view.adapter?.registerAdapterDataObserver(host.carouselAdapterObserver)
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onUnbind { _, view ->
|
onUnbind { _, _ ->
|
||||||
host.carousel = null
|
host.carousel = null
|
||||||
|
host.unsubscribeAdapterObserver()
|
||||||
try {
|
|
||||||
view.adapter?.unregisterAdapterDataObserver(host.carouselAdapterObserver)
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
withModelsFrom(recents) { roomSummary ->
|
withModelsFrom(recents) { roomSummary ->
|
||||||
|
@ -150,6 +127,33 @@ class HomeRoomsHeadersController @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun unsubscribeAdapterObserver() {
|
||||||
|
carouselAdapterObserver?.let { observer ->
|
||||||
|
try {
|
||||||
|
carousel?.adapter?.unregisterAdapterDataObserver(observer)
|
||||||
|
carouselAdapterObserver = null
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun subscribeAdapterObserver() {
|
||||||
|
(carousel?.layoutManager as? LinearLayoutManager)?.let { layoutManager ->
|
||||||
|
carouselAdapterObserver = FirstItemUpdatedObserver(layoutManager) {
|
||||||
|
carousel?.post {
|
||||||
|
layoutManager.scrollToPosition(0)
|
||||||
|
}
|
||||||
|
}.also { observer ->
|
||||||
|
try {
|
||||||
|
carousel?.adapter?.registerAdapterDataObserver(observer)
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun addRoomFilterHeaderItem(
|
private fun addRoomFilterHeaderItem(
|
||||||
filterChangedListener: ((HomeRoomFilter) -> Unit)?,
|
filterChangedListener: ((HomeRoomFilter) -> Unit)?,
|
||||||
filtersList: List<HomeRoomFilter>,
|
filtersList: List<HomeRoomFilter>,
|
||||||
|
|
|
@ -57,6 +57,16 @@ interface FontScalePreferences {
|
||||||
* @return list of values
|
* @return list of values
|
||||||
*/
|
*/
|
||||||
fun getAvailableScales(): List<FontScaleValue>
|
fun getAvailableScales(): List<FontScaleValue>
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val SCALE_TINY = 0.70f
|
||||||
|
const val SCALE_SMALL = 0.85f
|
||||||
|
const val SCALE_NORMAL = 1.00f
|
||||||
|
const val SCALE_LARGE = 1.15f
|
||||||
|
const val SCALE_LARGER = 1.30f
|
||||||
|
const val SCALE_LARGEST = 1.45f
|
||||||
|
const val SCALE_HUGE = 1.60f
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,13 +83,13 @@ class FontScalePreferencesImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private val fontScaleValues = listOf(
|
private val fontScaleValues = listOf(
|
||||||
FontScaleValue(0, "FONT_SCALE_TINY", 0.70f, R.string.tiny),
|
FontScaleValue(0, "FONT_SCALE_TINY", FontScalePreferences.SCALE_TINY, R.string.tiny),
|
||||||
FontScaleValue(1, "FONT_SCALE_SMALL", 0.85f, R.string.small),
|
FontScaleValue(1, "FONT_SCALE_SMALL", FontScalePreferences.SCALE_SMALL, R.string.small),
|
||||||
FontScaleValue(2, "FONT_SCALE_NORMAL", 1.00f, R.string.normal),
|
FontScaleValue(2, "FONT_SCALE_NORMAL", FontScalePreferences.SCALE_NORMAL, R.string.normal),
|
||||||
FontScaleValue(3, "FONT_SCALE_LARGE", 1.15f, R.string.large),
|
FontScaleValue(3, "FONT_SCALE_LARGE", FontScalePreferences.SCALE_LARGE, R.string.large),
|
||||||
FontScaleValue(4, "FONT_SCALE_LARGER", 1.30f, R.string.larger),
|
FontScaleValue(4, "FONT_SCALE_LARGER", FontScalePreferences.SCALE_LARGER, R.string.larger),
|
||||||
FontScaleValue(5, "FONT_SCALE_LARGEST", 1.45f, R.string.largest),
|
FontScaleValue(5, "FONT_SCALE_LARGEST", FontScalePreferences.SCALE_LARGEST, R.string.largest),
|
||||||
FontScaleValue(6, "FONT_SCALE_HUGE", 1.60f, R.string.huge)
|
FontScaleValue(6, "FONT_SCALE_HUGE", FontScalePreferences.SCALE_HUGE, R.string.huge)
|
||||||
)
|
)
|
||||||
|
|
||||||
private val normalFontScaleValue = fontScaleValues[2]
|
private val normalFontScaleValue = fontScaleValues[2]
|
||||||
|
|
|
@ -60,6 +60,7 @@ class IncomingShareController @Inject constructor(
|
||||||
roomSummary,
|
roomSummary,
|
||||||
data.selectedRoomIds,
|
data.selectedRoomIds,
|
||||||
RoomListDisplayMode.FILTERED,
|
RoomListDisplayMode.FILTERED,
|
||||||
|
singleLineLastEvent = false,
|
||||||
callback?.let { it::onRoomClicked },
|
callback?.let { it::onRoomClicked },
|
||||||
callback?.let { it::onRoomLongClicked }
|
callback?.let { it::onRoomLongClicked }
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
|
|
||||||
<size android:width="40dp" android:height="40dp"/>
|
|
||||||
|
|
||||||
<solid android:color="?vctr_reaction_background_off" />
|
<solid android:color="?vctr_reaction_background_off" />
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
android:id="@+id/recentRoot"
|
android:id="@+id/recentRoot"
|
||||||
android:layout_width="84dp"
|
android:layout_width="84dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?vctr_toolbar_background"
|
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:foreground="?attr/selectableItemBackground"
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
|
|
@ -190,7 +190,7 @@
|
||||||
android:layout_marginTop="3dp"
|
android:layout_marginTop="3dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="2"
|
android:lines="2"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textColor="?vctr_content_secondary"
|
android:textColor="?vctr_content_secondary"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:layout_marginTop="12dp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
@ -29,23 +29,20 @@
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<!-- Margin bottom does not work, so I use space -->
|
<TextView
|
||||||
<Space
|
|
||||||
android:id="@+id/roomAvatarBottomSpace"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="12dp"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/roomAvatarContainer"
|
|
||||||
tools:layout_marginStart="20dp" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/roomNameView"
|
android:id="@+id/roomNameView"
|
||||||
android:layout_width="wrap_content"
|
style="@style/Widget.Vector.TextView.Subtitle"
|
||||||
android:layout_height="15dp"
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="12dp"
|
||||||
android:layout_marginEnd="70dp"
|
android:layout_marginEnd="70dp"
|
||||||
android:background="@drawable/placeholder_shape_8"
|
android:background="@drawable/placeholder_shape_8"
|
||||||
|
android:duplicateParentState="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?vctr_content_primary"
|
||||||
|
android:textStyle="bold"
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="0.0"
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
@ -53,23 +50,38 @@
|
||||||
app:layout_constraintStart_toEndOf="@id/roomAvatarContainer"
|
app:layout_constraintStart_toEndOf="@id/roomAvatarContainer"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<View
|
<TextView
|
||||||
android:id="@+id/roomTypingView"
|
android:id="@+id/subtitleView"
|
||||||
|
style="@style/Widget.Vector.TextView.Body"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="30dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="3dp"
|
||||||
android:layout_marginEnd="20dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:background="@drawable/placeholder_shape_8"
|
android:background="@drawable/placeholder_shape_8"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:lines="2"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textColor="?vctr_content_secondary"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="@id/roomNameView"
|
app:layout_constraintStart_toStartOf="@id/roomNameView"
|
||||||
app:layout_constraintTop_toBottomOf="@id/roomNameView" />
|
app:layout_constraintTop_toBottomOf="@id/roomNameView" />
|
||||||
|
|
||||||
|
<!-- Margin bottom does not work, so I use space -->
|
||||||
|
<Space
|
||||||
|
android:id="@+id/roomAvatarBottomSpace"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="7dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/subtitleView"
|
||||||
|
tools:layout_marginStart="120dp" />
|
||||||
|
|
||||||
<!-- We use vctr_list_separator_system here for a better rendering -->
|
<!-- We use vctr_list_separator_system here for a better rendering -->
|
||||||
<View
|
<View
|
||||||
android:id="@+id/roomDividerView"
|
android:id="@+id/roomDividerView"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
android:background="?vctr_list_separator_system"
|
android:background="?vctr_list_separator_system"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/roomAvatarBottomSpace"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
Loading…
Reference in a new issue