Show progress dialog while we are sending invites in a room (#5342)
* Add `InvitePeopleState.sendInvitesAction` Keep track of the progress on sending invites with a new state property. * Keep `RoomInviteMembersView` open until invites are sent * Sync strings from localazy * extend `ProgressDialog` to support custom content For my current design, a simple text element is insufficient. I extend `ProgressDialog` to give more flexibility over the content of the dialog. * Show progress dialog while invites are being sent * Add new ProgressDialog previews to the naming exceptions list * Update screenshots --------- Co-authored-by: ElementBot <android@element.io> Co-authored-by: Jorge Martín <jorgem@element.io>
This commit is contained in:
parent
06e7bc1604
commit
5f920eb100
22 changed files with 189 additions and 19 deletions
|
|
@ -23,9 +23,11 @@ import dev.zacsweers.metro.Inject
|
|||
import io.element.android.features.invitepeople.api.InvitePeopleEvents
|
||||
import io.element.android.features.invitepeople.api.InvitePeoplePresenter
|
||||
import io.element.android.features.invitepeople.api.InvitePeopleState
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.map
|
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||
import io.element.android.libraries.architecture.runUpdatingState
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
|
@ -73,6 +75,8 @@ class DefaultInvitePeoplePresenter(
|
|||
var searchQuery by rememberSaveable { mutableStateOf("") }
|
||||
var searchActive by rememberSaveable { mutableStateOf(false) }
|
||||
val showSearchLoader = rememberSaveable { mutableStateOf(false) }
|
||||
val sendInvitesAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
||||
|
||||
val room by produceState(if (joinedRoom != null) AsyncData.Success(joinedRoom) else AsyncData.Loading()) {
|
||||
if (joinedRoom == null) {
|
||||
val result = matrixClient.getJoinedRoom(roomId)
|
||||
|
|
@ -116,7 +120,7 @@ class DefaultInvitePeoplePresenter(
|
|||
}
|
||||
is InvitePeopleEvents.SendInvites -> {
|
||||
room.dataOrNull()?.let {
|
||||
sessionCoroutineScope.sendInvites(it, selectedUsers.value)
|
||||
sessionCoroutineScope.sendInvites(it, selectedUsers.value, sendInvitesAction)
|
||||
}
|
||||
}
|
||||
is InvitePeopleEvents.CloseSearch -> {
|
||||
|
|
@ -128,12 +132,13 @@ class DefaultInvitePeoplePresenter(
|
|||
|
||||
return DefaultInvitePeopleState(
|
||||
room = room.map { },
|
||||
canInvite = selectedUsers.value.isNotEmpty(),
|
||||
canInvite = selectedUsers.value.isNotEmpty() && !sendInvitesAction.value.isLoading(),
|
||||
selectedUsers = selectedUsers.value,
|
||||
searchQuery = searchQuery,
|
||||
isSearchActive = searchActive,
|
||||
searchResults = searchResults.value,
|
||||
showSearchLoader = showSearchLoader.value,
|
||||
sendInvitesAction = sendInvitesAction.value,
|
||||
eventSink = ::handleEvents,
|
||||
)
|
||||
}
|
||||
|
|
@ -141,16 +146,21 @@ class DefaultInvitePeoplePresenter(
|
|||
private fun CoroutineScope.sendInvites(
|
||||
room: JoinedRoom,
|
||||
selectedUsers: List<MatrixUser>,
|
||||
sendInvitesAction: MutableState<AsyncAction<Unit>>,
|
||||
) = launch {
|
||||
val anyInviteFailed = selectedUsers
|
||||
.map { room.inviteUserById(it.userId) }
|
||||
.any { it.isFailure }
|
||||
sendInvitesAction.runUpdatingState {
|
||||
val anyInviteFailed = selectedUsers
|
||||
.map { room.inviteUserById(it.userId) }
|
||||
.any { it.isFailure }
|
||||
|
||||
if (anyInviteFailed) {
|
||||
appErrorStateService.showError(
|
||||
titleRes = CommonStrings.common_unable_to_invite_title,
|
||||
bodyRes = CommonStrings.common_unable_to_invite_message,
|
||||
)
|
||||
if (anyInviteFailed) {
|
||||
appErrorStateService.showError(
|
||||
titleRes = CommonStrings.common_unable_to_invite_title,
|
||||
bodyRes = CommonStrings.common_unable_to_invite_message,
|
||||
)
|
||||
}
|
||||
|
||||
Result.success(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ package io.element.android.features.invitepeople.impl
|
|||
|
||||
import io.element.android.features.invitepeople.api.InvitePeopleEvents
|
||||
import io.element.android.features.invitepeople.api.InvitePeopleState
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
|
|
@ -22,5 +23,6 @@ data class DefaultInvitePeopleState(
|
|||
val searchResults: SearchBarResultState<ImmutableList<InvitableUser>>,
|
||||
val selectedUsers: ImmutableList<MatrixUser>,
|
||||
override val isSearchActive: Boolean,
|
||||
override val sendInvitesAction: AsyncAction<Unit>,
|
||||
override val eventSink: (InvitePeopleEvents) -> Unit
|
||||
) : InvitePeopleState
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package io.element.android.features.invitepeople.impl
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
|
|
@ -68,6 +69,11 @@ internal class DefaultInvitePeopleStateProvider : PreviewParameterProvider<Defau
|
|||
showSearchLoader = true,
|
||||
),
|
||||
aDefaultInvitePeopleState(room = AsyncData.Failure(Exception("Room not found"))),
|
||||
aDefaultInvitePeopleState(
|
||||
canInvite = false,
|
||||
selectedUsers = aMatrixUserList().toImmutableList(),
|
||||
sendInvitesAction = AsyncAction.Loading,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -93,6 +99,7 @@ private fun aDefaultInvitePeopleState(
|
|||
selectedUsers: ImmutableList<MatrixUser> = persistentListOf(),
|
||||
isSearchActive: Boolean = false,
|
||||
showSearchLoader: Boolean = false,
|
||||
sendInvitesAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
): DefaultInvitePeopleState {
|
||||
return DefaultInvitePeopleState(
|
||||
room = room,
|
||||
|
|
@ -102,6 +109,7 @@ private fun aDefaultInvitePeopleState(
|
|||
selectedUsers = selectedUsers,
|
||||
isSearchActive = isSearchActive,
|
||||
showSearchLoader = showSearchLoader,
|
||||
sendInvitesAction = sendInvitesAction,
|
||||
eventSink = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -409,10 +409,23 @@ internal class DefaultInvitePeoplePresenterTest {
|
|||
assertThat(resultState.searchResults).isInstanceOf(SearchBarResultState.Results::class.java)
|
||||
// Send invites
|
||||
initialState.eventSink(InvitePeopleEvents.SendInvites)
|
||||
|
||||
// Can't invite in the loading state
|
||||
awaitItem().run {
|
||||
assertThat(sendInvitesAction.isLoading()).isTrue()
|
||||
assertThat(canInvite).isFalse()
|
||||
}
|
||||
|
||||
delay(1_000)
|
||||
inviteUserResult.assertions().isCalledOnce().with(
|
||||
value(selectedUser.userId)
|
||||
)
|
||||
|
||||
// Can invite again once the action is finished
|
||||
awaitItem().run {
|
||||
assertThat(sendInvitesAction.isReady()).isTrue()
|
||||
assertThat(canInvite).isTrue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -445,6 +458,13 @@ internal class DefaultInvitePeoplePresenterTest {
|
|||
assertThat(resultState.searchResults).isInstanceOf(SearchBarResultState.Results::class.java)
|
||||
// Send invites
|
||||
initialState.eventSink(InvitePeopleEvents.SendInvites)
|
||||
|
||||
// Can't invite in the loading state
|
||||
awaitItem().run {
|
||||
assertThat(sendInvitesAction.isLoading()).isTrue()
|
||||
assertThat(canInvite).isFalse()
|
||||
}
|
||||
|
||||
delay(1_000)
|
||||
inviteUserResult.assertions().isCalledOnce().with(
|
||||
value(selectedUser.userId)
|
||||
|
|
@ -455,6 +475,12 @@ internal class DefaultInvitePeoplePresenterTest {
|
|||
value(CommonStrings.common_unable_to_invite_title),
|
||||
value(CommonStrings.common_unable_to_invite_message)
|
||||
)
|
||||
|
||||
// Can invite again once the action is finished
|
||||
awaitItem().run {
|
||||
assertThat(sendInvitesAction.isReady()).isTrue()
|
||||
assertThat(canInvite).isTrue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue