new app layout's room list overhaul (#7101)

This commit is contained in:
Nikita Fedrunov 2022-09-12 16:47:32 +02:00 committed by GitHub
parent e37344a059
commit da83a85f74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 371 additions and 409 deletions

View File

@ -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)

View File

@ -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

View File

@ -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
}

View File

@ -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()
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)
}
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
private fun observeRecents() {
preferencesStore.areRecentsEnabledFlow
.distinctUntilChanged()
.flatMapLatest { areEnabled ->
if (areEnabled) {
session.flow()
.liveBreadcrumbs(roomSummaryQueryParams {
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
.map { Optional.from(it) }
} else {
RoomSortOrder.ACTIVITY
flow { emit(Optional.empty()) }
}.onEach { listOptional ->
setState { copy(headersData = headersData.copy(recents = listOptional.getOrNull())) }
}
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()
}.launchIn(viewModelScope)
}
return HomeRoomSection.RoomSummaryData(
list = liveResults.livePagedList,
filtersData = getFiltersDataFlow()
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 emitEmptyState() {
viewModelScope.launch {
val emptyState = getEmptyStateData(currentFilter, spaceStateHandler.getCurrentSpace())
_emptyStateFlow.emit(Optional.from(emptyState))
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,10 +193,9 @@ 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) {
return combine(favouritesFlow, dmsFLow) { hasFavourite, hasDm ->
hasFavourite to hasDm
}.map { (hasFavourite, hasDm) ->
val filtersData = mutableListOf(
HomeRoomFilter.ALL,
HomeRoomFilter.UNREADS
@ -254,13 +210,59 @@ class HomeRoomListViewModel @AssistedInject constructor(
HomeRoomFilter.PEOPlE
)
}
flow.emit(Optional.from(filtersData))
} else {
flow.emit(Optional.empty())
Optional.from(filtersData)
}
}
}.launchIn(viewModelScope)
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()
}

View File

@ -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

View File

@ -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()
}

View File

@ -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

View File

@ -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) })
}

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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
)

View File

@ -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()
}
}

View File

@ -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) })
}