Home room list: make some clean up

This commit is contained in:
ganfra 2022-09-26 19:36:50 +02:00
parent 2ea357ddc0
commit bf405394d8
5 changed files with 104 additions and 111 deletions

View file

@ -16,6 +16,7 @@
package im.vector.app.features.home.room.list.home package im.vector.app.features.home.room.list.home
import androidx.paging.PagedList
import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.paging.PagedListEpoxyController import com.airbnb.epoxy.paging.PagedListEpoxyController
import im.vector.app.core.platform.StateView import im.vector.app.core.platform.StateView
@ -47,7 +48,6 @@ class HomeFilteredRoomsController @Inject constructor(
var listener: RoomListListener? = null var listener: RoomListListener? = null
private var emptyStateData: StateView.State.Empty? = null private var emptyStateData: StateView.State.Empty? = null
private var currentState: StateView.State = StateView.State.Content
private val shouldUseSingleLine: Boolean private val shouldUseSingleLine: Boolean
@ -56,17 +56,22 @@ class HomeFilteredRoomsController @Inject constructor(
shouldUseSingleLine = fontScale.scale > FontScalePreferences.SCALE_LARGE shouldUseSingleLine = fontScale.scale > FontScalePreferences.SCALE_LARGE
} }
fun submitRoomsList(roomsList: PagedList<RoomSummary>) {
submitList(roomsList)
// If room is empty we may have a new EmptyState to display
if (roomsList.isEmpty()) {
requestForcedModelBuild()
}
}
override fun addModels(models: List<EpoxyModel<*>>) { override fun addModels(models: List<EpoxyModel<*>>) {
val emptyStateData = this.emptyStateData
if (models.isEmpty() && emptyStateData != null) { if (models.isEmpty() && emptyStateData != null) {
emptyStateData?.let { emptyState -> roomListEmptyItem {
roomListEmptyItem { id("state_item")
id("state_item") emptyData(emptyStateData)
emptyData(emptyState)
}
currentState = emptyState
} }
} else { } else {
currentState = StateView.State.Content
super.addModels(models) super.addModels(models)
} }
} }
@ -83,7 +88,14 @@ class HomeFilteredRoomsController @Inject constructor(
useSingleLineForLastEvent(host.shouldUseSingleLine) useSingleLineForLastEvent(host.shouldUseSingleLine)
} }
} else { } else {
roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), RoomListDisplayMode.ROOMS, listener, shouldUseSingleLine) roomSummaryItemFactory.create(
roomSummary = item,
roomChangeMembershipStates = roomChangeMembershipStates.orEmpty(),
selectedRoomIds = emptySet(),
displayMode = RoomListDisplayMode.ROOMS,
listener = listener,
singleLineLastEvent = shouldUseSingleLine
)
} }
} }
} }

View file

