App layout Home screen empty states (#7007)

This commit is contained in:
Nikita Fedrunov 2022-09-05 18:11:25 +02:00 committed by GitHub
parent ae653eb672
commit efe5eb0501
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 145 additions and 3 deletions

1
changelog.d/6835.feature Normal file
View File

@ -0,0 +1 @@
[App Layout] New empty states for home screen

View File

@ -3244,4 +3244,14 @@
<item quantity="other">Consider signing out from old sessions (%1$d days or more) that you dont use anymore.</item>
</plurals>
<!-- Note to translators: %s will be replaces with selected space name -->
<string name="home_empty_space_no_rooms_title">%s\nis looking a little empty.</string>
<!-- Note to translators: for RTL languages, Spaces will be at the bottom left. Please translate "bottom-left" instead of "bottom-right". Thanks!-->
<string name="home_empty_space_no_rooms_message">Spaces are a new way to group rooms and people. Add an existing room, or create a new one, using the bottom-right button.</string>
<!-- Note to translators: %s will be replaces with current user displayname -->
<string name="home_empty_no_rooms_title">Welcome to ${app_name},\n%s.</string>
<string name="home_empty_no_rooms_message">The all-in-one secure chat app for teams, friends and organisations. Create a chat, or join an existing room, to get started.</string>
<string name="home_empty_no_unreads_title">Nothing to report.</string>
<string name="home_empty_no_unreads_message">This is where your unread messages will show up, when you have some.</string>
</resources>

View File

@ -21,6 +21,7 @@ import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.core.view.isVisible
import im.vector.app.R
import im.vector.app.core.extensions.updateConstraintSet
@ -36,7 +37,8 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
val title: CharSequence? = null,
val image: Drawable? = null,
val isBigImage: Boolean = false,
val message: CharSequence? = null
val message: CharSequence? = null,
val imageScaleType: ImageView.ScaleType? = ImageView.ScaleType.FIT_CENTER,
) : State()
data class Error(val message: CharSequence? = null) : State()
@ -79,6 +81,7 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
is State.Content -> Unit
is State.Loading -> Unit
is State.Empty -> {
views.emptyImageView.scaleType = newState.imageScaleType
views.emptyImageView.setImageDrawable(newState.image)
views.emptyView.updateConstraintSet {
it.constrainPercentHeight(R.id.emptyImageView, if (newState.isBigImage) 0.5f else 0.1f)

View File

@ -199,11 +199,17 @@ 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)
section.list.observe(viewLifecycleOwner) { list ->
controller.submitList(list)
if (list.isEmpty()) {
controller.requestForcedModelBuild()
}
}
}.adapter
}

View File

@ -16,6 +16,7 @@
package im.vector.app.features.home.room.list.home
import android.widget.ImageView
import androidx.lifecycle.map
import androidx.paging.PagedList
import arrow.core.toOption
@ -23,11 +24,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.DrawableProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
@ -52,6 +56,7 @@ 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
@ -63,6 +68,8 @@ class HomeRoomListViewModel @AssistedInject constructor(
private val session: Session,
private val spaceStateHandler: SpaceStateHandler,
private val preferencesStore: HomeLayoutPreferencesStore,
private val stringProvider: StringProvider,
private val drawableProvider: DrawableProvider,
) : VectorViewModel<HomeRoomListViewState, HomeRoomListAction, HomeRoomListViewEvents>(initialState) {
@AssistedFactory
@ -82,6 +89,10 @@ class HomeRoomListViewModel @AssistedInject constructor(
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()
private var filteredPagedRoomSummariesLive: UpdatableLivePageResult? = null
init {
@ -109,6 +120,7 @@ class HomeRoomListViewModel @AssistedInject constructor(
}
newSections.add(getFilteredRoomsSection())
emitEmptyState()
_sections.emit(newSections)
setState {
@ -171,6 +183,7 @@ class HomeRoomListViewModel @AssistedInject constructor(
liveResults.queryParams = liveResults.queryParams.copy(
spaceFilter = selectedSpace?.roomId.toActiveSpaceOrNoFilter()
)
emitEmptyState()
}.launchIn(viewModelScope)
return HomeRoomSection.RoomSummaryData(
@ -179,6 +192,13 @@ class HomeRoomListViewModel @AssistedInject constructor(
)
}
private fun emitEmptyState() {
viewModelScope.launch {
val emptyState = getEmptyStateData(currentFilter, spaceStateHandler.getCurrentSpace())
_emptyStateFlow.emit(Optional.from(emptyState))
}
}
private fun getFiltersDataFlow(): SharedFlow<Optional<List<HomeRoomFilter>>> {
val flow = MutableSharedFlow<Optional<List<HomeRoomFilter>>>(replay = 1)
@ -250,6 +270,38 @@ class HomeRoomListViewModel @AssistedInject constructor(
}
}
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),
image = drawableProvider.getDrawable(R.drawable.ill_empty_space),
isBigImage = true
)
} 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),
image = drawableProvider.getDrawable(R.drawable.ill_empty_all_chats),
isBigImage = true
)
}
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),
image = drawableProvider.getDrawable(R.drawable.ill_empty_unreads),
isBigImage = true,
imageScaleType = ImageView.ScaleType.CENTER_INSIDE
)
else ->
null
}
}
override fun handle(action: HomeRoomListAction) {
when (action) {
is HomeRoomListAction.SelectRoom -> handleSelectRoom(action)
@ -261,9 +313,12 @@ class HomeRoomListViewModel @AssistedInject constructor(
}
private fun handleChangeRoomFilter(action: HomeRoomListAction.ChangeRoomFilter) {
currentFilter = action.filter
filteredPagedRoomSummariesLive?.let { liveResults ->
liveResults.queryParams = getFilteredQueryParams(action.filter, liveResults.queryParams)
}
emitEmptyState()
}
fun isPublicRoom(roomId: String): Boolean {

View File

@ -0,0 +1,40 @@
/*
* 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.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
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
@EpoxyModelClass
abstract class RoomListEmptyItem : VectorEpoxyModel<RoomListEmptyItem.Holder>(R.layout.item_state_view) {
@EpoxyAttribute
lateinit var emptyData: StateView.State.Empty
override fun bind(holder: Holder) {
super.bind(holder)
holder.stateView.state = emptyData
}
class Holder : VectorEpoxyHolder() {
val stateView by bind<StateView>(R.id.stateView)
}
}

View File

@ -18,11 +18,13 @@ 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
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
@ -44,6 +46,8 @@ class HomeFilteredRoomsController(
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
@ -54,14 +58,29 @@ class HomeFilteredRoomsController(
onFilterChangedListener(host.onFilterChanged)
}
}
super.addModels(models)
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
}
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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<im.vector.app.core.platform.StateView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/stateView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:colorBackground">
</im.vector.app.core.platform.StateView>