Adding multi/single selection variants

This commit is contained in:
Maxime NATUREL 2023-03-15 15:12:29 +01:00 committed by Florian Renaud
parent f950729d17
commit 79bd3da083
12 changed files with 242 additions and 156 deletions

View file

@ -17,19 +17,19 @@
package io.element.android.features.createroom.impl.root package io.element.android.features.createroom.impl.root
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import io.element.android.features.selectusers.api.SelectUsersPresenter import io.element.android.features.selectusers.api.SelectSingleUserPresenter
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.ui.model.MatrixUser import io.element.android.libraries.matrix.ui.model.MatrixUser
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class CreateRoomRootPresenter @Inject constructor( class CreateRoomRootPresenter @Inject constructor(
private val selectUsersPresenter: SelectUsersPresenter, private val selectSingleUserPresenter: SelectSingleUserPresenter,
) : Presenter<CreateRoomRootState> { ) : Presenter<CreateRoomRootState> {
@Composable @Composable
override fun present(): CreateRoomRootState { override fun present(): CreateRoomRootState {
val selectUsersState = selectUsersPresenter.present() val selectUsersState = selectSingleUserPresenter.present()
fun handleEvents(event: CreateRoomRootEvents) { fun handleEvents(event: CreateRoomRootEvents) {
when (event) { when (event) {

View file

@ -17,10 +17,7 @@
package io.element.android.features.createroom.impl.root package io.element.android.features.createroom.impl.root
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.selectusers.api.SelectUsersState import io.element.android.features.selectusers.api.aSelectUsersState
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.collections.immutable.persistentListOf
open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRootState> { open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRootState> {
override val values: Sequence<CreateRoomRootState> override val values: Sequence<CreateRoomRootState>
@ -31,11 +28,5 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRoot
fun aCreateRoomRootState() = CreateRoomRootState( fun aCreateRoomRootState() = CreateRoomRootState(
eventSink = {}, eventSink = {},
selectUsersState = SelectUsersState( selectUsersState = aSelectUsersState(),
searchQuery = "",
searchResults = persistentListOf(),
selectedUsers = persistentListOf(),
isSearchActive = false,
eventSink = {},
)
) )

View file

@ -24,40 +24,27 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SearchBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
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
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import io.element.android.features.selectusers.api.SearchUserBar
import io.element.android.features.selectusers.api.SelectUsersView import io.element.android.features.selectusers.api.SelectUsersView
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.CenterAlignedTopAppBar import io.element.android.libraries.designsystem.theme.components.CenterAlignedTopAppBar
import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.IconButton
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.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.ui.components.MatrixUserRow
import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.collections.immutable.ImmutableList
import io.element.android.libraries.designsystem.R as DrawableR import io.element.android.libraries.designsystem.R as DrawableR
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.R as StringR
@ -83,7 +70,7 @@ fun CreateRoomRootView(
) { ) {
SelectUsersView( SelectUsersView(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
state = state.selectUsersState state = state.selectUsersState,
) )
if (!state.selectUsersState.isSearchActive) { if (!state.selectUsersState.isSearchActive) {

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.selectusers.api
import io.element.android.libraries.architecture.Presenter
interface SelectMultipleUsersPresenter : Presenter<SelectUsersState>

View file

@ -18,4 +18,4 @@ package io.element.android.features.selectusers.api
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
interface SelectUsersPresenter : Presenter<SelectUsersState> interface SelectSingleUserPresenter : Presenter<SelectUsersState>

View file

@ -24,5 +24,6 @@ data class SelectUsersState(
val searchResults: ImmutableList<MatrixUser>, val searchResults: ImmutableList<MatrixUser>,
val selectedUsers: ImmutableList<MatrixUser>, val selectedUsers: ImmutableList<MatrixUser>,
val isSearchActive: Boolean, val isSearchActive: Boolean,
val isMultiSelectionEnabled: Boolean,
val eventSink: (SelectUsersEvents) -> Unit, val eventSink: (SelectUsersEvents) -> Unit,
) )

View file

@ -24,31 +24,23 @@ import kotlinx.collections.immutable.persistentListOf
open class SelectUsersStateProvider : PreviewParameterProvider<SelectUsersState> { open class SelectUsersStateProvider : PreviewParameterProvider<SelectUsersState> {
override val values: Sequence<SelectUsersState> override val values: Sequence<SelectUsersState>
get() = sequenceOf( get() = sequenceOf(
// TODO add states with selectedUsers
aSelectUsersState(), aSelectUsersState(),
aSelectUsersState().copy(isSearchActive = true), aSelectUsersState().copy(isSearchActive = true),
aSelectUsersState().copy(isSearchActive = true, searchQuery = "someone"), aSelectUsersState().copy(isSearchActive = true, searchQuery = "someone"),
aSelectUsersState().copy(isSearchActive = true, searchQuery = "someone", isMultiSelectionEnabled = true),
aSelectUsersState().copy( aSelectUsersState().copy(
isSearchActive = true, isSearchActive = true,
searchQuery = "@someone:matrix.org", searchQuery = "@someone:matrix.org",
searchResults = persistentListOf( selectedUsers = aListOfSelectedUsers(),
MatrixUser(id = UserId("@someone:matrix.org")), searchResults = aListOfResults(),
MatrixUser(id = UserId("@someone:matrix.org"), username = "someone"),
MatrixUser(
id = UserId("@someone_with_a_very_long_matrix_identifier:a_very_long_domain.org"),
username = "hey, I am someone with a very long display name"
),
MatrixUser(id = UserId("@someone_2:matrix.org"), username = "someone 2"),
MatrixUser(id = UserId("@someone_3:matrix.org"), username = "someone 3"),
MatrixUser(id = UserId("@someone_4:matrix.org"), username = "someone 4"),
MatrixUser(id = UserId("@someone_5:matrix.org"), username = "someone 5"),
MatrixUser(id = UserId("@someone_6:matrix.org"), username = "someone 6"),
MatrixUser(id = UserId("@someone_7:matrix.org"), username = "someone 7"),
MatrixUser(id = UserId("@someone_8:matrix.org"), username = "someone 8"),
MatrixUser(id = UserId("@someone_9:matrix.org"), username = "someone 9"),
MatrixUser(id = UserId("@someone_10:matrix.org"), username = "someone 10"),
)
), ),
aSelectUsersState().copy(
isSearchActive = true,
searchQuery = "@someone:matrix.org",
isMultiSelectionEnabled = true,
selectedUsers = aListOfSelectedUsers(),
searchResults = aListOfResults(),
)
) )
} }
@ -57,5 +49,29 @@ fun aSelectUsersState() = SelectUsersState(
searchQuery = "", searchQuery = "",
searchResults = persistentListOf(), searchResults = persistentListOf(),
selectedUsers = persistentListOf(), selectedUsers = persistentListOf(),
isMultiSelectionEnabled = false,
eventSink = {} eventSink = {}
) )
fun aListOfSelectedUsers() = persistentListOf(
MatrixUser(id = UserId("@someone:matrix.org")),
MatrixUser(id = UserId("@someone:matrix.org"), username = "someone"),
)
fun aListOfResults() = persistentListOf(
MatrixUser(id = UserId("@someone:matrix.org")),
MatrixUser(id = UserId("@someone:matrix.org"), username = "someone"),
MatrixUser(
id = UserId("@someone_with_a_very_long_matrix_identifier:a_very_long_domain.org"),
username = "hey, I am someone with a very long display name"
),
MatrixUser(id = UserId("@someone_2:matrix.org"), username = "someone 2"),
MatrixUser(id = UserId("@someone_3:matrix.org"), username = "someone 3"),
MatrixUser(id = UserId("@someone_4:matrix.org"), username = "someone 4"),
MatrixUser(id = UserId("@someone_5:matrix.org"), username = "someone 5"),
MatrixUser(id = UserId("@someone_6:matrix.org"), username = "someone 6"),
MatrixUser(id = UserId("@someone_7:matrix.org"), username = "someone 7"),
MatrixUser(id = UserId("@someone_8:matrix.org"), username = "someone 8"),
MatrixUser(id = UserId("@someone_9:matrix.org"), username = "someone 9"),
MatrixUser(id = UserId("@someone_10:matrix.org"), username = "someone 10"),
)

View file

@ -20,7 +20,6 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
@ -62,7 +61,6 @@ import io.element.android.libraries.matrix.ui.model.getBestName
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.R as StringR
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SelectUsersView( fun SelectUsersView(
state: SelectUsersState, state: SelectUsersState,
@ -71,28 +69,119 @@ fun SelectUsersView(
val eventSink = state.eventSink val eventSink = state.eventSink
Column( Column(
modifier = modifier modifier = modifier,
.fillMaxSize()
) { ) {
SearchUserBar( SearchUserBar(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
query = state.searchQuery, query = state.searchQuery,
results = state.searchResults, results = state.searchResults,
selectedUsers = state.selectedUsers,
active = state.isSearchActive, active = state.isSearchActive,
isMultiSelectionEnabled = state.isMultiSelectionEnabled,
onActiveChanged = { eventSink.invoke(SelectUsersEvents.OnSearchActiveChanged(it)) }, onActiveChanged = { eventSink.invoke(SelectUsersEvents.OnSearchActiveChanged(it)) },
onTextChanged = { state.eventSink(SelectUsersEvents.UpdateSearchQuery(it)) }, onTextChanged = { state.eventSink(SelectUsersEvents.UpdateSearchQuery(it)) },
onResultSelected = { state.eventSink(SelectUsersEvents.AddToSelection(it)) } onResultSelected = { state.eventSink(SelectUsersEvents.AddToSelection(it)) },
) onUserRemoved = { eventSink(SelectUsersEvents.RemoveFromSelection(it)) },
// TODO move into search content
SelectedUsersList(
modifier = Modifier.padding(16.dp),
selectedUsers = state.selectedUsers,
onUserRemoved = { eventSink(SelectUsersEvents.RemoveFromSelection(it)) }
) )
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchUserBar(
query: String,
results: ImmutableList<MatrixUser>,
selectedUsers: ImmutableList<MatrixUser>,
active: Boolean,
isMultiSelectionEnabled: Boolean,
modifier: Modifier = Modifier,
placeHolderTitle: String = stringResource(StringR.string.search_for_someone),
onActiveChanged: (Boolean) -> Unit = {},
onTextChanged: (String) -> Unit = {},
onResultSelected: (MatrixUser) -> Unit = {},
onUserRemoved: (MatrixUser) -> Unit = {},
) {
val focusManager = LocalFocusManager.current
if (!active) {
onTextChanged("")
focusManager.clearFocus()
}
SearchBar(
query = query,
onQueryChange = onTextChanged,
onSearch = { focusManager.clearFocus() },
active = active,
onActiveChange = onActiveChanged,
modifier = modifier
.padding(horizontal = if (!active) 16.dp else 0.dp),
placeholder = {
Text(
text = placeHolderTitle,
modifier = Modifier.alpha(0.4f), // FIXME align on Design system theme (removing alpha should be fine)
)
},
leadingIcon = if (active) {
{ BackButton(onClick = { onActiveChanged(false) }) }
} else {
null
},
trailingIcon = when {
active && query.isNotEmpty() -> {
{
IconButton(onClick = { onTextChanged("") }) {
Icon(Icons.Default.Close, stringResource(StringR.string.a11y_clear))
}
}
}
!active -> {
{
Icon(
imageVector = Icons.Default.Search,
contentDescription = stringResource(StringR.string.search),
modifier = Modifier.alpha(0.4f), // FIXME align on Design system theme (removing alpha should be fine)
)
}
}
else -> null
},
colors = if (!active) SearchBarDefaults.colors() else SearchBarDefaults.colors(containerColor = Color.Transparent),
content = {
if (isMultiSelectionEnabled && selectedUsers.isNotEmpty()) {
SelectedUsersList(
modifier = Modifier.padding(16.dp),
selectedUsers = selectedUsers,
onUserRemoved = onUserRemoved,
)
}
LazyColumn {
items(results) {
SearchUserResultItem(
matrixUser = it,
onClick = { onResultSelected(it) }
)
}
}
},
)
}
@Composable
fun SearchUserResultItem(
matrixUser: MatrixUser,
modifier: Modifier = Modifier,
onClick: () -> Unit = {},
) {
MatrixUserRow(
modifier = modifier,
matrixUser = matrixUser,
avatarSize = AvatarSize.Custom(36.dp),
onClick = onClick,
)
}
@Composable @Composable
fun SelectedUsersList( fun SelectedUsersList(
selectedUsers: List<MatrixUser>, selectedUsers: List<MatrixUser>,
@ -147,99 +236,14 @@ fun SelectedUser(
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchUserBar(
query: String,
results: ImmutableList<MatrixUser>,
active: Boolean,
modifier: Modifier = Modifier,
placeHolderTitle: String = stringResource(StringR.string.search_for_someone),
onActiveChanged: (Boolean) -> Unit = {},
onTextChanged: (String) -> Unit = {},
onResultSelected: (MatrixUser) -> Unit = {},
) {
val focusManager = LocalFocusManager.current
if (!active) {
onTextChanged("")
focusManager.clearFocus()
}
SearchBar(
query = query,
onQueryChange = onTextChanged,
onSearch = { focusManager.clearFocus() },
active = active,
onActiveChange = onActiveChanged,
modifier = modifier
.padding(horizontal = if (!active) 16.dp else 0.dp),
placeholder = {
Text(
text = placeHolderTitle,
modifier = Modifier.alpha(0.4f), // FIXME align on Design system theme (removing alpha should be fine)
)
},
leadingIcon = if (active) {
{ BackButton(onClick = { onActiveChanged(false) }) }
} else {
null
},
trailingIcon = when {
active && query.isNotEmpty() -> {
{
IconButton(onClick = { onTextChanged("") }) {
Icon(Icons.Default.Close, stringResource(StringR.string.a11y_clear))
}
}
}
!active -> {
{
Icon(
imageVector = Icons.Default.Search,
contentDescription = stringResource(StringR.string.search),
modifier = Modifier.alpha(0.4f), // FIXME align on Design system theme (removing alpha should be fine)
)
}
}
else -> null
},
colors = if (!active) SearchBarDefaults.colors() else SearchBarDefaults.colors(containerColor = Color.Transparent),
content = {
LazyColumn {
items(results) {
SearchUserResultItem(
matrixUser = it,
onClick = { onResultSelected(it) }
)
}
}
},
)
}
@Composable
fun SearchUserResultItem(
matrixUser: MatrixUser,
modifier: Modifier = Modifier,
onClick: () -> Unit = {},
) {
MatrixUserRow(
modifier = modifier,
matrixUser = matrixUser,
avatarSize = AvatarSize.Custom(36.dp),
onClick = onClick,
)
}
@Preview @Preview
@Composable @Composable
internal fun ChangeServerViewLightPreview(@PreviewParameter(SelectUsersStateProvider::class) state: SelectUsersState) = internal fun SelectUsersViewLightPreview(@PreviewParameter(SelectUsersStateProvider::class) state: SelectUsersState) =
ElementPreviewLight { ContentToPreview(state) } ElementPreviewLight { ContentToPreview(state) }
@Preview @Preview
@Composable @Composable
internal fun ChangeServerViewDarkPreview(@PreviewParameter(SelectUsersStateProvider::class) state: SelectUsersState) = internal fun SelectUsersViewDarkPreview(@PreviewParameter(SelectUsersStateProvider::class) state: SelectUsersState) =
ElementPreviewDark { ContentToPreview(state) } ElementPreviewDark { ContentToPreview(state) }
@Composable @Composable

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.selectusers.impl
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.selectusers.api.SelectMultipleUsersPresenter
import io.element.android.libraries.di.SessionScope
import javax.inject.Inject
// TODO add unit tests
@ContributesBinding(
scope = SessionScope::class,
boundType = SelectMultipleUsersPresenter::class,
)
class DefaultSelectMultipleUsersPresenter @Inject constructor() :
DefaultSelectUsersPresenter,
SelectMultipleUsersPresenter {
override val isMultiSelectionEnabled: Boolean = true
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.selectusers.impl
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.selectusers.api.SelectSingleUserPresenter
import io.element.android.libraries.di.SessionScope
import javax.inject.Inject
@ContributesBinding(
scope = SessionScope::class,
boundType = SelectSingleUserPresenter::class,
)
class DefaultSelectSingleUserPresenter @Inject constructor() :
DefaultSelectUsersPresenter,
SelectSingleUserPresenter {
override val isMultiSelectionEnabled: Boolean = false
}

View file

@ -24,26 +24,26 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.selectusers.api.SelectUsersEvents import io.element.android.features.selectusers.api.SelectUsersEvents
import io.element.android.features.selectusers.api.SelectUsersPresenter
import io.element.android.features.selectusers.api.SelectUsersState import io.element.android.features.selectusers.api.SelectUsersState
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.core.MatrixPatterns import io.element.android.libraries.matrix.api.core.MatrixPatterns
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.ui.model.MatrixUser import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import javax.inject.Inject
@ContributesBinding(SessionScope::class) interface DefaultSelectUsersPresenter : Presenter<SelectUsersState> {
class DefaultSelectUsersPresenter @Inject constructor() : SelectUsersPresenter {
val isMultiSelectionEnabled: Boolean
@Composable @Composable
override fun present(): SelectUsersState { override fun present(): SelectUsersState {
var isSearchActive by rememberSaveable { mutableStateOf(false) } var isSearchActive by rememberSaveable { mutableStateOf(false) }
val selectedUsers: MutableState<ImmutableList<MatrixUser>> = remember { mutableStateOf(persistentListOf()) } val selectedUsers: MutableState<ImmutableList<MatrixUser>> = remember {
mutableStateOf(persistentListOf())
}
var searchQuery by rememberSaveable { mutableStateOf("") } var searchQuery by rememberSaveable { mutableStateOf("") }
val searchResults: MutableState<ImmutableList<MatrixUser>> = remember { val searchResults: MutableState<ImmutableList<MatrixUser>> = remember {
mutableStateOf(persistentListOf()) mutableStateOf(persistentListOf())
@ -76,6 +76,7 @@ class DefaultSelectUsersPresenter @Inject constructor() : SelectUsersPresenter {
searchResults = searchResults.value, searchResults = searchResults.value,
selectedUsers = selectedUsers.value, selectedUsers = selectedUsers.value,
isSearchActive = isSearchActive, isSearchActive = isSearchActive,
isMultiSelectionEnabled = isMultiSelectionEnabled,
eventSink = ::handleEvents, eventSink = ::handleEvents,
) )
} }

View file

@ -28,11 +28,11 @@ import kotlinx.coroutines.test.runTest
import org.junit.Test import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class DefaultSelectUsersPresenterTests { class DefaultSelectSingleUserPresenterTests {
@Test @Test
fun `present - initial state`() = runTest { fun `present - initial state`() = runTest {
val presenter = DefaultSelectUsersPresenter() val presenter = DefaultSelectSingleUserPresenter()
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -43,7 +43,7 @@ class DefaultSelectUsersPresenterTests {
@Test @Test
fun `present - update search query`() = runTest { fun `present - update search query`() = runTest {
val presenter = DefaultSelectUsersPresenter() val presenter = DefaultSelectSingleUserPresenter()
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {