From 50034599628a320ca7ea32ef5f6a4a31ebf515e1 Mon Sep 17 00:00:00 2001 From: NIkita Fedrunov Date: Thu, 1 Sep 2022 14:39:13 +0200 Subject: [PATCH] empty state for new invites screen --- changelog.d/6876.feature | 1 + .../room/list/home/invites/InvitesFragment.kt | 43 ++++++++++----- .../list/home/invites/InvitesViewEvents.kt | 1 - .../list/home/invites/InvitesViewModel.kt | 53 +++++++++++++++---- .../list/home/invites/InvitesViewState.kt | 15 +++++- .../main/res/drawable/ic_invites_empty.xml | 14 +++++ .../src/main/res/layout/fragment_invites.xml | 21 +++++--- vector/src/main/res/values/strings.xml | 3 ++ 8 files changed, 117 insertions(+), 34 deletions(-) create mode 100644 changelog.d/6876.feature create mode 100644 vector/src/main/res/drawable/ic_invites_empty.xml diff --git a/changelog.d/6876.feature b/changelog.d/6876.feature new file mode 100644 index 0000000000..12a2b78a1e --- /dev/null +++ b/changelog.d/6876.feature @@ -0,0 +1 @@ +[App Layout] - Invites now show empty screen after you reject last invite diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesFragment.kt index 74b46cec33..7fbc6f5c3f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesFragment.kt @@ -20,15 +20,19 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope +import androidx.paging.PagedList import com.airbnb.mvrx.fragmentViewModel -import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.configureWith +import im.vector.app.core.platform.StateView import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentInvitesBinding import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.home.room.list.RoomListListener import im.vector.app.features.notifications.NotificationDrawerManager +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import javax.inject.Inject @@ -51,6 +55,8 @@ class InvitesFragment : VectorBaseFragment(), RoomListLi setupToolbar(views.invitesToolbar) .allowBack() + views.invitesStateView.contentView = views.invitesRecycler + views.invitesRecycler.configureWith(controller) controller.listener = this @@ -62,13 +68,32 @@ class InvitesFragment : VectorBaseFragment(), RoomListLi when (it) { is InvitesViewEvents.Failure -> showFailure(it.throwable) is InvitesViewEvents.OpenRoom -> handleOpenRoom(it.roomSummary, it.shouldCloseInviteView) - InvitesViewEvents.Close -> handleClose() } } - } - private fun handleClose() { - requireActivity().finish() + viewModel.invites.onEach { + when (it) { + is InvitesContentState.Content -> { + views.invitesStateView.state = StateView.State.Content + Suppress("UNCHECKED_CAST") + controller.submitList(it.content as? PagedList) + } + is InvitesContentState.Empty -> { + views.invitesStateView.state = StateView.State.Empty( + title = it.title, + image = it.image, + message = it.message + ) + } + is InvitesContentState.Error -> { + when (views.invitesStateView.state) { + StateView.State.Content -> showErrorInSnackbar(it.throwable) + else -> views.invitesStateView.state = StateView.State.Error(it.throwable.message) + } + } + InvitesContentState.Loading -> views.invitesStateView.state = StateView.State.Loading + } + }.launchIn(viewLifecycleOwner.lifecycleScope) } private fun handleOpenRoom(roomSummary: RoomSummary, shouldCloseInviteView: Boolean) { @@ -83,14 +108,6 @@ class InvitesFragment : VectorBaseFragment(), RoomListLi } } - override fun invalidate(): Unit = withState(viewModel) { state -> - super.invalidate() - - state.pagedList?.observe(viewLifecycleOwner) { list -> - controller.submitList(list) - } - } - override fun onRejectRoomInvitation(room: RoomSummary) { notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(room.roomId) } viewModel.handle(InvitesAction.RejectInvitation(room)) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewEvents.kt index d68577cf95..21310592a4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewEvents.kt @@ -22,5 +22,4 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary sealed class InvitesViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable) : InvitesViewEvents() data class OpenRoom(val roomSummary: RoomSummary, val shouldCloseInviteView: Boolean) : InvitesViewEvents() - object Close : InvitesViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewModel.kt index b0d854be66..1e5880d772 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewModel.kt @@ -16,14 +16,25 @@ package im.vector.app.features.home.room.list.home.invites +import androidx.lifecycle.asFlow import androidx.paging.PagedList 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.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.resources.DrawableProvider +import im.vector.app.core.resources.StringProvider +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.catch +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.session.Session @@ -36,6 +47,8 @@ import timber.log.Timber class InvitesViewModel @AssistedInject constructor( @Assisted val initialState: InvitesViewState, private val session: Session, + private val stringProvider: StringProvider, + private val drawableProvider: DrawableProvider ) : VectorViewModel(initialState) { private val pagedListConfig = PagedList.Config.Builder() @@ -52,6 +65,11 @@ class InvitesViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + private val _invites = MutableSharedFlow(replay = 1) + val invites = _invites.asSharedFlow() + + var invitesCount = -1 + init { observeInvites() } @@ -72,8 +90,6 @@ class InvitesViewModel @AssistedInject constructor( return@withState } - val shouldCloseInviteView = state.pagedList?.value?.size == 1 - viewModelScope.launch { try { session.roomService().leaveRoom(roomId) @@ -81,9 +97,6 @@ class InvitesViewModel @AssistedInject constructor( // Instead, we wait for the room to be rejected // Known bug: if the user is invited again (after rejecting the first invitation), the loading will be displayed instead of the buttons. // If we update the state, the button will be displayed again, so it's not ideal... - if (shouldCloseInviteView) { - _viewEvents.post(InvitesViewEvents.Close) - } } catch (failure: Throwable) { // Notify the user _viewEvents.post(InvitesViewEvents.Failure(failure)) @@ -101,9 +114,7 @@ class InvitesViewModel @AssistedInject constructor( } // close invites view when navigate to a room from the last one invite - val shouldCloseInviteView = state.pagedList?.value?.size == 1 - - _viewEvents.post(InvitesViewEvents.OpenRoom(action.roomSummary, shouldCloseInviteView)) + val shouldCloseInviteView = invitesCount == 1 // quick echo setState { @@ -117,6 +128,8 @@ class InvitesViewModel @AssistedInject constructor( } ) } + + _viewEvents.post(InvitesViewEvents.OpenRoom(action.roomSummary, shouldCloseInviteView)) } private fun observeInvites() { @@ -129,8 +142,26 @@ class InvitesViewModel @AssistedInject constructor( sortOrder = RoomSortOrder.ACTIVITY ) - setState { - copy(pagedList = pagedList) - } + pagedList.asFlow() + .map { + if (it.isEmpty()) { + InvitesContentState.Empty( + title = stringProvider.getString(R.string.invites_empty_title), + image = drawableProvider.getDrawable(R.drawable.ic_invites_empty), + message = stringProvider.getString(R.string.invites_empty_message) + ) + } else { + invitesCount = it.loadedCount + InvitesContentState.Content(it) + } + } + .catch { + emit(InvitesContentState.Error(it)) + } + .onStart { + emit(InvitesContentState.Loading) + }.onEach { + _invites.emit(it) + }.launchIn(viewModelScope) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewState.kt index 708db29604..397042a96a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewState.kt @@ -16,13 +16,24 @@ package im.vector.app.features.home.room.list.home.invites -import androidx.lifecycle.LiveData +import android.graphics.drawable.Drawable import androidx.paging.PagedList import com.airbnb.mvrx.MavericksState import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomSummary data class InvitesViewState( - val pagedList: LiveData>? = null, val roomMembershipChanges: Map = emptyMap(), ) : MavericksState + +sealed class InvitesContentState { + object Loading : InvitesContentState() + data class Empty( + val title: CharSequence, + val image: Drawable?, + val message: CharSequence + ) : InvitesContentState() + + data class Content(val content: PagedList) : InvitesContentState() + data class Error(val throwable: Throwable) : InvitesContentState() +} diff --git a/vector/src/main/res/drawable/ic_invites_empty.xml b/vector/src/main/res/drawable/ic_invites_empty.xml new file mode 100644 index 0000000000..79908ff380 --- /dev/null +++ b/vector/src/main/res/drawable/ic_invites_empty.xml @@ -0,0 +1,14 @@ + + + + diff --git a/vector/src/main/res/layout/fragment_invites.xml b/vector/src/main/res/layout/fragment_invites.xml index 74226357c9..070cad5ec8 100644 --- a/vector/src/main/res/layout/fragment_invites.xml +++ b/vector/src/main/res/layout/fragment_invites.xml @@ -20,17 +20,24 @@ - + app:layout_constraintTop_toBottomOf="@id/appBarLayout"> + + + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index f8eb4b8de0..178feb2223 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -445,6 +445,9 @@ Invites + Nothing new. + This is where your new requests and invites will be. + Conversations Matrix contacts only