Fix suggested rooms

This commit is contained in:
Valere 2021-04-12 09:31:35 +02:00
parent 0d3c2b4bef
commit b635663ae3
8 changed files with 231 additions and 67 deletions

View File

@ -28,6 +28,7 @@ import androidx.core.view.isVisible
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 androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyController
import com.airbnb.epoxy.OnModelBuildFinishedListener import com.airbnb.epoxy.OnModelBuildFinishedListener
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
@ -92,11 +93,11 @@ class RoomListFragment @Inject constructor(
data class SectionAdapterInfo( data class SectionAdapterInfo(
var section: SectionKey, var section: SectionKey,
val headerHeaderAdapter: SectionHeaderAdapter, val headerHeaderAdapter: SectionHeaderAdapter,
val contentAdapter: RoomSummaryPagedController val contentAdapter: EpoxyController
) )
private val adapterInfosList = mutableListOf<SectionAdapterInfo>() private val adapterInfosList = mutableListOf<SectionAdapterInfo>()
private var concatAdapter : ConcatAdapter? = null private var concatAdapter: ConcatAdapter? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -125,7 +126,7 @@ class RoomListFragment @Inject constructor(
// it's for invites local echo // it's for invites local echo
adapterInfosList.filter { it.section.notifyOfLocalEcho } adapterInfosList.filter { it.section.notifyOfLocalEcho }
.onEach { .onEach {
it.contentAdapter.roomChangeMembershipStates = ms (it.contentAdapter as? RoomSummaryPagedController)?.roomChangeMembershipStates = ms
} }
} }
} }
@ -180,8 +181,8 @@ class RoomListFragment @Inject constructor(
private fun setupCreateRoomButton() { private fun setupCreateRoomButton() {
when (roomListParams.displayMode) { when (roomListParams.displayMode) {
RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.isVisible = true RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.isVisible = true
RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.isVisible = true RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.isVisible = true
RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.isVisible = true RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.isVisible = true
else -> Unit // No button in this mode else -> Unit // No button in this mode
} }
@ -249,23 +250,62 @@ class RoomListFragment @Inject constructor(
it.updateSection(SectionHeaderAdapter.RoomsSectionData(section.sectionName)) it.updateSection(SectionHeaderAdapter.RoomsSectionData(section.sectionName))
} }
val contentAdapter = pagedControllerFactory.createRoomSummaryPagedController() val contentAdapter =
.also { controller -> when {
section.livePages.observe(viewLifecycleOwner) { pl -> section.livePages != null -> {
controller.submitList(pl) pagedControllerFactory.createRoomSummaryPagedController()
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(isHidden = pl.isEmpty())) .also { controller ->
checkEmptyState() section.livePages.observe(viewLifecycleOwner) { pl ->
controller.submitList(pl)
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(isHidden = pl.isEmpty()))
checkEmptyState()
}
section.notificationCount.observe(viewLifecycleOwner) { counts ->
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
notificationCount = counts.totalCount,
isHighlighted = counts.isHighlight
))
}
section.isExpanded.observe(viewLifecycleOwner) { _ ->
refreshCollapseStates()
}
controller.listener = this
}
} }
section.notificationCount.observe(viewLifecycleOwner) { counts -> section.liveSuggested != null -> {
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy( pagedControllerFactory.createSuggestedRoomListController()
notificationCount = counts.totalCount, .also { controller ->
isHighlighted = counts.isHighlight section.liveSuggested.observe(viewLifecycleOwner) { info ->
)) controller.setData(info)
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(isHidden = info.rooms.isEmpty()))
checkEmptyState()
}
section.isExpanded.observe(viewLifecycleOwner) { _ ->
refreshCollapseStates()
}
controller.listener = this
}
} }
section.isExpanded.observe(viewLifecycleOwner) { _ -> else -> {
refreshCollapseStates() pagedControllerFactory.createRoomSummaryListController()
.also { controller ->
section.liveList?.observe(viewLifecycleOwner) { list ->
controller.setData(list)
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(isHidden = list.isEmpty()))
checkEmptyState()
}
section.notificationCount.observe(viewLifecycleOwner) { counts ->
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
notificationCount = counts.totalCount,
isHighlighted = counts.isHighlight
))
}
section.isExpanded.observe(viewLifecycleOwner) { _ ->
refreshCollapseStates()
}
controller.listener = this
}
} }
controller.listener = this
} }
adapterInfosList.add( adapterInfosList.add(
SectionAdapterInfo( SectionAdapterInfo(
@ -294,8 +334,8 @@ class RoomListFragment @Inject constructor(
if (isAdded) { if (isAdded) {
when (roomListParams.displayMode) { when (roomListParams.displayMode) {
RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.show() RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.show()
RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.show() RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.show()
RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.show() RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.show()
else -> Unit else -> Unit
} }
} }
@ -368,14 +408,14 @@ class RoomListFragment @Inject constructor(
image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_noun_party_popper), image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_noun_party_popper),
message = getString(R.string.room_list_catchup_empty_body)) message = getString(R.string.room_list_catchup_empty_body))
} }
RoomListDisplayMode.PEOPLE -> RoomListDisplayMode.PEOPLE ->
StateView.State.Empty( StateView.State.Empty(
title = getString(R.string.room_list_people_empty_title), title = getString(R.string.room_list_people_empty_title),
image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_dm), image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_dm),
isBigImage = true, isBigImage = true,
message = getString(R.string.room_list_people_empty_body) message = getString(R.string.room_list_people_empty_body)
) )
RoomListDisplayMode.ROOMS -> RoomListDisplayMode.ROOMS ->
StateView.State.Empty( StateView.State.Empty(
title = getString(R.string.room_list_rooms_empty_title), title = getString(R.string.room_list_rooms_empty_title),
image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_room), image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_room),

