feature (space) : manage failures to join in Space screen
This commit is contained in:
@@ -12,4 +12,5 @@ import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
sealed interface SpaceEvents {
|
||||
data object LoadMore : SpaceEvents
|
||||
data class Join(val spaceRoom: SpaceRoom): SpaceEvents
|
||||
data object ClearFailures : SpaceEvents
|
||||
}
|
||||
|
||||
@@ -17,10 +17,11 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedFactory
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import io.element.android.features.invite.api.SeenInvitesStore
|
||||
import io.element.android.features.space.api.SpaceEntryPoint
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.mapState
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
@@ -34,14 +35,14 @@ import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
|
||||
import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.collections.immutable.toPersistentMap
|
||||
import kotlinx.collections.immutable.toPersistentSet
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
@AssistedInject
|
||||
class SpacePresenter(
|
||||
@AssistedInject class SpacePresenter(
|
||||
@Assisted private val inputs: SpaceEntryPoint.Inputs,
|
||||
private val client: MatrixClient,
|
||||
private val seenInvitesStore: SeenInvitesStore,
|
||||
@@ -76,15 +77,12 @@ class SpacePresenter(
|
||||
}.collectAsState()
|
||||
|
||||
val currentSpace by spaceRoomList.currentSpaceFlow.collectAsState()
|
||||
val joiningRooms = remember { mutableStateOf(emptySet<RoomId>()) }
|
||||
val joinActions = remember { mutableStateOf(emptyMap<RoomId, AsyncAction<Unit>>()) }
|
||||
|
||||
LaunchedEffect(children, joiningRooms.value) {
|
||||
val joinedChildren = children
|
||||
.filter { it.state == CurrentUserMembership.JOINED }
|
||||
.map { it.roomId }
|
||||
.toSet()
|
||||
joiningRooms.value.let { currentlyJoining ->
|
||||
joiningRooms.value = currentlyJoining - joinedChildren
|
||||
LaunchedEffect(children) {
|
||||
val joinedChildren = children.filter { it.state == CurrentUserMembership.JOINED }.map { it.roomId }.toSet()
|
||||
joinActions.value.let { currentlyJoining ->
|
||||
joinActions.value = currentlyJoining - joinedChildren
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +90,13 @@ class SpacePresenter(
|
||||
when (event) {
|
||||
SpaceEvents.LoadMore -> localCoroutineScope.paginate()
|
||||
is SpaceEvents.Join -> {
|
||||
sessionCoroutineScope.joinRoom(event.spaceRoom, joiningRooms)
|
||||
sessionCoroutineScope.joinRoom(event.spaceRoom, joinActions)
|
||||
}
|
||||
SpaceEvents.ClearFailures -> {
|
||||
val failedActions = joinActions.value
|
||||
.filterValues { it is AsyncAction.Failure }
|
||||
.mapValues { AsyncAction.Uninitialized }
|
||||
joinActions.value = joinActions.value + failedActions
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,21 +106,21 @@ class SpacePresenter(
|
||||
seenSpaceInvites = seenSpaceInvites,
|
||||
hideInvitesAvatar = hideInvitesAvatar,
|
||||
hasMoreToLoad = hasMoreToLoad,
|
||||
joiningRooms = joiningRooms.value.toPersistentSet(),
|
||||
joinActions = joinActions.value.toPersistentMap(),
|
||||
eventSink = ::handleEvents,
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.joinRoom(
|
||||
spaceRoom: SpaceRoom, joiningRooms: MutableState<Set<RoomId>>
|
||||
spaceRoom: SpaceRoom, joiningRooms: MutableState<Map<RoomId, AsyncAction<Unit>>>
|
||||
) = launch {
|
||||
joiningRooms.value = joiningRooms.value + spaceRoom.roomId
|
||||
joiningRooms.value = joiningRooms.value + mapOf(spaceRoom.roomId to AsyncAction.Loading)
|
||||
joinRoom.invoke(
|
||||
roomIdOrAlias = spaceRoom.roomId.toRoomIdOrAlias(),
|
||||
serverNames = spaceRoom.via,
|
||||
trigger = JoinedRoom.Trigger.SpaceHierarchy,
|
||||
).onFailure {
|
||||
joiningRooms.value = joiningRooms.value - spaceRoom.roomId
|
||||
joiningRooms.value = joiningRooms.value + mapOf(spaceRoom.roomId to AsyncAction.Failure(it))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,11 @@
|
||||
|
||||
package io.element.android.features.space.impl.root
|
||||
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
|
||||
data class SpaceState(
|
||||
@@ -18,6 +20,11 @@ data class SpaceState(
|
||||
val seenSpaceInvites: ImmutableSet<RoomId>,
|
||||
val hideInvitesAvatar: Boolean,
|
||||
val hasMoreToLoad: Boolean,
|
||||
val joiningRooms: ImmutableSet<RoomId>,
|
||||
val joinActions: ImmutableMap<RoomId, AsyncAction<Unit>>,
|
||||
val eventSink: (SpaceEvents) -> Unit
|
||||
)
|
||||
) {
|
||||
fun isJoining(spaceId: RoomId): Boolean = joinActions[spaceId] == AsyncAction.Loading
|
||||
val hasAnyFailure: Boolean = joinActions.values.any {
|
||||
it is AsyncAction.Failure
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,16 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.space.impl
|
||||
package io.element.android.features.space.impl.root
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import io.element.android.libraries.previewutils.room.aSpaceRoom
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
|
||||
open class SpaceStateProvider : PreviewParameterProvider<SpaceState> {
|
||||
@@ -56,7 +58,7 @@ fun aSpaceState(
|
||||
seenSpaceInvites = seenSpaceInvites.toImmutableSet(),
|
||||
hideInvitesAvatar = hideInvitesAvatar,
|
||||
hasMoreToLoad = hasMoreToLoad,
|
||||
joiningRooms = joiningRooms.toImmutableSet(),
|
||||
joinActions = joiningRooms.associateWith { AsyncAction.Uninitialized }.toImmutableMap(),
|
||||
eventSink = {})
|
||||
|
||||
private fun aListOfSpaceRooms(): List<SpaceRoom> {
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
@@ -34,6 +35,9 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncIndicator
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost
|
||||
import io.element.android.libraries.designsystem.components.async.rememberAsyncIndicatorState
|
||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
@@ -58,22 +62,22 @@ import io.element.android.libraries.matrix.ui.components.SpaceRoomItemView
|
||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@Composable
|
||||
fun SpaceView(
|
||||
state: SpaceState,
|
||||
onBackClick: () -> Unit,
|
||||
onLeaveSpaceClick: () -> Unit,
|
||||
onRoomClick: (spaceRoom: SpaceRoom) -> Unit,
|
||||
onShareSpace: () -> Unit,
|
||||
onLeaveSpaceClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
SpaceViewTopBar(
|
||||
state = state,
|
||||
onBackClick = onBackClick,
|
||||
currentSpace = state.currentSpace, onBackClick = onBackClick,
|
||||
onLeaveSpaceClick = onLeaveSpaceClick,
|
||||
onShareSpace = onShareSpace,
|
||||
)
|
||||
@@ -89,6 +93,30 @@ fun SpaceView(
|
||||
}
|
||||
},
|
||||
)
|
||||
JoinRoomFailureEffect(
|
||||
hasAnyFailure = state.hasAnyFailure,
|
||||
eventSink = state.eventSink
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun JoinRoomFailureEffect(
|
||||
hasAnyFailure: Boolean,
|
||||
eventSink: (SpaceEvents) -> Unit,
|
||||
) {
|
||||
val asyncIndicatorState = rememberAsyncIndicatorState()
|
||||
AsyncIndicatorHost(modifier = Modifier.statusBarsPadding(), asyncIndicatorState)
|
||||
LaunchedEffect(hasAnyFailure) {
|
||||
if (hasAnyFailure) {
|
||||
asyncIndicatorState.enqueue {
|
||||
AsyncIndicator.Failure(text = stringResource(CommonStrings.common_something_went_wrong))
|
||||
}
|
||||
delay(AsyncIndicator.DURATION_SHORT)
|
||||
eventSink(SpaceEvents.ClearFailures)
|
||||
} else {
|
||||
asyncIndicatorState.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -115,7 +143,7 @@ private fun SpaceViewContent(
|
||||
state.children.forEach { spaceRoom ->
|
||||
item {
|
||||
val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED
|
||||
val isCurrentlyJoining = spaceRoom.roomId in state.joiningRooms
|
||||
val isCurrentlyJoining = state.isJoining(spaceRoom.roomId)
|
||||
SpaceRoomItemView(
|
||||
spaceRoom = spaceRoom,
|
||||
showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites,
|
||||
@@ -163,13 +191,12 @@ private fun LoadingMoreIndicator(
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun SpaceViewTopBar(
|
||||
state: SpaceState,
|
||||
currentSpace: SpaceRoom?,
|
||||
onBackClick: () -> Unit,
|
||||
@Suppress("unused") onLeaveSpaceClick: () -> Unit,
|
||||
onShareSpace: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val currentSpace = state.currentSpace
|
||||
TopAppBar(
|
||||
modifier = modifier,
|
||||
navigationIcon = {
|
||||
@@ -229,6 +256,7 @@ private fun SpaceViewTopBar(
|
||||
)
|
||||
*/
|
||||
}
|
||||
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -290,9 +318,9 @@ internal fun SpaceViewPreview(
|
||||
) = ElementPreview {
|
||||
SpaceView(
|
||||
state = state,
|
||||
onBackClick = {},
|
||||
onLeaveSpaceClick = {},
|
||||
onRoomClick = {},
|
||||
onShareSpace = {},
|
||||
onLeaveSpaceClick = {},
|
||||
onBackClick = {},
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user