Update add room screen as per design

This commit is contained in:
Valere 2021-04-27 14:15:52 +02:00
parent 7465ac2ef6
commit 31ffa65fd0
5 changed files with 174 additions and 16 deletions

View File

@ -24,4 +24,12 @@ interface UpdatableLivePageResult {
val livePagedList: LiveData<PagedList<RoomSummary>> val livePagedList: LiveData<PagedList<RoomSummary>>
fun updateQuery(builder: (RoomSummaryQueryParams) -> RoomSummaryQueryParams) fun updateQuery(builder: (RoomSummaryQueryParams) -> RoomSummaryQueryParams)
val liveBoundaries: LiveData<ResultBoundaries>
} }
data class ResultBoundaries(
val frontLoaded: Boolean = false,
val endLoaded: Boolean = false,
val zeroItemLoaded: Boolean = false
)

View File

@ -18,6 +18,7 @@
package org.matrix.android.sdk.internal.session.room.summary package org.matrix.android.sdk.internal.session.room.summary
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
import androidx.paging.LivePagedListBuilder import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList import androidx.paging.PagedList
@ -28,6 +29,7 @@ import io.realm.Sort
import io.realm.kotlin.where import io.realm.kotlin.where
import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter
import org.matrix.android.sdk.api.session.room.ResultBoundaries
import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
@ -187,9 +189,25 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
roomSummaryMapper.map(it) roomSummaryMapper.map(it)
} }
val boundaries = MutableLiveData(ResultBoundaries())
val mapped = monarchy.findAllPagedWithChanges( val mapped = monarchy.findAllPagedWithChanges(
realmDataSourceFactory, realmDataSourceFactory,
LivePagedListBuilder(dataSourceFactory, pagedListConfig) LivePagedListBuilder(dataSourceFactory, pagedListConfig).also {
it.setBoundaryCallback(object : PagedList.BoundaryCallback<RoomSummary>() {
override fun onItemAtEndLoaded(itemAtEnd: RoomSummary) {
boundaries.postValue(boundaries.value?.copy(frontLoaded = true))
}
override fun onItemAtFrontLoaded(itemAtFront: RoomSummary) {
boundaries.postValue(boundaries.value?.copy(endLoaded = true))
}
override fun onZeroItemsLoaded() {
boundaries.postValue(boundaries.value?.copy(zeroItemLoaded = true))
}
})
}
) )
return object : UpdatableLivePageResult { return object : UpdatableLivePageResult {
@ -200,6 +218,9 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
roomSummariesQuery(it, builder.invoke(queryParams)).process(sortOrder) roomSummariesQuery(it, builder.invoke(queryParams)).process(sortOrder)
} }
} }
override val liveBoundaries: LiveData<ResultBoundaries>
get() = boundaries
} }
} }

View File

