mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 02:15:35 +03:00
new app layout's room list overhaul (#7101)
This commit is contained in:
parent
e37344a059
commit
da83a85f74
14 changed files with 371 additions and 409 deletions
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.list.home.filter
|
||||
package im.vector.app.features.home.room.list.home
|
||||
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
||||
|
@ -24,11 +24,11 @@ import im.vector.app.features.home.RoomListDisplayMode
|
|||
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.RoomSummaryItemPlaceHolder_
|
||||
import im.vector.app.features.home.room.list.home.roomListEmptyItem
|
||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import javax.inject.Inject
|
||||
|
||||
class HomeFilteredRoomsController(
|
||||
class HomeFilteredRoomsController @Inject constructor(
|
||||
private val roomSummaryItemFactory: RoomSummaryItemFactory,
|
||||
) : PagedListEpoxyController<RoomSummary>(
|
||||
// Important it must match the PageList builder notify Looper
|
||||
|
@ -43,22 +43,11 @@ class HomeFilteredRoomsController(
|
|||
}
|
||||
|
||||
var listener: RoomListListener? = null
|
||||
var onFilterChanged: ((HomeRoomFilter) -> Unit)? = null
|
||||
|
||||
private var filtersData: List<HomeRoomFilter>? = null
|
||||
private var emptyStateData: StateView.State.Empty? = null
|
||||
private var currentState: StateView.State = StateView.State.Content
|
||||
|
||||
override fun addModels(models: List<EpoxyModel<*>>) {
|
||||
val host = this
|
||||
if (host.filtersData != null) {
|
||||
roomFilterHeaderItem {
|
||||
id("filter_header")
|
||||
filtersData(host.filtersData)
|
||||
onFilterChangedListener(host.onFilterChanged)
|
||||
}
|
||||
}
|
||||
|
||||
if (models.isEmpty() && emptyStateData != null) {
|
||||
emptyStateData?.let { emptyState ->
|
||||
roomListEmptyItem {
|
||||
|
@ -77,10 +66,6 @@ class HomeFilteredRoomsController(
|
|||
this.emptyStateData = state
|
||||
}
|
||||
|
||||
fun submitFiltersData(data: List<HomeRoomFilter>?) {
|
||||
this.filtersData = data
|
||||
requestForcedModelBuild()
|
||||
}
|
||||
override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
|
||||
item ?: return RoomSummaryItemPlaceHolder_().apply { id(currentPosition) }
|
||||
return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), RoomListDisplayMode.ROOMS, listener)
|
|
@ -17,7 +17,7 @@
|
|||
package im.vector.app.features.home.room.list.home
|
||||
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter
|
||||
import im.vector.app.features.home.room.list.home.header.HomeRoomFilter
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ import androidx.lifecycle.lifecycleScope
|
|||
import androidx.recyclerview.widget.ConcatAdapter
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.airbnb.epoxy.EpoxyControllerAdapter
|
||||
import com.airbnb.epoxy.OnModelBuildFinishedListener
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
|
@ -41,15 +40,12 @@ import im.vector.app.databinding.FragmentRoomListBinding
|
|||
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.RoomListListener
|
||||
import im.vector.app.features.home.room.list.RoomSummaryItemFactory
|
||||
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
|
||||
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
|
||||
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
||||
import im.vector.app.features.home.room.list.home.filter.HomeFilteredRoomsController
|
||||
import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter
|
||||
import im.vector.app.features.home.room.list.home.header.HomeRoomFilter
|
||||
import im.vector.app.features.home.room.list.home.header.HomeRoomsHeadersController
|
||||
import im.vector.app.features.home.room.list.home.invites.InvitesActivity
|
||||
import im.vector.app.features.home.room.list.home.invites.InvitesCounterController
|
||||
import im.vector.app.features.home.room.list.home.recent.RecentRoomCarouselController
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
|
@ -63,10 +59,9 @@ class HomeRoomListFragment :
|
|||
VectorBaseFragment<FragmentRoomListBinding>(),
|
||||
RoomListListener {
|
||||
|
||||
@Inject lateinit var roomSummaryItemFactory: RoomSummaryItemFactory
|
||||
@Inject lateinit var userPreferencesProvider: UserPreferencesProvider
|
||||
@Inject lateinit var recentRoomCarouselController: RecentRoomCarouselController
|
||||
@Inject lateinit var invitesCounterController: InvitesCounterController
|
||||
@Inject lateinit var headersController: HomeRoomsHeadersController
|
||||
@Inject lateinit var roomsController: HomeFilteredRoomsController
|
||||
|
||||
private val roomListViewModel: HomeRoomListViewModel by fragmentViewModel()
|
||||
private lateinit var sharedQuickActionsViewModel: RoomListQuickActionsSharedActionViewModel
|
||||
|
@ -143,10 +138,25 @@ class HomeRoomListFragment :
|
|||
|
||||
modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) }
|
||||
|
||||
roomListViewModel.sections.onEach { sections ->
|
||||
setUpAdapters(sections)
|
||||
roomListViewModel.onEach(HomeRoomListViewState::headersData) {
|
||||
headersController.submitData(it)
|
||||
}
|
||||
|
||||
roomListViewModel.onEach(HomeRoomListViewState::roomsLivePagedList) { roomsListLive ->
|
||||
roomsListLive?.observe(viewLifecycleOwner) { roomsList ->
|
||||
roomsController.submitList(roomsList)
|
||||
if (roomsList.isEmpty()) {
|
||||
roomsController.requestForcedModelBuild()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
roomListViewModel.emptyStateFlow.onEach { emptyStateOptional ->
|
||||
roomsController.submitEmptyStateData(emptyStateOptional.getOrNull())
|
||||
}.launchIn(lifecycleScope)
|
||||
|
||||
setUpAdapters()
|
||||
|
||||
views.roomListView.adapter = concatAdapter
|
||||
|
||||
// we need to force scroll when recents/filter tabs are added to make them visible
|
||||
|
@ -163,13 +173,20 @@ class HomeRoomListFragment :
|
|||
views.stateView.state = state.state
|
||||
}
|
||||
|
||||
private fun setUpAdapters(sections: Set<HomeRoomSection>) {
|
||||
concatAdapter.adapters.forEach {
|
||||
concatAdapter.removeAdapter(it)
|
||||
}
|
||||
sections.forEach {
|
||||
concatAdapter.addAdapter(getAdapterForData(it))
|
||||
}
|
||||
private fun setUpAdapters() {
|
||||
val headersAdapter = headersController.also { controller ->
|
||||
controller.invitesClickListener = ::onInvitesCounterClicked
|
||||
controller.onFilterChangedListener = ::onRoomFilterChanged
|
||||
controller.recentsRoomListener = this
|
||||
}.adapter
|
||||
|
||||
val roomsAdapter = roomsController
|
||||
.also { controller ->
|
||||
controller.listener = this
|
||||
}.adapter
|
||||
|
||||
concatAdapter.addAdapter(headersAdapter)
|
||||
concatAdapter.addAdapter(roomsAdapter)
|
||||
}
|
||||
|
||||
private fun promptLeaveRoom(roomId: String) {
|
||||
|
@ -191,43 +208,6 @@ class HomeRoomListFragment :
|
|||
.show()
|
||||
}
|
||||
|
||||
private fun getAdapterForData(section: HomeRoomSection): EpoxyControllerAdapter {
|
||||
return when (section) {
|
||||
is HomeRoomSection.RoomSummaryData -> {
|
||||
HomeFilteredRoomsController(
|
||||
roomSummaryItemFactory,
|
||||
).also { controller ->
|
||||
controller.listener = this
|
||||
controller.onFilterChanged = ::onRoomFilterChanged
|
||||
roomListViewModel.emptyStateFlow.onEach { emptyStateOptional ->
|
||||
controller.submitEmptyStateData(emptyStateOptional.getOrNull())
|
||||
}.launchIn(lifecycleScope)
|
||||
section.filtersData.onEach {
|
||||
controller.submitFiltersData(it.getOrNull())
|
||||
}.launchIn(lifecycleScope)
|
||||
section.list.observe(viewLifecycleOwner) { list ->
|
||||
controller.submitList(list)
|
||||
if (list.isEmpty()) {
|
||||
controller.requestForcedModelBuild()
|
||||
}
|
||||
}
|
||||
}.adapter
|
||||
}
|
||||
is HomeRoomSection.RecentRoomsData -> recentRoomCarouselController.also { controller ->
|
||||
controller.listener = this
|
||||
section.list.observe(viewLifecycleOwner) { list ->
|
||||
controller.submitList(list)
|
||||
}
|
||||
}.adapter
|
||||
is HomeRoomSection.InvitesCountData -> invitesCounterController.also { controller ->
|
||||
controller.clickListener = ::onInvitesCounterClicked
|
||||
section.count.observe(viewLifecycleOwner) { count ->
|
||||
controller.submitData(count)
|
||||
}
|
||||
}.adapter
|
||||
}
|
||||
}
|
||||
|
||||
private fun onInvitesCounterClicked() {
|
||||
startActivity(Intent(activity, InvitesActivity::class.java))
|
||||
}
|
||||
|
@ -247,8 +227,13 @@ class HomeRoomListFragment :
|
|||
|
||||
override fun onDestroyView() {
|
||||
views.roomListView.cleanup()
|
||||
recentRoomCarouselController.listener = null
|
||||
invitesCounterController.clickListener = null
|
||||
|
||||
headersController.recentsRoomListener = null
|
||||
headersController.invitesClickListener = null
|
||||
headersController.onFilterChangedListener = null
|
||||
|
||||
roomsController.listener = null
|
||||
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
|
@ -266,21 +251,13 @@ class HomeRoomListFragment :
|
|||
return true
|
||||
}
|
||||
|
||||
override fun onRejectRoomInvitation(room: RoomSummary) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override fun onRejectRoomInvitation(room: RoomSummary) = Unit
|
||||
|
||||
override fun onAcceptRoomInvitation(room: RoomSummary) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override fun onAcceptRoomInvitation(room: RoomSummary) = Unit
|
||||
|
||||
override fun onJoinSuggestedRoom(room: SpaceChildInfo) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override fun onJoinSuggestedRoom(room: SpaceChildInfo) = Unit
|
||||
|
||||
override fun onSuggestedRoomClicked(room: SpaceChildInfo) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override fun onSuggestedRoomClicked(room: SpaceChildInfo) = Unit
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
package im.vector.app.features.home.room.list.home
|
||||
|
||||
import android.widget.ImageView
|
||||
import androidx.lifecycle.map
|
||||
import androidx.paging.PagedList
|
||||
import arrow.core.Option
|
||||
import arrow.core.toOption
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import dagger.assisted.Assisted
|
||||
|
@ -32,22 +32,22 @@ import im.vector.app.core.platform.StateView
|
|||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.resources.DrawableProvider
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.home.room.list.home.filter.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.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
||||
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
|
||||
import org.matrix.android.sdk.api.query.toActiveSpaceOrNoFilter
|
||||
|
@ -80,6 +80,7 @@ class HomeRoomListViewModel @AssistedInject constructor(
|
|||
|
||||
companion object : MavericksViewModelFactory<HomeRoomListViewModel, HomeRoomListViewState> by hiltMavericksViewModelFactory()
|
||||
|
||||
private var roomsFlow: Flow<Option<RoomSummary>>? = null
|
||||
private val pagedListConfig = PagedList.Config.Builder()
|
||||
.setPageSize(10)
|
||||
.setInitialLoadSizeHint(20)
|
||||
|
@ -87,9 +88,6 @@ class HomeRoomListViewModel @AssistedInject constructor(
|
|||
.setPrefetchDistance(10)
|
||||
.build()
|
||||
|
||||
private val _sections = MutableSharedFlow<Set<HomeRoomSection>>(replay = 1)
|
||||
val sections = _sections.asSharedFlow()
|
||||
|
||||
private var currentFilter: HomeRoomFilter = HomeRoomFilter.ALL
|
||||
private val _emptyStateFlow = MutableSharedFlow<Optional<StateView.State.Empty>>(replay = 1)
|
||||
val emptyStateFlow = _emptyStateFlow.asSharedFlow()
|
||||
|
@ -97,118 +95,77 @@ class HomeRoomListViewModel @AssistedInject constructor(
|
|||
private var filteredPagedRoomSummariesLive: UpdatableLivePageResult? = null
|
||||
|
||||
init {
|
||||
configureSections()
|
||||
observePreferences()
|
||||
observeOrderPreferences()
|
||||
observeInvites()
|
||||
observeRecents()
|
||||
observeFilterTabs()
|
||||
observeRooms()
|
||||
}
|
||||
|
||||
private fun observePreferences() {
|
||||
preferencesStore.areRecentsEnabledFlow.onEach {
|
||||
configureSections()
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
preferencesStore.isAZOrderingEnabledFlow.onEach {
|
||||
configureSections()
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun configureSections() = viewModelScope.launch {
|
||||
val newSections = mutableSetOf<HomeRoomSection>()
|
||||
newSections.add(getInvitesCountSection())
|
||||
|
||||
val areSettingsEnabled = preferencesStore.areRecentsEnabledFlow.first()
|
||||
if (areSettingsEnabled) {
|
||||
newSections.add(getRecentRoomsSection())
|
||||
}
|
||||
newSections.add(getFilteredRoomsSection())
|
||||
|
||||
emitEmptyState()
|
||||
_sections.emit(newSections)
|
||||
|
||||
setState {
|
||||
copy(state = StateView.State.Content)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getRecentRoomsSection(): HomeRoomSection {
|
||||
val liveList = session.roomService()
|
||||
.getBreadcrumbsLive(roomSummaryQueryParams {
|
||||
displayName = QueryStringValue.NoCondition
|
||||
memberships = listOf(Membership.JOIN)
|
||||
})
|
||||
|
||||
return HomeRoomSection.RecentRoomsData(
|
||||
list = liveList
|
||||
)
|
||||
}
|
||||
|
||||
private fun getInvitesCountSection(): HomeRoomSection.InvitesCountData {
|
||||
val builder = RoomSummaryQueryParams.Builder().also {
|
||||
it.memberships = listOf(Membership.INVITE)
|
||||
}
|
||||
|
||||
val liveCount = session.roomService().getRoomSummariesLive(
|
||||
builder.build(),
|
||||
RoomSortOrder.ACTIVITY
|
||||
).map { it.count() }
|
||||
|
||||
return HomeRoomSection.InvitesCountData(liveCount)
|
||||
}
|
||||
|
||||
private suspend fun getFilteredRoomsSection(): HomeRoomSection.RoomSummaryData {
|
||||
val builder = RoomSummaryQueryParams.Builder().also {
|
||||
it.memberships = listOf(Membership.JOIN)
|
||||
}
|
||||
|
||||
val params = getFilteredQueryParams(HomeRoomFilter.ALL, builder.build())
|
||||
val sortOrder = if (preferencesStore.isAZOrderingEnabledFlow.first()) {
|
||||
RoomSortOrder.NAME
|
||||
} else {
|
||||
RoomSortOrder.ACTIVITY
|
||||
}
|
||||
|
||||
val liveResults = session.roomService().getFilteredPagedRoomSummariesLive(
|
||||
params,
|
||||
pagedListConfig,
|
||||
sortOrder
|
||||
).also {
|
||||
this.filteredPagedRoomSummariesLive = it
|
||||
}
|
||||
|
||||
spaceStateHandler.getSelectedSpaceFlow()
|
||||
.distinctUntilChanged()
|
||||
.onStart {
|
||||
emit(spaceStateHandler.getCurrentSpace().toOption())
|
||||
}
|
||||
.onEach { selectedSpaceOption ->
|
||||
val selectedSpace = selectedSpaceOption.orNull()
|
||||
liveResults.queryParams = liveResults.queryParams.copy(
|
||||
spaceFilter = selectedSpace?.roomId.toActiveSpaceOrNoFilter()
|
||||
)
|
||||
emitEmptyState()
|
||||
private fun observeInvites() {
|
||||
session.flow()
|
||||
.liveRoomSummaries(
|
||||
roomSummaryQueryParams {
|
||||
memberships = listOf(Membership.INVITE)
|
||||
},
|
||||
RoomSortOrder.ACTIVITY
|
||||
).onEach { list ->
|
||||
setState { copy(headersData = headersData.copy(invitesCount = list.size)) }
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
return HomeRoomSection.RoomSummaryData(
|
||||
list = liveResults.livePagedList,
|
||||
filtersData = getFiltersDataFlow()
|
||||
)
|
||||
}
|
||||
|
||||
private fun emitEmptyState() {
|
||||
viewModelScope.launch {
|
||||
val emptyState = getEmptyStateData(currentFilter, spaceStateHandler.getCurrentSpace())
|
||||
_emptyStateFlow.emit(Optional.from(emptyState))
|
||||
private fun observeRecents() {
|
||||
preferencesStore.areRecentsEnabledFlow
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { areEnabled ->
|
||||
if (areEnabled) {
|
||||
session.flow()
|
||||
.liveBreadcrumbs(roomSummaryQueryParams {
|
||||
memberships = listOf(Membership.JOIN)
|
||||
})
|
||||
.map { Optional.from(it) }
|
||||
} else {
|
||||
flow { emit(Optional.empty()) }
|
||||
}.onEach { listOptional ->
|
||||
setState { copy(headersData = headersData.copy(recents = listOptional.getOrNull())) }
|
||||
}
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun observeFilterTabs() {
|
||||
preferencesStore.areFiltersEnabledFlow
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { areEnabled ->
|
||||
if (areEnabled) {
|
||||
getFilterTabsFlow()
|
||||
} else {
|
||||
flow { emit(Optional.empty()) }
|
||||
}.onEach { filtersOptional ->
|
||||
setState {
|
||||
validateCurrentFilter(filtersOptional.getOrNull())
|
||||
copy(
|
||||
headersData = headersData.copy(
|
||||
filtersList = filtersOptional.getOrNull(),
|
||||
currentFilter = currentFilter
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun validateCurrentFilter(filtersList: List<HomeRoomFilter>?) {
|
||||
if (filtersList?.contains(currentFilter) != true) {
|
||||
handleChangeRoomFilter(HomeRoomFilter.ALL)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFiltersDataFlow(): SharedFlow<Optional<List<HomeRoomFilter>>> {
|
||||
val flow = MutableSharedFlow<Optional<List<HomeRoomFilter>>>(replay = 1)
|
||||
|
||||
private fun getFilterTabsFlow(): Flow<Optional<MutableList<HomeRoomFilter>>> {
|
||||
val spaceFLow = spaceStateHandler.getSelectedSpaceFlow()
|
||||
.distinctUntilChanged()
|
||||
.onStart {
|
||||
emit(spaceStateHandler.getCurrentSpace().toOption())
|
||||
}
|
||||
|
||||
val favouritesFlow =
|
||||
spaceFLow.flatMapLatest { selectedSpace ->
|
||||
session.flow()
|
||||
|
@ -236,31 +193,76 @@ class HomeRoomListViewModel @AssistedInject constructor(
|
|||
.map { it.isNotEmpty() }
|
||||
.distinctUntilChanged()
|
||||
|
||||
combine(favouritesFlow, dmsFLow, preferencesStore.areFiltersEnabledFlow) { hasFavourite, hasDm, areFiltersEnabled ->
|
||||
Triple(hasFavourite, hasDm, areFiltersEnabled)
|
||||
}.onEach { (hasFavourite, hasDm, areFiltersEnabled) ->
|
||||
if (areFiltersEnabled) {
|
||||
val filtersData = mutableListOf(
|
||||
HomeRoomFilter.ALL,
|
||||
HomeRoomFilter.UNREADS
|
||||
return combine(favouritesFlow, dmsFLow) { hasFavourite, hasDm ->
|
||||
hasFavourite to hasDm
|
||||
}.map { (hasFavourite, hasDm) ->
|
||||
val filtersData = mutableListOf(
|
||||
HomeRoomFilter.ALL,
|
||||
HomeRoomFilter.UNREADS
|
||||
)
|
||||
if (hasFavourite) {
|
||||
filtersData.add(
|
||||
HomeRoomFilter.FAVOURITES
|
||||
)
|
||||
if (hasFavourite) {
|
||||
filtersData.add(
|
||||
HomeRoomFilter.FAVOURITES
|
||||
)
|
||||
}
|
||||
if (hasDm) {
|
||||
filtersData.add(
|
||||
HomeRoomFilter.PEOPlE
|
||||
)
|
||||
}
|
||||
flow.emit(Optional.from(filtersData))
|
||||
} else {
|
||||
flow.emit(Optional.empty())
|
||||
}
|
||||
}.launchIn(viewModelScope)
|
||||
if (hasDm) {
|
||||
filtersData.add(
|
||||
HomeRoomFilter.PEOPlE
|
||||
)
|
||||
}
|
||||
Optional.from(filtersData)
|
||||
}
|
||||
}
|
||||
|
||||
return flow
|
||||
private fun observeRooms() = viewModelScope.launch {
|
||||
val builder = RoomSummaryQueryParams.Builder().also {
|
||||
it.memberships = listOf(Membership.JOIN)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
spaceStateHandler.getSelectedSpaceFlow()
|
||||
.distinctUntilChanged()
|
||||
.onStart {
|
||||
emit(spaceStateHandler.getCurrentSpace().toOption())
|
||||
}
|
||||
.onEach { selectedSpaceOption ->
|
||||
val selectedSpace = selectedSpaceOption.orNull()
|
||||
liveResults.queryParams = liveResults.queryParams.copy(
|
||||
spaceFilter = selectedSpace?.roomId.toActiveSpaceOrNoFilter()
|
||||
)
|
||||
emitEmptyState()
|
||||
}
|
||||
.also { roomsFlow = it }
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
setState { copy(roomsLivePagedList = liveResults.livePagedList) }
|
||||
}
|
||||
|
||||
private fun observeOrderPreferences() {
|
||||
preferencesStore.isAZOrderingEnabledFlow.onEach {
|
||||
observeRooms()
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun emitEmptyState() {
|
||||
viewModelScope.launch {
|
||||
val emptyState = getEmptyStateData(currentFilter, spaceStateHandler.getCurrentSpace())
|
||||
_emptyStateFlow.emit(Optional.from(emptyState))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFilteredQueryParams(filter: HomeRoomFilter, currentParams: RoomSummaryQueryParams): RoomSummaryQueryParams {
|
||||
|
@ -323,16 +325,20 @@ class HomeRoomListViewModel @AssistedInject constructor(
|
|||
is HomeRoomListAction.LeaveRoom -> handleLeaveRoom(action)
|
||||
is HomeRoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
|
||||
is HomeRoomListAction.ToggleTag -> handleToggleTag(action)
|
||||
is HomeRoomListAction.ChangeRoomFilter -> handleChangeRoomFilter(action)
|
||||
is HomeRoomListAction.ChangeRoomFilter -> handleChangeRoomFilter(action.filter)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleChangeRoomFilter(action: HomeRoomListAction.ChangeRoomFilter) {
|
||||
currentFilter = action.filter
|
||||
private fun handleChangeRoomFilter(newFilter: HomeRoomFilter) {
|
||||
if (currentFilter == newFilter) {
|
||||
return
|
||||
}
|
||||
currentFilter = newFilter
|
||||
filteredPagedRoomSummariesLive?.let { liveResults ->
|
||||
liveResults.queryParams = getFilteredQueryParams(action.filter, liveResults.queryParams)
|
||||
liveResults.queryParams = getFilteredQueryParams(currentFilter, liveResults.queryParams)
|
||||
}
|
||||
|
||||
setState { copy(headersData = headersData.copy(currentFilter = currentFilter)) }
|
||||
emitEmptyState()
|
||||
}
|
||||
|
||||
|
|
|
@ -16,9 +16,15 @@
|
|||
|
||||
package im.vector.app.features.home.room.list.home
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagedList
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
import im.vector.app.core.platform.StateView
|
||||
import im.vector.app.features.home.room.list.home.header.RoomsHeadersData
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
|
||||
data class HomeRoomListViewState(
|
||||
val state: StateView.State = StateView.State.Loading
|
||||
val state: StateView.State = StateView.State.Content,
|
||||
val headersData: RoomsHeadersData = RoomsHeadersData(),
|
||||
val roomsLivePagedList: LiveData<PagedList<RoomSummary>>? = null
|
||||
) : MavericksState
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* 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.features.home.room.list.home
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagedList
|
||||
import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
|
||||
sealed class HomeRoomSection {
|
||||
data class RoomSummaryData(
|
||||
val list: LiveData<PagedList<RoomSummary>>,
|
||||
val filtersData: SharedFlow<Optional<List<HomeRoomFilter>>>,
|
||||
) : HomeRoomSection()
|
||||
|
||||
data class RecentRoomsData(
|
||||
val list: LiveData<List<RoomSummary>>
|
||||
) : HomeRoomSection()
|
||||
|
||||
data class InvitesCountData(
|
||||
val count: LiveData<Int>
|
||||
) : HomeRoomSection()
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.list.home.filter
|
||||
package im.vector.app.features.home.room.list.home.header
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import im.vector.app.R
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* 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.features.home.room.list.home.header
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.util.TypedValue
|
||||
import com.airbnb.epoxy.Carousel
|
||||
import com.airbnb.epoxy.CarouselModelBuilder
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.airbnb.epoxy.carousel
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
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.util.toMatrixItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class HomeRoomsHeadersController @Inject constructor(
|
||||
val stringProvider: StringProvider,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
resources: Resources,
|
||||
) : EpoxyController() {
|
||||
|
||||
private var data: RoomsHeadersData = RoomsHeadersData()
|
||||
|
||||
var onFilterChangedListener: ((HomeRoomFilter) -> Unit)? = null
|
||||
var recentsRoomListener: RoomListListener? = null
|
||||
var invitesClickListener: (() -> Unit)? = null
|
||||
|
||||
private val recentsHPadding = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
4f,
|
||||
resources.displayMetrics
|
||||
).toInt()
|
||||
|
||||
private val recentsTopPadding = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
12f,
|
||||
resources.displayMetrics
|
||||
).toInt()
|
||||
|
||||
override fun buildModels() {
|
||||
val host = this
|
||||
if (data.invitesCount != 0) {
|
||||
addInviteCounter(host.invitesClickListener, data.invitesCount)
|
||||
}
|
||||
|
||||
data.recents?.let {
|
||||
addRecents(host, it)
|
||||
}
|
||||
|
||||
host.data.filtersList?.let {
|
||||
addRoomFilterHeaderItem(host.onFilterChangedListener, it, host.data.currentFilter)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addInviteCounter(invitesClickListener: (() -> Unit)?, invitesCount: Int) {
|
||||
inviteCounterItem {
|
||||
id("invites_counter")
|
||||
invitesCount(invitesCount)
|
||||
listener { invitesClickListener?.invoke() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun addRecents(host: HomeRoomsHeadersController, recents: List<RoomSummary>) {
|
||||
carousel {
|
||||
id("recents_carousel")
|
||||
padding(
|
||||
Carousel.Padding(
|
||||
host.recentsHPadding,
|
||||
host.recentsTopPadding,
|
||||
host.recentsHPadding,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
)
|
||||
onBind { _, view, _ ->
|
||||
val colorSurface = MaterialColors.getColor(view, R.attr.vctr_toolbar_background)
|
||||
view.setBackgroundColor(colorSurface)
|
||||
}
|
||||
withModelsFrom(recents) { roomSummary ->
|
||||
val onClick = host.recentsRoomListener?.let { it::onRoomClicked }
|
||||
val onLongClick = host.recentsRoomListener?.let { it::onRoomLongClicked }
|
||||
|
||||
RecentRoomItem_()
|
||||
.id(roomSummary.roomId)
|
||||
.avatarRenderer(host.avatarRenderer)
|
||||
.matrixItem(roomSummary.toMatrixItem())
|
||||
.unreadNotificationCount(roomSummary.notificationCount)
|
||||
.showHighlighted(roomSummary.highlightCount > 0)
|
||||
.itemLongClickListener { _ -> onLongClick?.invoke(roomSummary) ?: false }
|
||||
.itemClickListener { onClick?.invoke(roomSummary) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addRoomFilterHeaderItem(
|
||||
filterChangedListener: ((HomeRoomFilter) -> Unit)?,
|
||||
filtersList: List<HomeRoomFilter>,
|
||||
currentFilter: HomeRoomFilter?,
|
||||
) {
|
||||
roomFilterHeaderItem {
|
||||
id("filter_header")
|
||||
filtersData(filtersList)
|
||||
selectedFilter(currentFilter)
|
||||
onFilterChangedListener(filterChangedListener)
|
||||
}
|
||||
}
|
||||
|
||||
fun submitData(data: RoomsHeadersData) {
|
||||
this.data = data
|
||||
requestModelBuild()
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T> CarouselModelBuilder.withModelsFrom(
|
||||
items: List<T>,
|
||||
modelBuilder: (T) -> EpoxyModel<*>
|
||||
) {
|
||||
models(items.map { modelBuilder(it) })
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.list.home.invites
|
||||
package im.vector.app.features.home.room.list.home.header
|
||||
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.list.home.recent
|
||||
package im.vector.app.features.home.room.list.home.header
|
||||
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.View
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.list.home.filter
|
||||
package im.vector.app.features.home.room.list.home.header
|
||||
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
|
@ -32,13 +32,20 @@ abstract class RoomFilterHeaderItem : VectorEpoxyModel<RoomFilterHeaderItem.Hold
|
|||
@EpoxyAttribute
|
||||
var filtersData: List<HomeRoomFilter>? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var selectedFilter: HomeRoomFilter? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
with(holder.tabLayout) {
|
||||
removeAllTabs()
|
||||
clearOnTabSelectedListeners()
|
||||
|
||||
filtersData?.forEach { filter ->
|
||||
addTab(newTab().setText(filter.titleRes).setTag(filter))
|
||||
addTab(
|
||||
newTab().setText(filter.titleRes).setTag(filter),
|
||||
filter == (selectedFilter ?: HomeRoomFilter.ALL)
|
||||
)
|
||||
}
|
||||
|
||||
addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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.features.home.room.list.home.header
|
||||
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
|
||||
data class RoomsHeadersData(
|
||||
val invitesCount: Int = 0,
|
||||
val filtersList: List<HomeRoomFilter>? = null,
|
||||
val currentFilter: HomeRoomFilter? = null,
|
||||
val recents: List<RoomSummary>? = null
|
||||
)
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* 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.features.home.room.list.home.invites
|
||||
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
class InvitesCounterController @Inject constructor(
|
||||
val stringProvider: StringProvider
|
||||
) : EpoxyController() {
|
||||
|
||||
private var count = 0
|
||||
var clickListener: (() -> Unit)? = null
|
||||
|
||||
override fun buildModels() {
|
||||
val host = this
|
||||
if (count != 0) {
|
||||
inviteCounterItem {
|
||||
id("invites_counter")
|
||||
invitesCount(host.count)
|
||||
listener { host.clickListener?.invoke() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun submitData(count: Int?) {
|
||||
this.count = count ?: 0
|
||||
requestModelBuild()
|
||||
}
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
/*
|
||||
* 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.features.home.room.list.home.recent
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.util.TypedValue
|
||||
import com.airbnb.epoxy.Carousel
|
||||
import com.airbnb.epoxy.CarouselModelBuilder
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.airbnb.epoxy.carousel
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import im.vector.app.R
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
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.util.toMatrixItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class RecentRoomCarouselController @Inject constructor(
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val resources: Resources,
|
||||
) : EpoxyController() {
|
||||
|
||||
private var data: List<RoomSummary>? = null
|
||||
var listener: RoomListListener? = null
|
||||
|
||||
private val hPadding = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
4f,
|
||||
resources.displayMetrics
|
||||
).toInt()
|
||||
|
||||
private val topPadding = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
12f,
|
||||
resources.displayMetrics
|
||||
).toInt()
|
||||
|
||||
fun submitList(recentList: List<RoomSummary>) {
|
||||
this.data = recentList
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
override fun buildModels() {
|
||||
val host = this
|
||||
data?.let { data ->
|
||||
carousel {
|
||||
id("recents_carousel")
|
||||
padding(Carousel.Padding(
|
||||
host.hPadding,
|
||||
host.topPadding,
|
||||
host.hPadding,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
)
|
||||
onBind { _, view, _ ->
|
||||
val colorSurface = MaterialColors.getColor(view, R.attr.vctr_toolbar_background)
|
||||
view.setBackgroundColor(colorSurface)
|
||||
}
|
||||
withModelsFrom(data) { roomSummary ->
|
||||
val onClick = host.listener?.let { it::onRoomClicked }
|
||||
val onLongClick = host.listener?.let { it::onRoomLongClicked }
|
||||
|
||||
RecentRoomItem_()
|
||||
.id(roomSummary.roomId)
|
||||
.avatarRenderer(host.avatarRenderer)
|
||||
.matrixItem(roomSummary.toMatrixItem())
|
||||
.unreadNotificationCount(roomSummary.notificationCount)
|
||||
.showHighlighted(roomSummary.highlightCount > 0)
|
||||
.itemLongClickListener { _ -> onLongClick?.invoke(roomSummary) ?: false }
|
||||
.itemClickListener { onClick?.invoke(roomSummary) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T> CarouselModelBuilder.withModelsFrom(
|
||||
items: List<T>,
|
||||
modelBuilder: (T) -> EpoxyModel<*>
|
||||
) {
|
||||
models(items.map { modelBuilder(it) })
|
||||
}
|
Loading…
Reference in a new issue