@ -26,7 +26,6 @@ import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
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.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R import im.vector.app.R
@ -137,6 +136,7 @@ class HomeRoomListFragment :
} }
private fun setupRecyclerView() { private fun setupRecyclerView() {
views.stateView.state = StateView.State.Content
val layoutManager = LinearLayoutManager(context) val layoutManager = LinearLayoutManager(context)
firstItemObserver = FirstItemUpdatedObserver(layoutManager) { firstItemObserver = FirstItemUpdatedObserver(layoutManager) {
layoutManager.scrollToPosition(0) layoutManager.scrollToPosition(0)
@ -151,17 +151,12 @@ class HomeRoomListFragment :
roomListViewModel.onEach(HomeRoomListViewState::headersData) { roomListViewModel.onEach(HomeRoomListViewState::headersData) {
headersController.submitData(it) headersController.submitData(it)
} }
roomListViewModel.roomsLivePagedList.observe(viewLifecycleOwner) { roomsList -> roomListViewModel.roomsLivePagedList.observe(viewLifecycleOwner) { roomsList ->
roomsController.submitList(roomsList) roomsController.submitRoomsList(roomsList)
if (roomsList.isEmpty()) { }
roomsController.requestForcedModelBuild() roomListViewModel.onEach(HomeRoomListViewState::emptyState) { emptyState ->
} roomsController.submitEmptyStateData(emptyState)
} }
roomListViewModel.emptyStateFlow.onEach { emptyStateOptional ->
roomsController.submitEmptyStateData(emptyStateOptional.getOrNull())
}.launchIn(lifecycleScope)
setUpAdapters() setUpAdapters()
@ -170,9 +165,7 @@ class HomeRoomListFragment :
concatAdapter.registerAdapterDataObserver(firstItemObserver) concatAdapter.registerAdapterDataObserver(firstItemObserver)
} }
override fun invalidate() = withState(roomListViewModel) { state -> override fun invalidate() = Unit
views.stateView.state = state.state
}
private fun setUpAdapters() { private fun setUpAdapters() {
val headersAdapter = headersController.also { controller -> val headersAdapter = headersController.also { controller ->

View file

@ -18,10 +18,9 @@ package im.vector.app.features.home.room.list.home
import android.widget.ImageView import android.widget.ImageView
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.paging.PagedList import androidx.paging.PagedList
import arrow.core.Option
import arrow.core.toOption import arrow.core.toOption
import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted import dagger.assisted.Assisted
@ -37,22 +36,16 @@ import im.vector.app.core.resources.DrawableProvider
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.displayname.getBestName import im.vector.app.features.displayname.getBestName
import im.vector.app.features.home.room.list.home.header.HomeRoomFilter import im.vector.app.features.home.room.list.home.header.HomeRoomFilter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter
@ -90,34 +83,26 @@ class HomeRoomListViewModel @AssistedInject constructor(
companion object : MavericksViewModelFactory<HomeRoomListViewModel, HomeRoomListViewState> by hiltMavericksViewModelFactory() companion object : MavericksViewModelFactory<HomeRoomListViewModel, HomeRoomListViewState> by hiltMavericksViewModelFactory()
private var roomsFlow: Flow<Option<RoomSummary>>? = null
private val pagedListConfig = PagedList.Config.Builder() private val pagedListConfig = PagedList.Config.Builder()
.setPageSize(10) .setPageSize(10)
.setInitialLoadSizeHint(20) .setInitialLoadSizeHint(20)
.setEnablePlaceholders(true) .setEnablePlaceholders(true)
.build() .build()
private val _roomsLivePagedList = MutableLiveData<PagedList<RoomSummary>>() private val _roomsLivePagedList = MediatorLiveData<PagedList<RoomSummary>>()
val roomsLivePagedList: LiveData<PagedList<RoomSummary>> = _roomsLivePagedList val roomsLivePagedList: LiveData<PagedList<RoomSummary>> = _roomsLivePagedList
private val internalPagedListObserver = Observer<PagedList<RoomSummary>> { private val internalPagedListObserver = Observer<PagedList<RoomSummary>> {
_roomsLivePagedList.postValue(it) _roomsLivePagedList.postValue(it)
} }
private var currentFilter: HomeRoomFilter = HomeRoomFilter.ALL
private val _emptyStateFlow = MutableSharedFlow<Optional<StateView.State.Empty>>(replay = 1)
val emptyStateFlow = _emptyStateFlow.asSharedFlow()
private var filteredPagedRoomSummariesLive: UpdatableLivePageResult? = null private var filteredPagedRoomSummariesLive: UpdatableLivePageResult? = null
private val switchDataSourceMutex = Mutex()
init { init {
observeOrderPreferences() observeOrderPreferences()
observeInvites() observeInvites()
observeRecents() observeRecents()
observeFilterTabs() observeFilterTabs()
observeRooms()
observeSpaceChanges() observeSpaceChanges()
} }
@ -129,14 +114,13 @@ class HomeRoomListViewModel @AssistedInject constructor(
} }
.onEach { selectedSpaceOption -> .onEach { selectedSpaceOption ->
val selectedSpace = selectedSpaceOption.orNull() val selectedSpace = selectedSpaceOption.orNull()
updateEmptyState()
filteredPagedRoomSummariesLive?.let { liveResults -> filteredPagedRoomSummariesLive?.let { liveResults ->
liveResults.queryParams = liveResults.queryParams.copy( liveResults.queryParams = liveResults.queryParams.copy(
spaceFilter = selectedSpace?.roomId.toActiveSpaceOrNoFilter() spaceFilter = selectedSpace?.roomId.toActiveSpaceOrNoFilter()
) )
emitEmptyState()
} }
} }
.also { roomsFlow = it }
.launchIn(viewModelScope) .launchIn(viewModelScope)
} }
@ -163,7 +147,7 @@ class HomeRoomListViewModel @AssistedInject constructor(
}) })
.map { Optional.from(it) } .map { Optional.from(it) }
} else { } else {
flow { emit(Optional.empty()) } flowOf(Optional.empty())
}.onEach { listOptional -> }.onEach { listOptional ->
setState { copy(headersData = headersData.copy(recents = listOptional.getOrNull())) } setState { copy(headersData = headersData.copy(recents = listOptional.getOrNull())) }
} }
@ -174,31 +158,30 @@ class HomeRoomListViewModel @AssistedInject constructor(
preferencesStore.areFiltersEnabledFlow preferencesStore.areFiltersEnabledFlow
.distinctUntilChanged() .distinctUntilChanged()
.flatMapLatest { areEnabled -> .flatMapLatest { areEnabled ->
if (areEnabled) { getFilterTabsFlow(areEnabled)
getFilterTabsFlow() }.onEach { filtersOptional ->
} else { val filters = filtersOptional.getOrNull()
flow { emit(Optional.empty()) } if (!isCurrentFilterStillValid(filters)) {
}.onEach { filtersOptional -> changeRoomFilter(HomeRoomFilter.ALL)
setState { }
validateCurrentFilter(filtersOptional.getOrNull()) setState {
copy( copy(
headersData = headersData.copy( headersData = headersData.copy(
filtersList = filtersOptional.getOrNull(), filtersList = filters,
currentFilter = currentFilter )
) )
)
}
} }
}.launchIn(viewModelScope) }.launchIn(viewModelScope)
} }
private fun validateCurrentFilter(filtersList: List<HomeRoomFilter>?) { private suspend fun isCurrentFilterStillValid(filtersList: List<HomeRoomFilter>?): Boolean {
if (filtersList?.contains(currentFilter) != true) { if (filtersList.isNullOrEmpty()) return false
handleChangeRoomFilter(HomeRoomFilter.ALL) val currentFilter = awaitState().headersData.currentFilter
} return filtersList.contains(currentFilter)
} }
private fun getFilterTabsFlow(): Flow<Optional<MutableList<HomeRoomFilter>>> { private fun getFilterTabsFlow(isEnabled: Boolean): Flow<Optional<MutableList<HomeRoomFilter>>> {
if (!isEnabled) return flowOf(Optional.empty())
val spaceFLow = spaceStateHandler.getSelectedSpaceFlow() val spaceFLow = spaceStateHandler.getSelectedSpaceFlow()
.distinctUntilChanged() .distinctUntilChanged()
.onStart { .onStart {
@ -252,44 +235,42 @@ class HomeRoomListViewModel @AssistedInject constructor(
} }
} }
private fun observeRooms() = viewModelScope.launch { private fun observeRooms(currentFilter: HomeRoomFilter, isAZOrdering: Boolean) {
switchDataSourceMutex.withLock { filteredPagedRoomSummariesLive?.livePagedList?.let { livePagedList ->
filteredPagedRoomSummariesLive?.livePagedList?.removeObserver(internalPagedListObserver) _roomsLivePagedList.removeSource(livePagedList)
val builder = RoomSummaryQueryParams.Builder().also {
it.memberships = listOf(Membership.JOIN)
it.spaceFilter = spaceStateHandler.getCurrentSpace()?.roomId.toActiveSpaceOrNoFilter()
}
val params = getFilteredQueryParams(currentFilter, builder.build())
val sortOrder = if (preferencesStore.isAZOrderingEnabledFlow.first()) {
RoomSortOrder.NAME
} else {
RoomSortOrder.ACTIVITY
}
val liveResults = session.roomService().getFilteredPagedRoomSummariesLive(
params,
pagedListConfig,
sortOrder
).also {
filteredPagedRoomSummariesLive = it
}
liveResults.livePagedList.observeForever(internalPagedListObserver)
} }
val builder = RoomSummaryQueryParams.Builder().also {
it.memberships = listOf(Membership.JOIN)
it.spaceFilter = spaceStateHandler.getCurrentSpace()?.roomId.toActiveSpaceOrNoFilter()
}
val params = getFilteredQueryParams(currentFilter, builder.build())
val sortOrder = if (isAZOrdering) {
RoomSortOrder.NAME
} else {
RoomSortOrder.ACTIVITY
}
val liveResults = session.roomService().getFilteredPagedRoomSummariesLive(
params,
pagedListConfig,
sortOrder
).also {
filteredPagedRoomSummariesLive = it
}
_roomsLivePagedList.addSource(liveResults.livePagedList, internalPagedListObserver)
} }
private fun observeOrderPreferences() { private fun observeOrderPreferences() {
preferencesStore.isAZOrderingEnabledFlow.onEach { preferencesStore.isAZOrderingEnabledFlow
observeRooms() .onEach { isAZOrdering ->
}.launchIn(viewModelScope) val currentFilter = awaitState().headersData.currentFilter
observeRooms(currentFilter, isAZOrdering)
}.launchIn(viewModelScope)
} }
private fun emitEmptyState() { private suspend fun updateEmptyState() {
viewModelScope.launch { val currentFilter = awaitState().headersData.currentFilter
val emptyState = getEmptyStateData(currentFilter, spaceStateHandler.getCurrentSpace()) val emptyState = getEmptyStateData(currentFilter, spaceStateHandler.getCurrentSpace())
_emptyStateFlow.emit(Optional.from(emptyState)) setState { copy(emptyState = emptyState) }
}
} }
private fun getFilteredQueryParams(filter: HomeRoomFilter, currentParams: RoomSummaryQueryParams): RoomSummaryQueryParams { private fun getFilteredQueryParams(filter: HomeRoomFilter, currentParams: RoomSummaryQueryParams): RoomSummaryQueryParams {
@ -358,21 +339,28 @@ class HomeRoomListViewModel @AssistedInject constructor(
} }
override fun onCleared() { override fun onCleared() {
filteredPagedRoomSummariesLive?.livePagedList?.let { livePagedList ->
_roomsLivePagedList.removeSource(livePagedList)
}
super.onCleared() super.onCleared()
filteredPagedRoomSummariesLive?.livePagedList?.removeObserver(internalPagedListObserver)
} }
private fun handleChangeRoomFilter(newFilter: HomeRoomFilter) { private fun handleChangeRoomFilter(newFilter: HomeRoomFilter) {
viewModelScope.launch {
changeRoomFilter(newFilter)
}
}
private suspend fun changeRoomFilter(newFilter: HomeRoomFilter) {
val currentFilter = awaitState().headersData.currentFilter
if (currentFilter == newFilter) { if (currentFilter == newFilter) {
return return
} }
currentFilter = newFilter setState { copy(headersData = headersData.copy(currentFilter = newFilter)) }
updateEmptyState()
filteredPagedRoomSummariesLive?.let { liveResults -> filteredPagedRoomSummariesLive?.let { liveResults ->
liveResults.queryParams = getFilteredQueryParams(currentFilter, liveResults.queryParams) liveResults.queryParams = getFilteredQueryParams(newFilter, liveResults.queryParams)
} }
setState { copy(headersData = headersData.copy(currentFilter = currentFilter)) }
emitEmptyState()
} }
fun isPublicRoom(roomId: String): Boolean { fun isPublicRoom(roomId: String): Boolean {
@ -393,9 +381,9 @@ class HomeRoomListViewModel @AssistedInject constructor(
} }
private fun handleChangeNotificationMode(action: HomeRoomListAction.ChangeRoomNotificationState) { private fun handleChangeNotificationMode(action: HomeRoomListAction.ChangeRoomNotificationState) {
val room = session.getRoom(action.roomId) viewModelScope.launch {
if (room != null) { val room = session.getRoom(action.roomId)
viewModelScope.launch { if (room != null) {
try { try {
room.roomPushRuleService().setRoomNotificationState(action.notificationState) room.roomPushRuleService().setRoomNotificationState(action.notificationState)
} catch (failure: Throwable) { } catch (failure: Throwable) {
@ -406,8 +394,8 @@ class HomeRoomListViewModel @AssistedInject constructor(
} }
private fun handleToggleTag(action: HomeRoomListAction.ToggleTag) { private fun handleToggleTag(action: HomeRoomListAction.ToggleTag) {
session.getRoom(action.roomId)?.let { room -> viewModelScope.launch {
viewModelScope.launch(Dispatchers.IO) { session.getRoom(action.roomId)?.let { room ->
try { try {
if (room.roomSummary()?.hasTag(action.tag) == false) { if (room.roomSummary()?.hasTag(action.tag) == false) {
// Favorite and low priority tags are exclusive, so maybe delete the other tag first // Favorite and low priority tags are exclusive, so maybe delete the other tag first
@ -430,11 +418,11 @@ class HomeRoomListViewModel @AssistedInject constructor(
} }
private fun handleDeleteLocalRooms() = withState { private fun handleDeleteLocalRooms() = withState {
val localRoomIds = session.roomService()
.getRoomSummaries(roomSummaryQueryParams { roomId = QueryStringValue.Contains(RoomLocalEcho.PREFIX) })
.map { it.roomId }
viewModelScope.launch { viewModelScope.launch {
val localRoomIds = session.roomService()
.getRoomSummaries(roomSummaryQueryParams { roomId = QueryStringValue.Contains(RoomLocalEcho.PREFIX) })
.map { it.roomId }
localRoomIds.forEach { localRoomIds.forEach {
session.roomService().deleteLocalRoom(it) session.roomService().deleteLocalRoom(it)
} }

View file

@ -21,6 +21,6 @@ import im.vector.app.core.platform.StateView
import im.vector.app.features.home.room.list.home.header.RoomsHeadersData import im.vector.app.features.home.room.list.home.header.RoomsHeadersData
data class HomeRoomListViewState( data class HomeRoomListViewState(
val state: StateView.State = StateView.State.Content, val emptyState: StateView.State.Empty? = null,
val headersData: RoomsHeadersData = RoomsHeadersData(), val headersData: RoomsHeadersData = RoomsHeadersData(),
) : MavericksState ) : MavericksState

View file

@ -21,6 +21,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
data class RoomsHeadersData( data class RoomsHeadersData(
val invitesCount: Int = 0, val invitesCount: Int = 0,
val filtersList: List<HomeRoomFilter>? = null, val filtersList: List<HomeRoomFilter>? = null,
val currentFilter: HomeRoomFilter? = null, val currentFilter: HomeRoomFilter = HomeRoomFilter.ALL,
val recents: List<RoomSummary>? = null val recents: List<RoomSummary>? = null
) )