@ -22,6 +22,8 @@ import com.airbnb.epoxy.paging.PagedListEpoxyController
import im.vector.app.core.utils.DebouncedClickListener import im.vector.app.core.utils.DebouncedClickListener
import im.vector.app.core.utils.createUIHandler import im.vector.app.core.utils.createUIHandler
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.list.RoomCategoryItem_
import org.matrix.android.sdk.api.session.room.ResultBoundaries
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
@ -54,6 +56,26 @@ class AddRoomListController @Inject constructor(
var listener: Listener? = null var listener: Listener? = null
var ignoreRooms: List<String>? = null var ignoreRooms: List<String>? = null
var initialLoadOccurred = false
fun boundaryChange(boundary: ResultBoundaries) {
val boundaryHasLoadedSomething = boundary.frontLoaded || boundary.zeroItemLoaded
if (initialLoadOccurred != boundaryHasLoadedSomething) {
initialLoadOccurred = boundaryHasLoadedSomething
requestForcedModelBuild()
}
}
var sectionName: String? = null
set(value) {
if (value != field) {
field = value
requestForcedModelBuild()
}
}
var totalSize: Int = 0
var selectedItems: Map<String, Boolean> = emptyMap() var selectedItems: Map<String, Boolean> = emptyMap()
set(value) { set(value) {
field = value field = value
@ -62,13 +84,27 @@ class AddRoomListController @Inject constructor(
} }
override fun addModels(models: List<EpoxyModel<*>>) { override fun addModels(models: List<EpoxyModel<*>>) {
if (ignoreRooms == null) { val filteredModel = if (ignoreRooms == null) {
super.addModels(models) models
} else { } else {
super.addModels(
models.filter { models.filter {
it !is RoomSelectionItem || !ignoreRooms!!.contains(it.matrixItem.id) it !is RoomSelectionItem || !ignoreRooms!!.contains(it.matrixItem.id)
} }
}
val somethingToShow = filteredModel.isNotEmpty() || !initialLoadOccurred
if (somethingToShow || filteredModel.isNotEmpty()) {
add(
RoomCategoryItem_().apply {
id("header")
title(sectionName ?: "")
expanded(true)
}
)
}
super.addModels(filteredModel)
if (!initialLoadOccurred) {
add(
RoomSelectionPlaceHolderItem_().apply { id("loading") }
) )
} }
} }

View File

@ -16,6 +16,9 @@
package im.vector.app.features.spaces.manage package im.vector.app.features.spaces.manage
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
@ -23,23 +26,30 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyViewHolder
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.jakewharton.rxbinding3.appcompat.queryTextChanges import com.jakewharton.rxbinding3.appcompat.queryTextChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentSpaceAddRoomsBinding import im.vector.app.databinding.FragmentSpaceAddRoomsBinding
import im.vector.app.features.home.room.list.RoomCategoryItem_
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
class SpaceAddRoomFragment @Inject constructor( class SpaceAddRoomFragment @Inject constructor(
private val epoxyController: AddRoomListController, private val spaceEpoxyController: AddRoomListController,
private val roomEpoxyController: AddRoomListController,
private val viewModelFactory: SpaceAddRoomsViewModel.Factory private val viewModelFactory: SpaceAddRoomsViewModel.Factory
) : VectorBaseFragment<FragmentSpaceAddRoomsBinding>(), ) : VectorBaseFragment<FragmentSpaceAddRoomsBinding>(),
OnBackPressed, AddRoomListController.Listener, SpaceAddRoomsViewModel.Factory { OnBackPressed, AddRoomListController.Listener, SpaceAddRoomsViewModel.Factory {
@ -79,7 +89,8 @@ class SpaceAddRoomFragment @Inject constructor(
.disposeOnDestroyView() .disposeOnDestroyView()
viewModel.selectionListLiveData.observe(viewLifecycleOwner) { viewModel.selectionListLiveData.observe(viewLifecycleOwner) {
epoxyController.selectedItems = it spaceEpoxyController.selectedItems = it
roomEpoxyController.selectedItems = it
saveNeeded = it.values.any { it } saveNeeded = it.values.any { it }
invalidateOptionsMenu() invalidateOptionsMenu()
} }
@ -89,7 +100,8 @@ class SpaceAddRoomFragment @Inject constructor(
}.disposeOnDestroyView() }.disposeOnDestroyView()
viewModel.selectSubscribe(this, SpaceAddRoomsState::ignoreRooms) { viewModel.selectSubscribe(this, SpaceAddRoomsState::ignoreRooms) {
epoxyController.ignoreRooms = it spaceEpoxyController.ignoreRooms = it
roomEpoxyController.ignoreRooms = it
}.disposeOnDestroyView() }.disposeOnDestroyView()
viewModel.selectSubscribe(this, SpaceAddRoomsState::isSaving) { viewModel.selectSubscribe(this, SpaceAddRoomsState::isSaving) {
@ -142,16 +154,71 @@ class SpaceAddRoomFragment @Inject constructor(
override fun onDestroyView() { override fun onDestroyView() {
views.roomList.cleanup() views.roomList.cleanup()
epoxyController.listener = null spaceEpoxyController.listener = null
roomEpoxyController.listener = null
super.onDestroyView() super.onDestroyView()
} }
private fun setupRecyclerView() { private fun setupRecyclerView() {
views.roomList.configureWith(epoxyController, showDivider = true) val concatAdapter = ConcatAdapter()
epoxyController.listener = this spaceEpoxyController.sectionName = getString(R.string.spaces_header)
viewModel.updatableLivePageResult.livePagedList.observe(viewLifecycleOwner) { roomEpoxyController.sectionName = getString(R.string.rooms_header)
epoxyController.submitList(it) spaceEpoxyController.listener = this
roomEpoxyController.listener = this
viewModel.updatableLiveSpacePageResult.liveBoundaries.observe(viewLifecycleOwner) {
spaceEpoxyController.boundaryChange(it)
} }
viewModel.updatableLiveSpacePageResult.livePagedList.observe(viewLifecycleOwner) {
spaceEpoxyController.totalSize = it.size
spaceEpoxyController.submitList(it)
}
viewModel.updatableLivePageResult.liveBoundaries.observe(viewLifecycleOwner) {
roomEpoxyController.boundaryChange(it)
}
viewModel.updatableLivePageResult.livePagedList.observe(viewLifecycleOwner) {
roomEpoxyController.totalSize = it.size
roomEpoxyController.submitList(it)
}
views.roomList.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
views.roomList.addItemDecoration(
object : DividerItemDecoration(context, VERTICAL) {
val decorationDrawable = ContextCompat.getDrawable(requireContext(), R.drawable.divider_horizontal)
override fun getDrawable(): Drawable? {
return decorationDrawable
}
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val position = parent.getChildAdapterPosition(view)
val vh = parent.findViewHolderForAdapterPosition(position)
val nextIsSectionOrFinal = parent.findViewHolderForAdapterPosition(position + 1)?.let {
(it as? EpoxyViewHolder)?.model is RoomCategoryItem_
} ?: true
if (vh == null
|| (vh as? EpoxyViewHolder)?.model is RoomCategoryItem_
|| nextIsSectionOrFinal
) {
outRect.setEmpty()
} else {
super.getItemOffsets(outRect, view, parent, state)
}
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(c, parent, state)
}
}
)
views.roomList.setHasFixedSize(true)
concatAdapter.addAdapter(roomEpoxyController.adapter)
concatAdapter.addAdapter(spaceEpoxyController.adapter)
views.roomList.adapter = concatAdapter
} }
override fun onBackPressed(toolbarButton: Boolean): Boolean { override fun onBackPressed(toolbarButton: Boolean): Boolean {

View File

@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
class AddRoomError(val errorList: Map<String, Throwable>) : Throwable() { class AddRoomError(val errorList: Map<String, Throwable>) : Throwable() {
@ -53,11 +54,31 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
private val session: Session private val session: Session
) : VectorViewModel<SpaceAddRoomsState, SpaceAddRoomActions, SpaceAddRoomsViewEvents>(initialState) { ) : VectorViewModel<SpaceAddRoomsState, SpaceAddRoomActions, SpaceAddRoomsViewEvents>(initialState) {
val updatableLivePageResult: UpdatableLivePageResult by lazy { val updatableLiveSpacePageResult: UpdatableLivePageResult by lazy {
session.getFilteredPagedRoomSummariesLive( session.getFilteredPagedRoomSummariesLive(
roomSummaryQueryParams { roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN) this.memberships = listOf(Membership.JOIN)
this.excludeType = null this.excludeType = null
this.includeType = listOf(RoomType.SPACE)
this.activeSpaceId = ActiveSpaceFilter.ExcludeSpace(initialState.spaceId)
this.displayName = QueryStringValue.Contains(initialState.currentFilter, QueryStringValue.Case.INSENSITIVE)
},
pagedListConfig = PagedList.Config.Builder()
.setPageSize(10)
.setInitialLoadSizeHint(20)
.setEnablePlaceholders(true)
.setPrefetchDistance(10)
.build(),
sortOrder = RoomSortOrder.NAME
)
}
val updatableLivePageResult: UpdatableLivePageResult by lazy {
session.getFilteredPagedRoomSummariesLive(
roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN)
this.excludeType = listOf(RoomType.SPACE)
this.includeType = null
this.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS this.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
this.activeSpaceId = ActiveSpaceFilter.ExcludeSpace(initialState.spaceId) this.activeSpaceId = ActiveSpaceFilter.ExcludeSpace(initialState.spaceId)
this.displayName = QueryStringValue.Contains(initialState.currentFilter, QueryStringValue.Case.INSENSITIVE) this.displayName = QueryStringValue.Contains(initialState.currentFilter, QueryStringValue.Case.INSENSITIVE)
@ -116,6 +137,11 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE)
) )
} }
updatableLiveSpacePageResult.updateQuery {
it.copy(
displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE)
)
}
setState { setState {
copy( copy(
currentFilter = action.filter currentFilter = action.filter