View File

@ -17,8 +17,10 @@
package im.vector.app.features.home.room.list package im.vector.app.features.home.room.list
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.liveData import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
@ -32,6 +34,7 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.home.RoomListDisplayMode
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.rxkotlin.Observables
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -70,6 +73,8 @@ class RoomListViewModel @Inject constructor(
private var activeSpaceAwareQueries: List<ActiveSpaceQueryUpdater>? = null private var activeSpaceAwareQueries: List<ActiveSpaceQueryUpdater>? = null
val suggestedRoomJoiningState: MutableLiveData<Map<String, Async<Unit>>> = MutableLiveData(emptyMap())
interface ActiveSpaceQueryUpdater { interface ActiveSpaceQueryUpdater {
fun updateForSpaceId(roomId: String?) fun updateForSpaceId(roomId: String?)
} }
@ -86,31 +91,12 @@ class RoomListViewModel @Inject constructor(
appStateHandler.selectedSpaceDataSource.observe() appStateHandler.selectedSpaceDataSource.observe()
// .observeOn(Schedulers.computation()) // .observeOn(Schedulers.computation())
.distinctUntilChanged() .distinctUntilChanged()
.switchMap { activeSpaceOption -> .subscribe { activeSpaceOption ->
val selectedSpace = activeSpaceOption.orNull() val selectedSpace = activeSpaceOption.orNull()
activeSpaceAwareQueries?.onEach { updater -> activeSpaceAwareQueries?.onEach { updater ->
updater.updateForSpaceId(selectedSpace?.roomId?.takeIf { MatrixPatterns.isRoomId(it) }) updater.updateForSpaceId(selectedSpace?.roomId?.takeIf { MatrixPatterns.isRoomId(it) })
} }
// activeSpaceAwareQueries?.forEach { }.disposeOnClear()
// it.updateQuery {
// it.copy(
// activeSpaceId = ActiveSpaceFilter.ActiveSpace(selectedSpace?.roomId?.takeIf { MatrixPatterns.isRoomId(it) })
// )
// }
// }
if (selectedSpace == null) {
Observable.just(emptyList())
} else {
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
val spaceSum = tryOrNull { session.spaceService().querySpaceChildren(selectedSpace.roomId, suggestedOnly = true) }
val value = spaceSum?.second ?: emptyList()
emit(value)
}.asObservable()
}
}
.execute { info ->
copy(asyncSuggestedRooms = info)
}
appStateHandler.selectedSpaceDataSource.observe() appStateHandler.selectedSpaceDataSource.observe()
// .observeOn(Schedulers.computation()) // .observeOn(Schedulers.computation())
@ -244,6 +230,51 @@ class RoomListViewModel @Inject constructor(
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
it.roomTagQueryFilter = RoomTagQueryFilter(null, null, true) it.roomTagQueryFilter = RoomTagQueryFilter(null, null, true)
} }
// add suggested rooms
val suggestedRoomsObservable = // MutableLiveData<List<SpaceChildInfo>>()
appStateHandler.selectedSpaceDataSource.observe()
.distinctUntilChanged()
.switchMap { activeSpaceOption ->
val selectedSpace = activeSpaceOption.orNull()
if (selectedSpace == null) {
Observable.just(emptyList())
} else {
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
val spaceSum = tryOrNull { session.spaceService().querySpaceChildren(selectedSpace.roomId, suggestedOnly = true) }
val value = spaceSum?.second ?: emptyList()
// i need to check if it's already joined.
val filtered = value.filter {
session.getRoomSummary(it.childRoomId)?.membership?.isActive() != true
}
emit(filtered)
}.asObservable()
}
}
// .subscribe {
// Timber.w("VAL: Suggested rooms is ${it}")
// liveSuggestedRooms.postValue(it)
// }.disposeOnClear()
val liveSuggestedRooms = MutableLiveData<SuggestedRoomInfo>()
Observables.combineLatest(
suggestedRoomsObservable,
suggestedRoomJoiningState.asObservable()
) { rooms, joinStates ->
SuggestedRoomInfo(
rooms,
joinStates
)
}.subscribe {
liveSuggestedRooms.postValue(it)
}.disposeOnClear()
sections.add(
RoomsSection(
sectionName = stringProvider.getString(R.string.suggested_header),
liveSuggested = liveSuggestedRooms,
notifyOfLocalEcho = false
)
)
} else if (initialState.displayMode == RoomListDisplayMode.FILTERED) { } else if (initialState.displayMode == RoomListDisplayMode.FILTERED) {
withQueryParams( withQueryParams(
{ {
@ -499,33 +530,22 @@ class RoomListViewModel @Inject constructor(
} }
private fun handleJoinSuggestedRoom(action: RoomListAction.JoinSuggestedRoom) { private fun handleJoinSuggestedRoom(action: RoomListAction.JoinSuggestedRoom) {
setState { suggestedRoomJoiningState.postValue(suggestedRoomJoiningState.value.orEmpty().toMutableMap().apply {
copy( this[action.roomId] = Loading()
suggestedRoomJoiningState = this.suggestedRoomJoiningState.toMutableMap().apply { }.toMap())
this[action.roomId] = Loading()
}.toMap()
)
}
viewModelScope.launch { viewModelScope.launch {
try { try {
awaitCallback<Unit> { awaitCallback<Unit> {
session.joinRoom(action.roomId, null, action.viaServers ?: emptyList(), it) session.joinRoom(action.roomId, null, action.viaServers ?: emptyList(), it)
} }
setState { suggestedRoomJoiningState.postValue(suggestedRoomJoiningState.value.orEmpty().toMutableMap().apply {
copy( this[action.roomId] = Success(Unit)
suggestedRoomJoiningState = this.suggestedRoomJoiningState.toMutableMap().apply { }.toMap())
this[action.roomId] = Success(Unit)
}.toMap()
)
}
} catch (failure: Throwable) { } catch (failure: Throwable) {
setState { suggestedRoomJoiningState.postValue(suggestedRoomJoiningState.value.orEmpty().toMutableMap().apply {
copy( this[action.roomId] = Fail(failure)
suggestedRoomJoiningState = this.suggestedRoomJoiningState.toMutableMap().apply { }.toMap())
this[action.roomId] = Fail(failure)
}.toMap()
)
}
} }
} }
} }

View File

@ -29,7 +29,6 @@ data class RoomListViewState(
val roomFilter: String = "", val roomFilter: String = "",
val roomMembershipChanges: Map<String, ChangeMembershipState> = emptyMap(), val roomMembershipChanges: Map<String, ChangeMembershipState> = emptyMap(),
val asyncSuggestedRooms: Async<List<SpaceChildInfo>> = Uninitialized, val asyncSuggestedRooms: Async<List<SpaceChildInfo>> = Uninitialized,
val suggestedRoomJoiningState: Map<String, Async<Unit>> = emptyMap(),
val currentUserName: String? = null, val currentUserName: String? = null,
val currentSpace: Async<RoomSummary?> = Uninitialized val currentSpace: Async<RoomSummary?> = Uninitialized
) : MvRxState { ) : MvRxState {

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2021 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.list
import com.airbnb.epoxy.TypedEpoxyController
import org.matrix.android.sdk.api.session.room.model.RoomSummary
class RoomSummaryListController(
private val roomSummaryItemFactory: RoomSummaryItemFactory
) : TypedEpoxyController<List<RoomSummary>>() {
var listener: RoomListListener? = null
override fun buildModels(data: List<RoomSummary>?) {
data?.forEach {
add(roomSummaryItemFactory.create(it, emptyMap(), emptySet(), listener))
}
}
}

View File

@ -30,6 +30,14 @@ class RoomSummaryPagedControllerFactory @Inject constructor(
fun createRoomSummaryPagedController(): RoomSummaryPagedController { fun createRoomSummaryPagedController(): RoomSummaryPagedController {
return RoomSummaryPagedController(roomSummaryItemFactory) return RoomSummaryPagedController(roomSummaryItemFactory)
} }
fun createRoomSummaryListController(): RoomSummaryListController {
return RoomSummaryListController(roomSummaryItemFactory)
}
fun createSuggestedRoomListController(): SuggestedRoomListController {
return SuggestedRoomListController(roomSummaryItemFactory)
}
} }
class RoomSummaryPagedController( class RoomSummaryPagedController(

View File

@ -24,7 +24,10 @@ import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotification
data class RoomsSection( data class RoomsSection(
val sectionName: String, val sectionName: String,
val livePages: LiveData<PagedList<RoomSummary>>, // can be a paged list or a regular list
val livePages: LiveData<PagedList<RoomSummary>>? = null,
val liveList: LiveData<List<RoomSummary>>? = null,
val liveSuggested: LiveData<SuggestedRoomInfo>? = null,
val isExpanded: MutableLiveData<Boolean> = MutableLiveData(true), val isExpanded: MutableLiveData<Boolean> = MutableLiveData(true),
val notificationCount: MutableLiveData<RoomAggregateNotificationCount> = MutableLiveData(RoomAggregateNotificationCount(0, 0)), val notificationCount: MutableLiveData<RoomAggregateNotificationCount> = MutableLiveData(RoomAggregateNotificationCount(0, 0)),
val notifyOfLocalEcho: Boolean = false val notifyOfLocalEcho: Boolean = false

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2021 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.list
import com.airbnb.mvrx.Async
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
class SuggestedRoomInfo(
val rooms: List<SpaceChildInfo>,
val joinEcho: Map<String, Async<Unit>>
)

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) 2021 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.list
import com.airbnb.epoxy.TypedEpoxyController
class SuggestedRoomListController(
private val roomSummaryItemFactory: RoomSummaryItemFactory
) : TypedEpoxyController<SuggestedRoomInfo>() {
var listener: RoomListListener? = null
override fun buildModels(data: SuggestedRoomInfo?) {
data?.rooms?.forEach { info ->
roomSummaryItemFactory.createSuggestion(info, data.joinEcho) {
listener?.onJoinSuggestedRoom(info)
}.let {
add(it)
}
}
}
}