mirror of
https://github.com/element-hq/element-android
synced 2024-12-18 07:12:47 +03:00
Merge pull request #6986 from vector-im/feature/nfe/invites_empty_state
empty state for new invites screen
This commit is contained in:
commit
1c35e5ae9c
8 changed files with 115 additions and 34 deletions
1
changelog.d/6876.feature
Normal file
1
changelog.d/6876.feature
Normal file
|
@ -0,0 +1 @@
|
|||
[App Layout] - Invites now show empty screen after you reject last invite
|
|
@ -451,6 +451,9 @@
|
|||
<!-- Invites fragment -->
|
||||
<string name="invites_title">Invites</string>
|
||||
|
||||
<string name="invites_empty_title">Nothing new.</string>
|
||||
<string name="invites_empty_message">This is where your new requests and invites will be.</string>
|
||||
|
||||
<!-- People fragment -->
|
||||
<string name="direct_chats_header">Conversations</string>
|
||||
<string name="matrix_only_filter">Matrix contacts only</string>
|
||||
|
|
|
@ -20,15 +20,18 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
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 +54,8 @@ class InvitesFragment : VectorBaseFragment<FragmentInvitesBinding>(), RoomListLi
|
|||
setupToolbar(views.invitesToolbar)
|
||||
.allowBack()
|
||||
|
||||
views.invitesStateView.contentView = views.invitesRecycler
|
||||
|
||||
views.invitesRecycler.configureWith(controller)
|
||||
controller.listener = this
|
||||
|
||||
|
@ -62,13 +67,31 @@ class InvitesFragment : VectorBaseFragment<FragmentInvitesBinding>(), 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
|
||||
controller.submitList(it.content)
|
||||
}
|
||||
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 +106,6 @@ class InvitesFragment : VectorBaseFragment<FragmentInvitesBinding>(), 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))
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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<InvitesViewState, InvitesAction, InvitesViewEvents>(initialState) {
|
||||
|
||||
private val pagedListConfig = PagedList.Config.Builder()
|
||||
|
@ -52,6 +65,11 @@ class InvitesViewModel @AssistedInject constructor(
|
|||
|
||||
companion object : MavericksViewModelFactory<InvitesViewModel, InvitesViewState> by hiltMavericksViewModelFactory()
|
||||
|
||||
private val _invites = MutableSharedFlow<InvitesContentState>(replay = 1)
|
||||
val invites = _invites.asSharedFlow()
|
||||
|
||||
private 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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<PagedList<RoomSummary>>? = null,
|
||||
val roomMembershipChanges: Map<String, ChangeMembershipState> = emptyMap(),
|
||||
) : MavericksState
|
||||
|
||||
sealed interface InvitesContentState {
|
||||
object Loading : InvitesContentState
|
||||
data class Empty(
|
||||
val title: CharSequence,
|
||||
val image: Drawable?,
|
||||
val message: CharSequence
|
||||
) : InvitesContentState
|
||||
|
||||
data class Content(val content: PagedList<RoomSummary>) : InvitesContentState
|
||||
data class Error(val throwable: Throwable) : InvitesContentState
|
||||
}
|
||||
|
|
14
vector/src/main/res/drawable/ic_invites_empty.xml
Normal file
14
vector/src/main/res/drawable/ic_invites_empty.xml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="60dp"
|
||||
android:height="60dp"
|
||||
android:viewportWidth="60"
|
||||
android:viewportHeight="60">
|
||||
<path
|
||||
android:pathData="M30,30m-30,0a30,30 0,1 1,60 0a30,30 0,1 1,-60 0"
|
||||
android:fillColor="#E3E8F0"/>
|
||||
<path
|
||||
android:pathData="M25.665,33.544L15.229,23.209L29.236,13.398C29.993,12.868 31.007,12.868 31.764,13.398L45.771,23.209L35.247,33.631L33.851,32.446C31.93,30.816 29.11,30.778 27.145,32.355L25.665,33.544ZM22.439,36.134L14,42.91V27.777L22.439,36.134ZM47,27.777V43.606L38.393,36.301L47,27.777ZM31.177,35.566L43.47,46H16.714L29.733,35.546C30.156,35.208 30.765,35.216 31.177,35.566Z"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#737D8C"
|
||||
android:strokeColor="#737D8C"/>
|
||||
</vector>
|
|
@ -20,17 +20,24 @@
|
|||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/invites_recycler"
|
||||
<im.vector.app.core.platform.StateView
|
||||
android:id="@+id/invites_state_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:fastScrollEnabled="true"
|
||||
android:overScrollMode="always"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appBarLayout" />
|
||||
app:layout_constraintTop_toBottomOf="@id/appBarLayout">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/invites_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fastScrollEnabled="true"
|
||||
android:overScrollMode="always"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
</im.vector.app.core.platform.StateView>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
Loading…
Reference in a new issue