mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 06:55:41 +00:00
Replace the SearchBar by a SearchField in the room select screen
The room select screen was using a SearchBar, with an active mode displaying the search results instead of the room list. Use a SearchField instead, displayed above the room list, which filters the rooms directly. The account switch section and the selected rooms are now also displayed while searching. - Remove isSearchActive from the state, and the ToggleSearchActive event. - The "No results" message is displayed when the query is not blank and no room matches it. - The back button now always closes the screen. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
-1
@@ -13,7 +13,6 @@ import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
|
|||||||
|
|
||||||
sealed interface RoomSelectEvent {
|
sealed interface RoomSelectEvent {
|
||||||
data class ToggleSelectedRoom(val room: SelectRoomInfo) : RoomSelectEvent
|
data class ToggleSelectedRoom(val room: SelectRoomInfo) : RoomSelectEvent
|
||||||
data object ToggleSearchActive : RoomSelectEvent
|
|
||||||
data class UpdateVisibleRange(val range: IntRange) : RoomSelectEvent
|
data class UpdateVisibleRange(val range: IntRange) : RoomSelectEvent
|
||||||
data object ToggleAccountListExpanded : RoomSelectEvent
|
data object ToggleAccountListExpanded : RoomSelectEvent
|
||||||
data class SelectAccount(val sessionId: SessionId) : RoomSelectEvent
|
data class SelectAccount(val sessionId: SessionId) : RoomSelectEvent
|
||||||
|
|||||||
+1
-4
@@ -63,7 +63,6 @@ class RoomSelectPresenter(
|
|||||||
override fun present(): RoomSelectState {
|
override fun present(): RoomSelectState {
|
||||||
var selectedRooms by remember { mutableStateOf(persistentListOf<SelectRoomInfo>()) }
|
var selectedRooms by remember { mutableStateOf(persistentListOf<SelectRoomInfo>()) }
|
||||||
val queryState = rememberTextFieldState()
|
val queryState = rememberTextFieldState()
|
||||||
var isSearchActive by remember { mutableStateOf(false) }
|
|
||||||
var isAccountListExpanded by remember { mutableStateOf(false) }
|
var isAccountListExpanded by remember { mutableStateOf(false) }
|
||||||
var selectedSessionId by remember { mutableStateOf(initialSessionId) }
|
var selectedSessionId by remember { mutableStateOf(initialSessionId) }
|
||||||
val sessions by remember { sessionStore.sessionsFlow() }.collectAsState(initial = emptyList())
|
val sessions by remember { sessionStore.sessionsFlow() }.collectAsState(initial = emptyList())
|
||||||
@@ -136,7 +135,7 @@ class RoomSelectPresenter(
|
|||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
when {
|
when {
|
||||||
roomSummaryDetailsList.isNotEmpty() -> SearchBarResultState.Results(roomSummaryDetailsList.toImmutableList())
|
roomSummaryDetailsList.isNotEmpty() -> SearchBarResultState.Results(roomSummaryDetailsList.toImmutableList())
|
||||||
isSearchActive -> SearchBarResultState.NoResultsFound
|
queryState.text.isNotBlank() -> SearchBarResultState.NoResultsFound
|
||||||
else -> SearchBarResultState.Initial
|
else -> SearchBarResultState.Initial
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,7 +151,6 @@ class RoomSelectPresenter(
|
|||||||
selectedRooms.adding(event.room)
|
selectedRooms.adding(event.room)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RoomSelectEvent.ToggleSearchActive -> isSearchActive = !isSearchActive
|
|
||||||
RoomSelectEvent.ToggleAccountListExpanded -> isAccountListExpanded = !isAccountListExpanded
|
RoomSelectEvent.ToggleAccountListExpanded -> isAccountListExpanded = !isAccountListExpanded
|
||||||
is RoomSelectEvent.SelectAccount -> {
|
is RoomSelectEvent.SelectAccount -> {
|
||||||
selectedSessionId = event.sessionId
|
selectedSessionId = event.sessionId
|
||||||
@@ -172,7 +170,6 @@ class RoomSelectPresenter(
|
|||||||
maxNumberOfRooms = maxNumberOfRooms,
|
maxNumberOfRooms = maxNumberOfRooms,
|
||||||
resultState = searchResults,
|
resultState = searchResults,
|
||||||
searchQuery = queryState,
|
searchQuery = queryState,
|
||||||
isSearchActive = isSearchActive,
|
|
||||||
selectedRooms = selectedRooms,
|
selectedRooms = selectedRooms,
|
||||||
selectedAccount = selectedAccount,
|
selectedAccount = selectedAccount,
|
||||||
hasRoomListError = hasRoomListError,
|
hasRoomListError = hasRoomListError,
|
||||||
|
|||||||
-1
@@ -20,7 +20,6 @@ data class RoomSelectState(
|
|||||||
val maxNumberOfRooms: Int,
|
val maxNumberOfRooms: Int,
|
||||||
val resultState: SearchBarResultState<ImmutableList<SelectRoomInfo>>,
|
val resultState: SearchBarResultState<ImmutableList<SelectRoomInfo>>,
|
||||||
val searchQuery: TextFieldState,
|
val searchQuery: TextFieldState,
|
||||||
val isSearchActive: Boolean,
|
|
||||||
val selectedRooms: ImmutableList<SelectRoomInfo>,
|
val selectedRooms: ImmutableList<SelectRoomInfo>,
|
||||||
val selectedAccount: MatrixUser,
|
val selectedAccount: MatrixUser,
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-5
@@ -26,17 +26,15 @@ open class RoomSelectStatePreviewParam : PreviewParameterProvider<RoomSelectStat
|
|||||||
override val values: Sequence<RoomSelectState>
|
override val values: Sequence<RoomSelectState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aRoomSelectState(),
|
aRoomSelectState(),
|
||||||
aRoomSelectState(searchQuery = "Test", isSearchActive = true),
|
aRoomSelectState(searchQuery = "Test", resultState = SearchBarResultState.NoResultsFound),
|
||||||
aRoomSelectState(resultState = SearchBarResultState.Results(aRoomSelectRoomList())),
|
aRoomSelectState(resultState = SearchBarResultState.Results(aRoomSelectRoomList())),
|
||||||
aRoomSelectState(
|
aRoomSelectState(
|
||||||
resultState = SearchBarResultState.Results(aRoomSelectRoomList()),
|
resultState = SearchBarResultState.Results(aRoomSelectRoomList()),
|
||||||
searchQuery = "Test",
|
searchQuery = "Test",
|
||||||
isSearchActive = true,
|
|
||||||
),
|
),
|
||||||
aRoomSelectState(
|
aRoomSelectState(
|
||||||
resultState = SearchBarResultState.Results(aRoomSelectRoomList()),
|
resultState = SearchBarResultState.Results(aRoomSelectRoomList()),
|
||||||
searchQuery = "Test",
|
searchQuery = "Test",
|
||||||
isSearchActive = true,
|
|
||||||
selectedRooms = aRoomSelectRoomList().subList(0, 1),
|
selectedRooms = aRoomSelectRoomList().subList(0, 1),
|
||||||
),
|
),
|
||||||
aRoomSelectState(
|
aRoomSelectState(
|
||||||
@@ -76,7 +74,6 @@ internal fun aRoomSelectState(
|
|||||||
maxNumberOfRooms: Int = 10,
|
maxNumberOfRooms: Int = 10,
|
||||||
resultState: SearchBarResultState<ImmutableList<SelectRoomInfo>> = SearchBarResultState.Initial,
|
resultState: SearchBarResultState<ImmutableList<SelectRoomInfo>> = SearchBarResultState.Initial,
|
||||||
searchQuery: String = "",
|
searchQuery: String = "",
|
||||||
isSearchActive: Boolean = false,
|
|
||||||
selectedRooms: ImmutableList<SelectRoomInfo> = persistentListOf(),
|
selectedRooms: ImmutableList<SelectRoomInfo> = persistentListOf(),
|
||||||
selectedAccount: MatrixUser = MatrixUser(UserId("@alice:example.org")),
|
selectedAccount: MatrixUser = MatrixUser(UserId("@alice:example.org")),
|
||||||
hasRoomListError: Boolean = false,
|
hasRoomListError: Boolean = false,
|
||||||
@@ -88,7 +85,6 @@ internal fun aRoomSelectState(
|
|||||||
maxNumberOfRooms = maxNumberOfRooms,
|
maxNumberOfRooms = maxNumberOfRooms,
|
||||||
resultState = resultState,
|
resultState = resultState,
|
||||||
searchQuery = TextFieldState(initialText = searchQuery),
|
searchQuery = TextFieldState(initialText = searchQuery),
|
||||||
isSearchActive = isSearchActive,
|
|
||||||
selectedRooms = selectedRooms,
|
selectedRooms = selectedRooms,
|
||||||
selectedAccount = selectedAccount,
|
selectedAccount = selectedAccount,
|
||||||
hasRoomListError = hasRoomListError,
|
hasRoomListError = hasRoomListError,
|
||||||
|
|||||||
+46
-68
@@ -49,6 +49,7 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.semantics.semantics
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.semantics.stateDescription
|
import androidx.compose.ui.semantics.stateDescription
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -70,8 +71,8 @@ import io.element.android.libraries.designsystem.theme.components.Icon
|
|||||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||||
import io.element.android.libraries.designsystem.theme.components.ListSectionHeader
|
import io.element.android.libraries.designsystem.theme.components.ListSectionHeader
|
||||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBar
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.SearchField
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
@@ -123,10 +124,8 @@ fun RoomSelectView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var canHandleBack by remember { mutableStateOf(true) }
|
var canHandleBack by remember { mutableStateOf(true) }
|
||||||
fun onBackButton(state: RoomSelectState) {
|
fun onBackButton() {
|
||||||
if (state.isSearchActive) {
|
if (canHandleBack) {
|
||||||
state.eventSink(RoomSelectEvent.ToggleSearchActive)
|
|
||||||
} else if (canHandleBack) {
|
|
||||||
canHandleBack = false
|
canHandleBack = false
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
@@ -134,7 +133,7 @@ fun RoomSelectView(
|
|||||||
|
|
||||||
BackHandler(
|
BackHandler(
|
||||||
enabled = canHandleBack,
|
enabled = canHandleBack,
|
||||||
onBack = { onBackButton(state) }
|
onBack = ::onBackButton,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Use a new list state when the selected account changes, so that the list of rooms is scrolled to the top
|
// Use a new list state when the selected account changes, so that the list of rooms is scrolled to the top
|
||||||
@@ -159,7 +158,7 @@ fun RoomSelectView(
|
|||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
BackButton(
|
BackButton(
|
||||||
enabled = canHandleBack,
|
enabled = canHandleBack,
|
||||||
onClick = { onBackButton(state) }
|
onClick = ::onBackButton,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
@@ -179,81 +178,47 @@ fun RoomSelectView(
|
|||||||
.consumeWindowInsets(paddingValues)
|
.consumeWindowInsets(paddingValues)
|
||||||
.then(
|
.then(
|
||||||
// Only when the account switch section is displayed, else its unknown height would consume all the scroll events
|
// Only when the account switch section is displayed, else its unknown height would consume all the scroll events
|
||||||
if (state.showAccountSwitch && !state.isSearchActive) {
|
if (state.showAccountSwitch) {
|
||||||
Modifier.nestedScroll(accountSwitchScrollBehavior.nestedScrollConnection)
|
Modifier.nestedScroll(accountSwitchScrollBehavior.nestedScrollConnection)
|
||||||
} else {
|
} else {
|
||||||
Modifier
|
Modifier
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
SearchBar(
|
SearchField(
|
||||||
|
state = state.searchQuery,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(
|
.padding(
|
||||||
WindowInsets.safeDrawing
|
WindowInsets.safeDrawing
|
||||||
.only(WindowInsetsSides.Horizontal)
|
.only(WindowInsetsSides.Horizontal)
|
||||||
.asPaddingValues()
|
.asPaddingValues()
|
||||||
),
|
)
|
||||||
placeHolderTitle = stringResource(CommonStrings.action_search),
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
queryState = state.searchQuery,
|
placeholder = stringResource(CommonStrings.action_search),
|
||||||
active = state.isSearchActive,
|
)
|
||||||
onActiveChange = { state.eventSink(RoomSelectEvent.ToggleSearchActive) },
|
if (state.showAccountSwitch) {
|
||||||
resultState = state.resultState,
|
// Keep this space outside the collapsing section, so that the section does not touch the search field when it is collapsing
|
||||||
showBackButton = false,
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
) { summaries ->
|
TopAppBarScrollBehaviorLayout(scrollBehavior = accountSwitchScrollBehavior) {
|
||||||
LazyColumn(
|
AccountSwitchSection(
|
||||||
state = lazyListState,
|
selectedAccount = state.selectedAccount,
|
||||||
contentPadding = lazyColumnContentPadding,
|
otherAccounts = state.otherAccounts,
|
||||||
) {
|
isExpanded = state.isAccountListExpanded,
|
||||||
if (summaries.isNotEmpty()) {
|
onToggleExpand = { state.eventSink(RoomSelectEvent.ToggleAccountListExpanded) },
|
||||||
item {
|
onSelectAccount = { state.eventSink(RoomSelectEvent.SelectAccount(it)) },
|
||||||
ListSectionHeader(
|
)
|
||||||
title = stringResource(CommonStrings.common_header_rooms),
|
|
||||||
hasDivider = false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
SelectedRoomsHelper(
|
|
||||||
selectedRooms = state.selectedRooms,
|
|
||||||
showVerticalSpace = false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
items(summaries, key = { it.roomId.value }) { roomSummary ->
|
|
||||||
RoomSummaryView(
|
|
||||||
roomSummary,
|
|
||||||
isSelected = state.selectedRooms.any { it.roomId == roomSummary.roomId },
|
|
||||||
onSelection = { roomSummary ->
|
|
||||||
state.eventSink(RoomSelectEvent.ToggleSelectedRoom(roomSummary))
|
|
||||||
},
|
|
||||||
canBeSelected = state.canSelectMoreRooms,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SelectedRoomsHelper(
|
||||||
if (!state.isSearchActive) {
|
selectedRooms = state.selectedRooms,
|
||||||
if (state.showAccountSwitch) {
|
// showVerticalSpace only if there is no other accounts
|
||||||
// Keep this space outside the collapsing section, so that the section does not touch the search bar when it is collapsing
|
showVerticalSpace = !state.showAccountSwitch,
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
)
|
||||||
TopAppBarScrollBehaviorLayout(scrollBehavior = accountSwitchScrollBehavior) {
|
when {
|
||||||
AccountSwitchSection(
|
state.hasRoomListError -> RoomListError()
|
||||||
selectedAccount = state.selectedAccount,
|
state.resultState is SearchBarResultState.NoResultsFound -> NoResultsFound()
|
||||||
otherAccounts = state.otherAccounts,
|
state.resultState is SearchBarResultState.Results -> {
|
||||||
isExpanded = state.isAccountListExpanded,
|
|
||||||
onToggleExpand = { state.eventSink(RoomSelectEvent.ToggleAccountListExpanded) },
|
|
||||||
onSelectAccount = { state.eventSink(RoomSelectEvent.SelectAccount(it)) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SelectedRoomsHelper(
|
|
||||||
selectedRooms = state.selectedRooms,
|
|
||||||
// showVerticalSpace only if there is no other accounts
|
|
||||||
showVerticalSpace = !state.showAccountSwitch,
|
|
||||||
)
|
|
||||||
if (state.hasRoomListError) {
|
|
||||||
RoomListError()
|
|
||||||
} else if (state.resultState is SearchBarResultState.Results) {
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = lazyListState,
|
state = lazyListState,
|
||||||
contentPadding = lazyColumnContentPadding,
|
contentPadding = lazyColumnContentPadding,
|
||||||
@@ -278,11 +243,24 @@ fun RoomSelectView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NoResultsFound() {
|
||||||
|
Text(
|
||||||
|
text = stringResource(CommonStrings.common_no_results),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 80.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RoomListError() {
|
private fun RoomListError() {
|
||||||
IconTitleSubtitleMolecule(
|
IconTitleSubtitleMolecule(
|
||||||
|
|||||||
-15
@@ -59,7 +59,6 @@ class RoomSelectPresenterTest {
|
|||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.selectedRooms).isEmpty()
|
assertThat(initialState.selectedRooms).isEmpty()
|
||||||
assertThat(initialState.resultState).isInstanceOf(SearchBarResultState.Initial::class.java)
|
assertThat(initialState.resultState).isInstanceOf(SearchBarResultState.Initial::class.java)
|
||||||
assertThat(initialState.isSearchActive).isFalse()
|
|
||||||
assertThat(initialState.maxNumberOfRooms).isEqualTo(10)
|
assertThat(initialState.maxNumberOfRooms).isEqualTo(10)
|
||||||
assertThat(initialState.canSelectMoreRooms).isTrue()
|
assertThat(initialState.canSelectMoreRooms).isTrue()
|
||||||
assertThat(initialState.selectedAccount).isEqualTo(MatrixUser(A_SESSION_ID))
|
assertThat(initialState.selectedAccount).isEqualTo(MatrixUser(A_SESSION_ID))
|
||||||
@@ -284,18 +283,6 @@ class RoomSelectPresenterTest {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - toggle search active`() = runTest {
|
|
||||||
val presenter = createRoomSelectPresenter()
|
|
||||||
presenter.test {
|
|
||||||
val initialState = awaitItem()
|
|
||||||
initialState.eventSink(RoomSelectEvent.ToggleSearchActive)
|
|
||||||
assertThat(awaitItem().isSearchActive).isTrue()
|
|
||||||
initialState.eventSink(RoomSelectEvent.ToggleSearchActive)
|
|
||||||
assertThat(awaitItem().isSearchActive).isFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - update query`() = runTest {
|
fun `present - update query`() = runTest {
|
||||||
val roomSummary = aRoomSummary()
|
val roomSummary = aRoomSummary()
|
||||||
@@ -314,8 +301,6 @@ class RoomSelectPresenterTest {
|
|||||||
// Do not compare the lambda because they will be different. So copy the lambda from expectedRoomSummary to result
|
// Do not compare the lambda because they will be different. So copy the lambda from expectedRoomSummary to result
|
||||||
val result = (awaitItem().resultState as SearchBarResultState.Results).results
|
val result = (awaitItem().resultState as SearchBarResultState.Results).results
|
||||||
assertThat(result).isEqualTo(listOf(expectedRoomInfo))
|
assertThat(result).isEqualTo(listOf(expectedRoomInfo))
|
||||||
initialState.eventSink(RoomSelectEvent.ToggleSearchActive)
|
|
||||||
skipItems(1)
|
|
||||||
initialState.searchQuery.setTextAndPlaceCursorAtEnd("string not contained")
|
initialState.searchQuery.setTextAndPlaceCursorAtEnd("string not contained")
|
||||||
assertThat(
|
assertThat(
|
||||||
roomList.currentFilter.value
|
roomList.currentFilter.value
|
||||||
|
|||||||
+11
@@ -155,6 +155,17 @@ class RoomSelectViewTest : RobolectricTest() {
|
|||||||
onNodeWithText("Room 0").assertIsNotDisplayed()
|
onNodeWithText("Room 0").assertIsNotDisplayed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `a message is displayed when no room matches the search query`() = runAndroidComposeUiTest {
|
||||||
|
setRoomSelectView(
|
||||||
|
aRoomSelectState(
|
||||||
|
searchQuery = "Test",
|
||||||
|
resultState = SearchBarResultState.NoResultsFound,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onNodeWithText(activity!!.getString(CommonStrings.common_no_results)).assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `an error is displayed instead of the rooms if they cannot be loaded`() = runAndroidComposeUiTest<ComponentActivity> {
|
fun `an error is displayed instead of the rooms if they cannot be loaded`() = runAndroidComposeUiTest<ComponentActivity> {
|
||||||
setRoomSelectView(
|
setRoomSelectView(
|
||||||
|
|||||||
Reference in New Issue
Block a user