diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableLivePageResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableLivePageResult.kt index 3bcca0c241..b83f57f5ef 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableLivePageResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableLivePageResult.kt @@ -24,4 +24,12 @@ interface UpdatableLivePageResult { val livePagedList: LiveData> fun updateQuery(builder: (RoomSummaryQueryParams) -> RoomSummaryQueryParams) + + val liveBoundaries: LiveData } + +data class ResultBoundaries( + val frontLoaded: Boolean = false, + val endLoaded: Boolean = false, + val zeroItemLoaded: Boolean = false +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index d2bb51cbef..887219fdd8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.summary import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Transformations import androidx.paging.LivePagedListBuilder import androidx.paging.PagedList @@ -28,6 +29,7 @@ import io.realm.Sort import io.realm.kotlin.where import org.matrix.android.sdk.api.query.ActiveSpaceFilter 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.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult @@ -187,9 +189,25 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat roomSummaryMapper.map(it) } + val boundaries = MutableLiveData(ResultBoundaries()) + val mapped = monarchy.findAllPagedWithChanges( realmDataSourceFactory, - LivePagedListBuilder(dataSourceFactory, pagedListConfig) + LivePagedListBuilder(dataSourceFactory, pagedListConfig).also { + it.setBoundaryCallback(object : PagedList.BoundaryCallback() { + 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 { @@ -200,6 +218,9 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat roomSummariesQuery(it, builder.invoke(queryParams)).process(sortOrder) } } + + override val liveBoundaries: LiveData + get() = boundaries } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt index cc6ebe0f60..46ca2a6606 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt @@ -22,6 +22,8 @@ import com.airbnb.epoxy.paging.PagedListEpoxyController import im.vector.app.core.utils.DebouncedClickListener import im.vector.app.core.utils.createUIHandler 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.RoomType import org.matrix.android.sdk.api.util.toMatrixItem @@ -54,6 +56,26 @@ class AddRoomListController @Inject constructor( var listener: Listener? = null var ignoreRooms: List? = 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 = emptyMap() set(value) { field = value @@ -62,15 +84,29 @@ class AddRoomListController @Inject constructor( } override fun addModels(models: List>) { - if (ignoreRooms == null) { - super.addModels(models) + val filteredModel = if (ignoreRooms == null) { + models } else { - super.addModels( - models.filter { - it !is RoomSelectionItem || !ignoreRooms!!.contains(it.matrixItem.id) + models.filter { + 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") } + ) + } } override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> { diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt index dc55373b36..2143b31be7 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt @@ -16,6 +16,9 @@ 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.view.LayoutInflater import android.view.Menu @@ -23,23 +26,30 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup 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.activityViewModel import com.airbnb.mvrx.fragmentViewModel import com.jakewharton.rxbinding3.appcompat.queryTextChanges import im.vector.app.R 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.VectorBaseFragment import im.vector.app.databinding.FragmentSpaceAddRoomsBinding +import im.vector.app.features.home.room.list.RoomCategoryItem_ import io.reactivex.rxkotlin.subscribeBy import org.matrix.android.sdk.api.session.room.model.RoomSummary import java.util.concurrent.TimeUnit import javax.inject.Inject class SpaceAddRoomFragment @Inject constructor( - private val epoxyController: AddRoomListController, + private val spaceEpoxyController: AddRoomListController, + private val roomEpoxyController: AddRoomListController, private val viewModelFactory: SpaceAddRoomsViewModel.Factory ) : VectorBaseFragment(), OnBackPressed, AddRoomListController.Listener, SpaceAddRoomsViewModel.Factory { @@ -79,7 +89,8 @@ class SpaceAddRoomFragment @Inject constructor( .disposeOnDestroyView() viewModel.selectionListLiveData.observe(viewLifecycleOwner) { - epoxyController.selectedItems = it + spaceEpoxyController.selectedItems = it + roomEpoxyController.selectedItems = it saveNeeded = it.values.any { it } invalidateOptionsMenu() } @@ -89,7 +100,8 @@ class SpaceAddRoomFragment @Inject constructor( }.disposeOnDestroyView() viewModel.selectSubscribe(this, SpaceAddRoomsState::ignoreRooms) { - epoxyController.ignoreRooms = it + spaceEpoxyController.ignoreRooms = it + roomEpoxyController.ignoreRooms = it }.disposeOnDestroyView() viewModel.selectSubscribe(this, SpaceAddRoomsState::isSaving) { @@ -142,16 +154,71 @@ class SpaceAddRoomFragment @Inject constructor( override fun onDestroyView() { views.roomList.cleanup() - epoxyController.listener = null + spaceEpoxyController.listener = null + roomEpoxyController.listener = null super.onDestroyView() } private fun setupRecyclerView() { - views.roomList.configureWith(epoxyController, showDivider = true) - epoxyController.listener = this - viewModel.updatableLivePageResult.livePagedList.observe(viewLifecycleOwner) { - epoxyController.submitList(it) + val concatAdapter = ConcatAdapter() + spaceEpoxyController.sectionName = getString(R.string.spaces_header) + roomEpoxyController.sectionName = getString(R.string.rooms_header) + 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 { diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt index e12aa14e83..5b1674d44a 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt @@ -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.UpdatableLivePageResult 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 class AddRoomError(val errorList: Map) : Throwable() { @@ -53,11 +54,31 @@ class SpaceAddRoomsViewModel @AssistedInject constructor( private val session: Session ) : VectorViewModel(initialState) { - val updatableLivePageResult: UpdatableLivePageResult by lazy { + val updatableLiveSpacePageResult: UpdatableLivePageResult by lazy { session.getFilteredPagedRoomSummariesLive( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) 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.activeSpaceId = ActiveSpaceFilter.ExcludeSpace(initialState.spaceId) 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) ) } + updatableLiveSpacePageResult.updateQuery { + it.copy( + displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) + ) + } setState { copy( currentFilter = action.filter