From 0b8bbbfe42756e038d576d597c9024b40661b80b Mon Sep 17 00:00:00 2001 From: NIkita Fedrunov Date: Tue, 30 Aug 2022 11:21:04 +0200 Subject: [PATCH 1/3] app layout empty states --- .../home/room/list/home/RoomListEmptyItem.kt | 27 +++++++++++++++++++ .../filter/HomeFilteredRoomsController.kt | 3 +++ .../main/res/layout/item_room_list_empty.xml | 8 ++++++ 3 files changed, 38 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/home/RoomListEmptyItem.kt create mode 100644 vector/src/main/res/layout/item_room_list_empty.xml diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/RoomListEmptyItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/RoomListEmptyItem.kt new file mode 100644 index 00000000000..06d48b35ba5 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/RoomListEmptyItem.kt @@ -0,0 +1,27 @@ +/* + * 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 com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.features.home.room.list.home.filter.RoomFilterHeaderItem + +@EpoxyModelClass +abstract class RoomListEmptyItem : VectorEpoxyModel(R.layout.item_room_list_empty) { + +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt index 2d673bc0899..59f61682cb4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt @@ -54,6 +54,9 @@ class HomeFilteredRoomsController( onFilterChangedListener(host.onFilterChanged) } } + + if (models.isEmpty()) { + } super.addModels(models) } diff --git a/vector/src/main/res/layout/item_room_list_empty.xml b/vector/src/main/res/layout/item_room_list_empty.xml new file mode 100644 index 00000000000..a6c3cc84786 --- /dev/null +++ b/vector/src/main/res/layout/item_room_list_empty.xml @@ -0,0 +1,8 @@ + + + + From 186886a00d9829b87302933252dfd28c2a604e7f Mon Sep 17 00:00:00 2001 From: NIkita Fedrunov Date: Wed, 31 Aug 2022 11:14:43 +0200 Subject: [PATCH 2/3] 2 --- .../home/room/list/home/RoomListEmptyItem.kt | 14 +++++++++++++- .../home/filter/HomeFilteredRoomsController.kt | 4 ++++ .../src/main/res/layout/item_room_list_empty.xml | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/RoomListEmptyItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/RoomListEmptyItem.kt index 06d48b35ba5..96b348f7585 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/RoomListEmptyItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/RoomListEmptyItem.kt @@ -17,11 +17,23 @@ package im.vector.app.features.home.room.list.home import com.airbnb.epoxy.EpoxyModelClass +import com.google.android.material.tabs.TabLayout import im.vector.app.R +import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.platform.StateView import im.vector.app.features.home.room.list.home.filter.RoomFilterHeaderItem @EpoxyModelClass -abstract class RoomListEmptyItem : VectorEpoxyModel(R.layout.item_room_list_empty) { +abstract class RoomListEmptyItem : VectorEpoxyModel(R.layout.item_room_list_empty) { + override fun bind(holder: Holder) { + super.bind(holder) + + holder.stateView.state = StateView.State.Empty("title") + } + + class Holder : VectorEpoxyHolder() { + val stateView by bind(R.id.stateView) + } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt index 59f61682cb4..0d2c5c2defc 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt @@ -23,6 +23,7 @@ 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 @@ -56,6 +57,9 @@ class HomeFilteredRoomsController( } if (models.isEmpty()) { + roomListEmptyItem { + id("state_item") + } } super.addModels(models) } diff --git a/vector/src/main/res/layout/item_room_list_empty.xml b/vector/src/main/res/layout/item_room_list_empty.xml index a6c3cc84786..3cf2e3e6d20 100644 --- a/vector/src/main/res/layout/item_room_list_empty.xml +++ b/vector/src/main/res/layout/item_room_list_empty.xml @@ -1,6 +1,6 @@ From a306ea282fc1c60591ce70dc900bb8466bbdfd91 Mon Sep 17 00:00:00 2001 From: NIkita Fedrunov Date: Thu, 1 Sep 2022 12:52:12 +0200 Subject: [PATCH 3/3] 3 --- .../room/list/home/HomeRoomListFragment.kt | 3 + .../room/list/home/HomeRoomListViewModel.kt | 57 ++++++++++++++++--- .../home/room/list/home/HomeRoomSection.kt | 1 + .../home/room/list/home/RoomListEmptyItem.kt | 8 ++- .../filter/HomeFilteredRoomsController.kt | 25 ++++++-- 5 files changed, 78 insertions(+), 16 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt index 32635e3407f..a6d324604d8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt @@ -199,6 +199,9 @@ class HomeRoomListFragment : ).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) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt index 5ecf9d6d963..7048581bcc9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt @@ -23,11 +23,14 @@ import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import im.vector.app.R import im.vector.app.SpaceStateHandler import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.StateView import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.resources.StringProvider +import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow @@ -52,10 +55,12 @@ import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.state.isPublic import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.flow.flow class HomeRoomListViewModel @AssistedInject constructor( @@ -63,6 +68,7 @@ class HomeRoomListViewModel @AssistedInject constructor( private val session: Session, private val spaceStateHandler: SpaceStateHandler, private val preferencesStore: HomeLayoutPreferencesStore, + private val stringProvider: StringProvider, ) : VectorViewModel(initialState) { @AssistedFactory @@ -82,6 +88,9 @@ class HomeRoomListViewModel @AssistedInject constructor( private val _sections = MutableSharedFlow>(replay = 1) val sections = _sections.asSharedFlow() + private val _emptyStateFlow = MutableSharedFlow>(replay = 1) + val emptyStateFlow = _emptyStateFlow.asSharedFlow() + private var filteredPagedRoomSummariesLive: UpdatableLivePageResult? = null init { @@ -230,11 +239,11 @@ class HomeRoomListViewModel @AssistedInject constructor( private fun getFilteredQueryParams(filter: HomeRoomFilter, currentParams: RoomSummaryQueryParams): RoomSummaryQueryParams { return when (filter) { - HomeRoomFilter.ALL -> currentParams.copy( + HomeRoomFilter.ALL -> currentParams.copy( roomCategoryFilter = null, roomTagQueryFilter = null ) - HomeRoomFilter.UNREADS -> currentParams.copy( + HomeRoomFilter.UNREADS -> currentParams.copy( roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS, roomTagQueryFilter = RoomTagQueryFilter(null, false, null) ) @@ -243,20 +252,45 @@ class HomeRoomListViewModel @AssistedInject constructor( roomCategoryFilter = null, roomTagQueryFilter = RoomTagQueryFilter(true, null, null) ) - HomeRoomFilter.PEOPlE -> currentParams.copy( + HomeRoomFilter.PEOPlE -> currentParams.copy( roomCategoryFilter = RoomCategoryFilter.ONLY_DM, roomTagQueryFilter = null ) } } + private fun getEmptyStateData(filter: HomeRoomFilter, selectedSpace: RoomSummary?): StateView.State.Empty? { + return when (filter) { + HomeRoomFilter.ALL -> + if (selectedSpace != null) { + StateView.State.Empty( + title = stringProvider.getString(R.string.home_empty_space_no_rooms_title, selectedSpace.displayName), + message = stringProvider.getString(R.string.home_empty_space_no_rooms_message) + ) + } else { + val userName = session.userService().getUser(session.myUserId)?.displayName ?: "" + StateView.State.Empty( + title = stringProvider.getString(R.string.home_empty_no_rooms_title, userName), + message = stringProvider.getString(R.string.home_empty_no_rooms_message) + ) + } + HomeRoomFilter.UNREADS -> + StateView.State.Empty( + title = stringProvider.getString(R.string.home_empty_no_unreads_title), + message = stringProvider.getString(R.string.home_empty_no_unreads_message) + ) + else -> + null + } + } + override fun handle(action: HomeRoomListAction) { when (action) { - is HomeRoomListAction.SelectRoom -> handleSelectRoom(action) - is HomeRoomListAction.LeaveRoom -> handleLeaveRoom(action) + is HomeRoomListAction.SelectRoom -> handleSelectRoom(action) + is HomeRoomListAction.LeaveRoom -> handleLeaveRoom(action) is HomeRoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action) - is HomeRoomListAction.ToggleTag -> handleToggleTag(action) - is HomeRoomListAction.ChangeRoomFilter -> handleChangeRoomFilter(action) + is HomeRoomListAction.ToggleTag -> handleToggleTag(action) + is HomeRoomListAction.ChangeRoomFilter -> handleChangeRoomFilter(action) } } @@ -264,6 +298,11 @@ class HomeRoomListViewModel @AssistedInject constructor( filteredPagedRoomSummariesLive?.let { liveResults -> liveResults.queryParams = getFilteredQueryParams(action.filter, liveResults.queryParams) } + + viewModelScope.launch { + val emptyState = getEmptyStateData(action.filter, spaceStateHandler.getCurrentSpace()) + _emptyStateFlow.emit(Optional.from(emptyState)) + } } fun isPublicRoom(roomId: String): Boolean { @@ -322,9 +361,9 @@ class HomeRoomListViewModel @AssistedInject constructor( private fun String.otherTag(): String? { return when (this) { - RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY + RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY RoomTag.ROOM_TAG_LOW_PRIORITY -> RoomTag.ROOM_TAG_FAVOURITE - else -> null + else -> null } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt index 29df594d06b..9a49e26f596 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt @@ -18,6 +18,7 @@ package im.vector.app.features.home.room.list.home import androidx.lifecycle.LiveData import androidx.paging.PagedList +import im.vector.app.core.platform.StateView 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 diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/RoomListEmptyItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/RoomListEmptyItem.kt index 96b348f7585..fce1484163d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/RoomListEmptyItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/RoomListEmptyItem.kt @@ -16,21 +16,25 @@ package im.vector.app.features.home.room.list.home +import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import com.google.android.material.tabs.TabLayout import im.vector.app.R import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.platform.StateView +import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter import im.vector.app.features.home.room.list.home.filter.RoomFilterHeaderItem @EpoxyModelClass abstract class RoomListEmptyItem : VectorEpoxyModel(R.layout.item_room_list_empty) { + @EpoxyAttribute + var emptyData: StateView.State.Empty? = null + override fun bind(holder: Holder) { super.bind(holder) - - holder.stateView.state = StateView.State.Empty("title") + holder.stateView.state = emptyData ?: StateView.State.Content } class Holder : VectorEpoxyHolder() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt index 0d2c5c2defc..9b294b798df 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt @@ -18,6 +18,7 @@ package im.vector.app.features.home.room.list.home.filter import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.paging.PagedListEpoxyController +import im.vector.app.core.platform.StateView import im.vector.app.core.utils.createUIHandler import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.home.room.list.RoomListListener @@ -45,6 +46,8 @@ class HomeFilteredRoomsController( var onFilterChanged: ((HomeRoomFilter) -> Unit)? = null private var filtersData: List? = null + private var emptyStateData: StateView.State.Empty? = null + private var currentState: StateView.State = StateView.State.Content override fun addModels(models: List>) { val host = this @@ -56,19 +59,31 @@ class HomeFilteredRoomsController( } } - if (models.isEmpty()) { - roomListEmptyItem { - id("state_item") + if (models.isEmpty() && emptyStateData != null) { + emptyStateData?.let { emptyState -> + roomListEmptyItem { + id("state_item") + emptyData(emptyState) + } + currentState = emptyState } + } else { + currentState = StateView.State.Content + super.addModels(models) + } + } + + fun submitEmptyStateData(state: StateView.State.Empty?) { + this.emptyStateData = state + if (currentState is StateView.State.Empty) { + requestModelBuild() } - super.addModels(models) } fun submitFiltersData(data: List?) { 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)