change(members): make sure state is not lost when navigating
This commit is contained in:
@@ -11,6 +11,7 @@ package io.element.android.features.roomdetails.impl.members
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
|
||||
sealed interface RoomMemberListEvents {
|
||||
data class ChangeSelectedSection(val section: SelectedSection): RoomMemberListEvents
|
||||
data class UpdateSearchQuery(val query: String) : RoomMemberListEvents
|
||||
data class OnSearchActiveChanged(val active: Boolean) : RoomMemberListEvents
|
||||
data class RoomMemberSelected(val roomMember: RoomMember) : RoomMemberListEvents
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
package io.element.android.features.roomdetails.impl.members
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.lifecycle.subscribe
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
@@ -21,6 +23,7 @@ import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.features.roommembermoderation.api.ModerationAction
|
||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
|
||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationRenderer
|
||||
import io.element.android.libraries.architecture.appyx.launchMolecule
|
||||
import io.element.android.libraries.architecture.callback
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
@@ -41,6 +44,7 @@ class RoomMemberListNode(
|
||||
}
|
||||
|
||||
private val callback: Callback = callback()
|
||||
private val stateFlow = launchMolecule { presenter.present() }
|
||||
|
||||
init {
|
||||
lifecycle.subscribe(
|
||||
@@ -64,7 +68,7 @@ class RoomMemberListNode(
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
val state by stateFlow.collectAsState()
|
||||
RoomMemberListView(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
|
||||
@@ -10,6 +10,7 @@ package io.element.android.features.roomdetails.impl.members
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -18,7 +19,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
|
||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents.ShowActionsForUser
|
||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
@@ -53,7 +54,7 @@ class RoomMemberListPresenter(
|
||||
private val roomMembersModerationPresenter: Presenter<RoomMemberModerationState>,
|
||||
private val encryptionService: EncryptionService,
|
||||
) : Presenter<RoomMemberListState> {
|
||||
private var roomMembers: AsyncData<RoomMembers> by mutableStateOf(AsyncData.Loading())
|
||||
|
||||
private val powerLevelRoomMemberComparator = PowerLevelRoomMemberComparator()
|
||||
|
||||
@Composable
|
||||
@@ -77,6 +78,9 @@ class RoomMemberListPresenter(
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
var roomMembers: AsyncData<RoomMembers> by remember { mutableStateOf(AsyncData.Loading())}
|
||||
var selectedSection by remember { mutableStateOf(SelectedSection.MEMBERS)}
|
||||
|
||||
// Update the room members when the screen is loaded
|
||||
LaunchedEffect(Unit) {
|
||||
room.updateMembers()
|
||||
@@ -160,7 +164,14 @@ class RoomMemberListPresenter(
|
||||
is RoomMemberListEvents.OnSearchActiveChanged -> isSearchActive = event.active
|
||||
is RoomMemberListEvents.UpdateSearchQuery -> searchQuery = event.query
|
||||
is RoomMemberListEvents.RoomMemberSelected ->
|
||||
roomModerationState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(event.roomMember.toMatrixUser()))
|
||||
roomModerationState.eventSink(ShowActionsForUser(event.roomMember.toMatrixUser()))
|
||||
is RoomMemberListEvents.ChangeSelectedSection -> selectedSection = event.section
|
||||
}
|
||||
}
|
||||
|
||||
if (!roomModerationState.canBan && selectedSection == SelectedSection.BANNED) {
|
||||
SideEffect {
|
||||
selectedSection = SelectedSection.MEMBERS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,6 +182,7 @@ class RoomMemberListPresenter(
|
||||
isSearchActive = isSearchActive,
|
||||
canInvite = canInvite,
|
||||
moderationState = roomModerationState,
|
||||
selectedSection = selectedSection,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,10 +21,16 @@ data class RoomMemberListState(
|
||||
val searchResults: SearchBarResultState<AsyncData<RoomMembers>>,
|
||||
val isSearchActive: Boolean,
|
||||
val canInvite: Boolean,
|
||||
val selectedSection: SelectedSection,
|
||||
val moderationState: RoomMemberModerationState,
|
||||
val eventSink: (RoomMemberListEvents) -> Unit,
|
||||
)
|
||||
|
||||
enum class SelectedSection {
|
||||
MEMBERS,
|
||||
BANNED
|
||||
}
|
||||
|
||||
data class RoomMembers(
|
||||
val invited: ImmutableList<RoomMemberWithIdentityState>,
|
||||
val joined: ImmutableList<RoomMemberWithIdentityState>,
|
||||
|
||||
@@ -21,107 +21,131 @@ import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMemberListState> {
|
||||
override val values: Sequence<RoomMemberListState>
|
||||
get() = sequenceOf(
|
||||
aRoomMemberListState(
|
||||
roomMembers = AsyncData.Success(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(aVictor().withIdentity(), aWalter().withIdentity()),
|
||||
joined = persistentListOf(anAlice().withIdentity(), aBob().withIdentity(), aWalter().withIdentity()),
|
||||
banned = persistentListOf(),
|
||||
)
|
||||
)
|
||||
),
|
||||
aRoomMemberListState(
|
||||
roomMembers = AsyncData.Success(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(aVictor().withIdentity(), aWalter().withIdentity()),
|
||||
joined = persistentListOf(
|
||||
anAlice().withIdentity(identityState = IdentityState.Verified),
|
||||
aBob().withIdentity(identityState = IdentityState.PinViolation),
|
||||
aWalter().withIdentity(identityState = IdentityState.VerificationViolation)
|
||||
),
|
||||
banned = persistentListOf(),
|
||||
)
|
||||
),
|
||||
moderationState = aRoomMemberModerationState(canBan = true)
|
||||
),
|
||||
aRoomMemberListState(roomMembers = AsyncData.Loading()),
|
||||
aRoomMemberListState().copy(canInvite = true),
|
||||
aRoomMemberListState().copy(isSearchActive = false),
|
||||
aRoomMemberListState().copy(isSearchActive = true),
|
||||
aRoomMemberListState().copy(isSearchActive = true, searchQuery = "someone"),
|
||||
aRoomMemberListState().copy(
|
||||
isSearchActive = true,
|
||||
searchQuery = "@someone:matrix.org",
|
||||
searchResults = SearchBarResultState.Results(
|
||||
AsyncData.Success(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(aVictor().withIdentity()),
|
||||
joined = persistentListOf(anAlice().withIdentity()),
|
||||
banned = persistentListOf(),
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
aRoomMemberListState().copy(
|
||||
isSearchActive = true,
|
||||
searchQuery = "something-with-no-results",
|
||||
searchResults = SearchBarResultState.NoResultsFound()
|
||||
),
|
||||
aRoomMemberListState(
|
||||
roomMembers = AsyncData.Failure(Exception("Error details")),
|
||||
),
|
||||
)
|
||||
get() = roomMemberListStates() + bannedRoomMemberListStates()
|
||||
}
|
||||
|
||||
internal class RoomMemberListStateBannedProvider : PreviewParameterProvider<RoomMemberListState> {
|
||||
override val values: Sequence<RoomMemberListState>
|
||||
get() = sequenceOf(
|
||||
aRoomMemberListState(
|
||||
roomMembers = AsyncData.Success(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(),
|
||||
joined = persistentListOf(),
|
||||
banned = persistentListOf(
|
||||
aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice").withIdentity(),
|
||||
aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob").withIdentity(),
|
||||
aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie").withIdentity(),
|
||||
),
|
||||
)
|
||||
),
|
||||
moderationState = aRoomMemberModerationState(),
|
||||
private fun roomMemberListStates(): Sequence<RoomMemberListState> = sequenceOf(
|
||||
aRoomMemberListState(
|
||||
roomMembers = AsyncData.Success(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(aVictor().withIdentity(), aWalter().withIdentity()),
|
||||
joined = persistentListOf(anAlice().withIdentity(), aBob().withIdentity(), aWalter().withIdentity()),
|
||||
banned = persistentListOf(),
|
||||
),
|
||||
aRoomMemberListState(
|
||||
roomMembers = AsyncData.Loading(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(),
|
||||
joined = persistentListOf(),
|
||||
banned = persistentListOf(
|
||||
aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice").withIdentity(),
|
||||
aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob").withIdentity(),
|
||||
aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie").withIdentity(),
|
||||
),
|
||||
)
|
||||
),
|
||||
selectedSection = SelectedSection.MEMBERS,
|
||||
),
|
||||
aRoomMemberListState(
|
||||
roomMembers = AsyncData.Success(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(aVictor().withIdentity(), aWalter().withIdentity()),
|
||||
joined = persistentListOf(
|
||||
anAlice().withIdentity(identityState = IdentityState.Verified),
|
||||
aBob().withIdentity(identityState = IdentityState.PinViolation),
|
||||
aWalter().withIdentity(identityState = IdentityState.VerificationViolation)
|
||||
),
|
||||
moderationState = aRoomMemberModerationState(),
|
||||
),
|
||||
aRoomMemberListState(
|
||||
roomMembers = AsyncData.Success(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(),
|
||||
joined = persistentListOf(),
|
||||
banned = persistentListOf(),
|
||||
)
|
||||
),
|
||||
moderationState = aRoomMemberModerationState(),
|
||||
banned = persistentListOf(),
|
||||
)
|
||||
)
|
||||
}
|
||||
),
|
||||
selectedSection = SelectedSection.MEMBERS,
|
||||
moderationState = aRoomMemberModerationState(canBan = true)
|
||||
),
|
||||
aRoomMemberListState(
|
||||
roomMembers = AsyncData.Loading(),
|
||||
selectedSection = SelectedSection.MEMBERS,
|
||||
),
|
||||
aRoomMemberListState().copy(
|
||||
canInvite = true,
|
||||
selectedSection = SelectedSection.MEMBERS,
|
||||
),
|
||||
aRoomMemberListState().copy(
|
||||
isSearchActive = false,
|
||||
selectedSection = SelectedSection.MEMBERS,
|
||||
),
|
||||
aRoomMemberListState().copy(
|
||||
isSearchActive = true,
|
||||
selectedSection = SelectedSection.MEMBERS,
|
||||
),
|
||||
aRoomMemberListState().copy(
|
||||
isSearchActive = true,
|
||||
searchQuery = "someone",
|
||||
selectedSection = SelectedSection.MEMBERS,
|
||||
),
|
||||
aRoomMemberListState().copy(
|
||||
isSearchActive = true,
|
||||
searchQuery = "@someone:matrix.org",
|
||||
searchResults = SearchBarResultState.Results(
|
||||
AsyncData.Success(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(aVictor().withIdentity()),
|
||||
joined = persistentListOf(anAlice().withIdentity()),
|
||||
banned = persistentListOf(),
|
||||
)
|
||||
)
|
||||
),
|
||||
selectedSection = SelectedSection.MEMBERS,
|
||||
),
|
||||
aRoomMemberListState().copy(
|
||||
isSearchActive = true,
|
||||
searchQuery = "something-with-no-results",
|
||||
searchResults = SearchBarResultState.NoResultsFound(),
|
||||
selectedSection = SelectedSection.MEMBERS,
|
||||
),
|
||||
aRoomMemberListState(
|
||||
roomMembers = AsyncData.Failure(Exception("Error details")),
|
||||
selectedSection = SelectedSection.MEMBERS,
|
||||
),
|
||||
)
|
||||
|
||||
private fun bannedRoomMemberListStates(): Sequence<RoomMemberListState> = sequenceOf(
|
||||
aRoomMemberListState(
|
||||
roomMembers = AsyncData.Success(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(),
|
||||
joined = persistentListOf(),
|
||||
banned = persistentListOf(
|
||||
aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice").withIdentity(),
|
||||
aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob").withIdentity(),
|
||||
aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie").withIdentity(),
|
||||
),
|
||||
)
|
||||
),
|
||||
moderationState = aRoomMemberModerationState(),
|
||||
selectedSection = SelectedSection.BANNED,
|
||||
),
|
||||
aRoomMemberListState(
|
||||
roomMembers = AsyncData.Loading(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(),
|
||||
joined = persistentListOf(),
|
||||
banned = persistentListOf(
|
||||
aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice").withIdentity(),
|
||||
aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob").withIdentity(),
|
||||
aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie").withIdentity(),
|
||||
),
|
||||
)
|
||||
),
|
||||
moderationState = aRoomMemberModerationState(),
|
||||
selectedSection = SelectedSection.BANNED,
|
||||
),
|
||||
aRoomMemberListState(
|
||||
roomMembers = AsyncData.Success(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(),
|
||||
joined = persistentListOf(),
|
||||
banned = persistentListOf(),
|
||||
)
|
||||
),
|
||||
moderationState = aRoomMemberModerationState(),
|
||||
selectedSection = SelectedSection.BANNED,
|
||||
)
|
||||
)
|
||||
|
||||
internal fun aRoomMemberListState(
|
||||
roomMembers: AsyncData<RoomMembers> = AsyncData.Loading(),
|
||||
searchResults: SearchBarResultState<AsyncData<RoomMembers>> = SearchBarResultState.Initial(),
|
||||
moderationState: RoomMemberModerationState = aRoomMemberModerationState(),
|
||||
selectedSection: SelectedSection = SelectedSection.MEMBERS,
|
||||
) = RoomMemberListState(
|
||||
roomMembers = roomMembers,
|
||||
searchQuery = "",
|
||||
@@ -129,6 +153,7 @@ internal fun aRoomMemberListState(
|
||||
isSearchActive = false,
|
||||
canInvite = false,
|
||||
moderationState = moderationState,
|
||||
selectedSection = SelectedSection.MEMBERS,
|
||||
eventSink = {}
|
||||
)
|
||||
|
||||
|
||||
@@ -30,10 +30,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -48,6 +45,7 @@ import io.element.android.features.roomdetails.impl.R
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.form.textFieldState
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
@@ -68,17 +66,11 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
private enum class SelectedSection {
|
||||
MEMBERS,
|
||||
BANNED
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RoomMemberListView(
|
||||
state: RoomMemberListState,
|
||||
navigator: RoomMemberListNavigator,
|
||||
modifier: Modifier = Modifier,
|
||||
initialSelectedSectionIndex: Int = 0,
|
||||
) {
|
||||
fun onSelectUser(roomMember: RoomMember) {
|
||||
state.eventSink(RoomMemberListEvents.RoomMemberSelected(roomMember))
|
||||
@@ -96,12 +88,6 @@ fun RoomMemberListView(
|
||||
}
|
||||
}
|
||||
) { padding ->
|
||||
var selectedSection by remember { mutableStateOf(SelectedSection.entries[initialSelectedSectionIndex]) }
|
||||
if (!state.moderationState.canBan && selectedSection == SelectedSection.BANNED) {
|
||||
SideEffect {
|
||||
selectedSection = SelectedSection.MEMBERS
|
||||
}
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -117,7 +103,7 @@ fun RoomMemberListView(
|
||||
onActiveChange = { state.eventSink(RoomMemberListEvents.OnSearchActiveChanged(it)) },
|
||||
onTextChange = { state.eventSink(RoomMemberListEvents.UpdateSearchQuery(it)) },
|
||||
onSelectUser = ::onSelectUser,
|
||||
selectedSection = selectedSection,
|
||||
selectedSection = state.selectedSection,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
@@ -126,8 +112,8 @@ fun RoomMemberListView(
|
||||
roomMembers = state.roomMembers,
|
||||
showMembersCount = true,
|
||||
canDisplayBannedUsersControls = state.moderationState.canBan,
|
||||
selectedSection = selectedSection,
|
||||
onSelectedSectionChange = { selectedSection = it },
|
||||
selectedSection = state.selectedSection,
|
||||
onSelectedSectionChange = { state.eventSink(RoomMemberListEvents.ChangeSelectedSection(it)) },
|
||||
onSelectUser = ::onSelectUser,
|
||||
)
|
||||
}
|
||||
@@ -380,9 +366,13 @@ private fun RoomMemberSearchBar(
|
||||
selectedSection: SelectedSection,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var queryFieldState by textFieldState(query)
|
||||
SearchBar(
|
||||
query = query,
|
||||
onQueryChange = onTextChange,
|
||||
query = queryFieldState,
|
||||
onQueryChange = { newQuery ->
|
||||
queryFieldState = newQuery
|
||||
onTextChange(newQuery)
|
||||
},
|
||||
active = active,
|
||||
onActiveChange = onActiveChange,
|
||||
modifier = modifier,
|
||||
@@ -409,13 +399,3 @@ internal fun RoomMemberListViewPreview(@PreviewParameter(RoomMemberListStateProv
|
||||
navigator = object : RoomMemberListNavigator {},
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun RoomMemberListViewBannedPreview(@PreviewParameter(RoomMemberListStateBannedProvider::class) state: RoomMemberListState) = ElementPreview {
|
||||
RoomMemberListView(
|
||||
initialSelectedSectionIndex = 1,
|
||||
state = state,
|
||||
navigator = object : RoomMemberListNavigator {},
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user