Browse Source

Create a reusable selectusers feature module

test/jme/compound-poc
Maxime NATUREL 2 years ago committed by Florian Renaud
parent
commit
31f8c92b58
  1. 4
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt
  2. 24
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleEvents.kt
  3. 8
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleNode.kt
  4. 16
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenter.kt
  5. 25
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleState.kt
  6. 46
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleStateProvider.kt
  7. 120
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt
  8. 30
      features/selectusers/api/build.gradle.kts
  9. 3
      features/selectusers/api/src/main/kotlin/io/element/android/features/selectusers/api/SelectUsersEvents.kt
  10. 21
      features/selectusers/api/src/main/kotlin/io/element/android/features/selectusers/api/SelectUsersPresenter.kt
  11. 4
      features/selectusers/api/src/main/kotlin/io/element/android/features/selectusers/api/SelectUsersState.kt
  12. 4
      features/selectusers/api/src/main/kotlin/io/element/android/features/selectusers/api/SelectUsersStateProvider.kt
  13. 190
      features/selectusers/api/src/main/kotlin/io/element/android/features/selectusers/api/SelectUsersView.kt
  14. 55
      features/selectusers/impl/build.gradle.kts
  15. 20
      features/selectusers/impl/src/main/AndroidManifest.xml
  16. 89
      features/selectusers/impl/src/main/kotlin/io/element/android/features/selectusers/impl/DefaultSelectUsersPresenter.kt
  17. 2
      settings.gradle.kts

4
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt

@ -28,8 +28,8 @@ import com.bumble.appyx.navmodel.backstack.operation.push @@ -28,8 +28,8 @@ import com.bumble.appyx.navmodel.backstack.operation.push
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.createroom.impl.addpeople.AddPeopleNode
import io.element.android.features.createroom.impl.root.CreateRoomRootNode
import io.element.android.features.createroom.impl.selectusers.SelectUsersNode
import io.element.android.libraries.architecture.BackstackNode
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.architecture.createNode
@ -67,7 +67,7 @@ class CreateRoomFlowNode @AssistedInject constructor( @@ -67,7 +67,7 @@ class CreateRoomFlowNode @AssistedInject constructor(
}
createNode<CreateRoomRootNode>(buildContext, plugins = listOf(callback))
}
NavTarget.NewRoom -> createNode<SelectUsersNode>(buildContext)
NavTarget.NewRoom -> createNode<AddPeopleNode>(buildContext)
}
}

24
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleEvents.kt

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
/*
* 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.createroom.impl.addpeople
import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.collections.immutable.ImmutableList
sealed interface AddPeopleEvents {
data class UpdateSelection(val users: ImmutableList<MatrixUser>) : AddPeopleEvents
}

8
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/selectusers/SelectUsersNode.kt → features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleNode.kt

@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.createroom.impl.selectusers
package io.element.android.features.createroom.impl.addpeople
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@ -27,16 +27,16 @@ import io.element.android.anvilannotations.ContributesNode @@ -27,16 +27,16 @@ import io.element.android.anvilannotations.ContributesNode
import io.element.android.libraries.di.AppScope
@ContributesNode(AppScope::class)
class SelectUsersNode @AssistedInject constructor(
class AddPeopleNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val presenter: SelectUsersPresenter,
private val presenter: AddPeoplePresenter,
) : Node(buildContext, plugins = plugins) {
@Composable
override fun View(modifier: Modifier) {
val state = presenter.present()
SelectUsersView(
AddPeopleView(
state = state,
modifier = modifier,
onBackPressed = { navigateUp() },

16
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/selectusers/SelectUsersPresenter.kt → features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenter.kt

@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.createroom.impl.selectusers
package io.element.android.features.createroom.impl.addpeople
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
@ -24,24 +24,22 @@ import io.element.android.libraries.architecture.Presenter @@ -24,24 +24,22 @@ import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import javax.inject.Inject
// TODO add unit tests
class SelectUsersPresenter @Inject constructor() : Presenter<SelectUsersState> {
class AddPeoplePresenter @Inject constructor() : Presenter<AddPeopleState> {
@Composable
override fun present(): SelectUsersState {
val selectedUsers: MutableState<ImmutableList<MatrixUser>> = remember { mutableStateOf(persistentListOf()) }
override fun present(): AddPeopleState {
val selectedUsers: MutableState<ImmutableList<MatrixUser>> = remember { mutableStateOf(persistentListOf(aMatrixUser("test"))) }
fun handleEvents(event: SelectUsersEvents) {
fun handleEvents(event: AddPeopleEvents) {
when (event) {
is SelectUsersEvents.AddToSelection -> selectedUsers.value = selectedUsers.value.plus(event.matrixUser).toImmutableList()
is SelectUsersEvents.RemoveFromSelection -> selectedUsers.value = selectedUsers.value.minus(event.matrixUser).toImmutableList()
is AddPeopleEvents.UpdateSelection -> selectedUsers.value = event.users
}
}
return SelectUsersState(
return AddPeopleState(
selectedUsers = selectedUsers.value,
eventSink = ::handleEvents,
)

25
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleState.kt

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
/*
* 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.createroom.impl.addpeople
import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.collections.immutable.ImmutableList
data class AddPeopleState(
val selectedUsers: ImmutableList<MatrixUser>,
val eventSink: (AddPeopleEvents) -> Unit,
)

46
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleStateProvider.kt

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
/*
* 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.createroom.impl.addpeople
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.designsystem.components.avatar.AvatarData
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 AddPeopleStateProvider : PreviewParameterProvider<AddPeopleState> {
override val values: Sequence<AddPeopleState>
get() = sequenceOf(
aAddPeopleState(),
aAddPeopleState().copy(
selectedUsers = persistentListOf(
aMatrixUser(userName = ""),
aMatrixUser(userName = "User"),
aMatrixUser(userName = "User with long name"),
)
)
)
}
fun aAddPeopleState() = AddPeopleState(
selectedUsers = persistentListOf(),
eventSink = {}
)
fun aMatrixUser(userName: String): MatrixUser {
return MatrixUser(id = UserId("@id"), username = userName, avatarData = AvatarData("@id", "U"))
}

120
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt

@ -0,0 +1,120 @@ @@ -0,0 +1,120 @@
/*
* Copyright (c) 2022 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.createroom.impl.addpeople
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
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.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.ui.strings.R as StringR
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AddPeopleView(
state: AddPeopleState,
modifier: Modifier = Modifier,
onBackPressed: () -> Unit = {},
onNextPressed: () -> Unit = {},
) {
var isSearchActive by rememberSaveable { mutableStateOf(false) }
val eventSink = state.eventSink
Scaffold(
topBar = {
AddPeopleViewTopBar(
hasSelectedUsers = state.selectedUsers.isNotEmpty(),
onBackPressed = onBackPressed,
onNextPressed = onNextPressed,
)
}
) { padding ->
Column(
modifier = modifier
.fillMaxSize()
.padding(padding),
) {
// TODO use reusable searchUser bar with multi selection
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AddPeopleViewTopBar(
hasSelectedUsers: Boolean,
modifier: Modifier = Modifier,
onBackPressed: () -> Unit = {},
onNextPressed: () -> Unit = {},
) {
CenterAlignedTopAppBar(
modifier = modifier,
title = {
Text(
text = stringResource(id = StringR.string.add_people),
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
)
},
navigationIcon = { BackButton(onClick = onBackPressed) },
actions = {
TextButton(
modifier = Modifier.padding(horizontal = 8.dp),
onClick = onNextPressed,
) {
val textActionResId = if (hasSelectedUsers) StringR.string.action_next else StringR.string.action_skip
Text(
text = stringResource(id = textActionResId),
fontSize = 16.sp,
)
}
}
)
}
@Preview
@Composable
internal fun ChangeServerViewLightPreview(@PreviewParameter(AddPeopleStateProvider::class) state: AddPeopleState) =
ElementPreviewLight { ContentToPreview(state) }
@Preview
@Composable
internal fun ChangeServerViewDarkPreview(@PreviewParameter(AddPeopleStateProvider::class) state: AddPeopleState) =
ElementPreviewDark { ContentToPreview(state) }
@Composable
private fun ContentToPreview(state: AddPeopleState) {
AddPeopleView(state = state)
}

30
features/selectusers/api/build.gradle.kts

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
/*
* 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.
*/
plugins {
id("io.element.android-compose-library")
}
android {
namespace = "io.element.android.features.selectusers.api"
}
dependencies {
implementation(projects.libraries.architecture)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.uiStrings)
implementation(projects.libraries.matrixui)
}

3
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/selectusers/SelectUsersEvents.kt → features/selectusers/api/src/main/kotlin/io/element/android/features/selectusers/api/SelectUsersEvents.kt

@ -14,11 +14,12 @@ @@ -14,11 +14,12 @@
* limitations under the License.
*/
package io.element.android.features.createroom.impl.selectusers
package io.element.android.features.selectusers.api
import io.element.android.libraries.matrix.ui.model.MatrixUser
sealed interface SelectUsersEvents {
data class UpdateSearchQuery(val query: String) : SelectUsersEvents
data class AddToSelection(val matrixUser: MatrixUser) : SelectUsersEvents
data class RemoveFromSelection(val matrixUser: MatrixUser) : SelectUsersEvents
}

21
features/selectusers/api/src/main/kotlin/io/element/android/features/selectusers/api/SelectUsersPresenter.kt

@ -0,0 +1,21 @@ @@ -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 SelectUsersPresenter : Presenter<SelectUsersState>

4
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/selectusers/SelectUsersState.kt → features/selectusers/api/src/main/kotlin/io/element/android/features/selectusers/api/SelectUsersState.kt

@ -14,12 +14,14 @@ @@ -14,12 +14,14 @@
* limitations under the License.
*/
package io.element.android.features.createroom.impl.selectusers
package io.element.android.features.selectusers.api
import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.collections.immutable.ImmutableList
data class SelectUsersState(
val searchQuery: String,
val searchResults: ImmutableList<MatrixUser>,
val selectedUsers: ImmutableList<MatrixUser>,
val eventSink: (SelectUsersEvents) -> Unit,
)

4
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/selectusers/SelectUsersStateProvider.kt → features/selectusers/api/src/main/kotlin/io/element/android/features/selectusers/api/SelectUsersStateProvider.kt

@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.createroom.impl.selectusers
package io.element.android.features.selectusers.api
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.designsystem.components.avatar.AvatarData
@ -37,6 +37,8 @@ open class SelectUsersStateProvider : PreviewParameterProvider<SelectUsersState> @@ -37,6 +37,8 @@ open class SelectUsersStateProvider : PreviewParameterProvider<SelectUsersState>
}
fun aSelectUsersState() = SelectUsersState(
searchQuery = "",
searchResults = persistentListOf(),
selectedUsers = persistentListOf(),
eventSink = {}
)

190
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/selectusers/SelectUsersView.kt → features/selectusers/api/src/main/kotlin/io/element/android/features/selectusers/api/SelectUsersView.kt

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 New Vector Ltd
* 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.
@ -14,47 +14,55 @@ @@ -14,47 +14,55 @@
* limitations under the License.
*/
package io.element.android.features.createroom.impl.selectusers
package io.element.android.features.selectusers.api
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SearchBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.element.android.libraries.designsystem.components.avatar.Avatar
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.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.CenterAlignedTopAppBar
import io.element.android.libraries.designsystem.theme.components.DockedSearchBar
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.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.matrix.ui.components.MatrixUserRow
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.ui.model.getBestName
import kotlinx.collections.immutable.ImmutableList
import io.element.android.libraries.ui.strings.R as StringR
@OptIn(ExperimentalMaterial3Api::class)
@ -62,66 +70,38 @@ import io.element.android.libraries.ui.strings.R as StringR @@ -62,66 +70,38 @@ import io.element.android.libraries.ui.strings.R as StringR
fun SelectUsersView(
state: SelectUsersState,
modifier: Modifier = Modifier,
onBackPressed: () -> Unit = {},
onNextPressed: () -> Unit = {},
onSearchActiveChanged: (Boolean) -> Unit = {},
onSelectionChanged: (ImmutableList<MatrixUser>) -> Unit = {},
) {
var isSearchActive by rememberSaveable { mutableStateOf(false) }
val eventSink = state.eventSink
Scaffold(
topBar = {
SelectUsersViewTopBar(
hasSelectedUsers = state.selectedUsers.isNotEmpty(),
onBackPressed = onBackPressed,
onNextPressed = onNextPressed,
)
}
) { padding ->
Column(
modifier = modifier
.fillMaxSize()
.padding(padding)
) {
// TODO create a SearchUserView with multi selection option + callbacks
SelectedUsersList(
modifier = Modifier.padding(horizontal = 16.dp),
selectedUsers = state.selectedUsers,
onUserRemoved = { eventSink(SelectUsersEvents.RemoveFromSelection(it)) }
)
}
}
}
// TODO how to pass back the selection list?
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SelectUsersViewTopBar(
hasSelectedUsers: Boolean,
modifier: Modifier = Modifier,
onBackPressed: () -> Unit = {},
onNextPressed: () -> Unit = {},
) {
CenterAlignedTopAppBar(
modifier = modifier,
title = {
Text(
text = stringResource(id = StringR.string.add_people),
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
)
},
navigationIcon = { BackButton(onClick = onBackPressed) },
actions = {
TextButton(
modifier = Modifier.padding(horizontal = 8.dp),
onClick = onNextPressed,
) {
val textActionResId = if (hasSelectedUsers) StringR.string.action_next else StringR.string.action_skip
Text(
text = stringResource(id = textActionResId),
fontSize = 16.sp,
)
}
}
)
Column(
modifier = modifier
.fillMaxSize()
) {
SearchUserBar(
modifier = Modifier.fillMaxWidth(),
query = state.searchQuery,
results = state.searchResults,
active = isSearchActive,
onActiveChanged = {
isSearchActive = it
onSearchActiveChanged(it)
},
onTextChanged = { state.eventSink(SelectUsersEvents.UpdateSearchQuery(it)) },
onResultSelected = { state.eventSink(SelectUsersEvents.AddToSelection(it)) }
)
// TODO move into search content
SelectedUsersList(
modifier = Modifier.padding(16.dp),
selectedUsers = state.selectedUsers,
onUserRemoved = { eventSink(SelectUsersEvents.RemoveFromSelection(it)) }
)
}
}
@Composable
@ -178,6 +158,88 @@ fun SelectedUser( @@ -178,6 +158,88 @@ 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()
}
DockedSearchBar(
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
},
shape = if (!active) SearchBarDefaults.dockedShape else SearchBarDefaults.fullScreenShape,
colors = if (!active) SearchBarDefaults.colors() else SearchBarDefaults.colors(containerColor = Color.Transparent),
content = {
results.forEach {
SearchUserResultItem(
matrixUser = it,
onClick = { onResultSelected(it) }
)
}
},
)
}
@Composable
fun SearchUserResultItem(
matrixUser: MatrixUser,
modifier: Modifier = Modifier,
onClick: () -> Unit = {},
) {
MatrixUserRow(
modifier = modifier.heightIn(min = 56.dp),
matrixUser = matrixUser,
avatarSize = AvatarSize.Custom(36),
onClick = onClick,
)
}
@Preview
@Composable
internal fun ChangeServerViewLightPreview(@PreviewParameter(SelectUsersStateProvider::class) state: SelectUsersState) =

55
features/selectusers/impl/build.gradle.kts

@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
/*
* Copyright (c) 2022 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.
*/
// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id("io.element.android-compose-library")
alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
}
android {
namespace = "io.element.android.features.selectusers.impl"
}
anvil {
generateDaggerFactories.set(true)
}
dependencies {
implementation(projects.anvilannotations)
anvil(projects.anvilcodegen)
implementation(projects.libraries.core)
implementation(projects.libraries.architecture)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.matrixui)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.elementresources)
implementation(projects.libraries.testtags)
implementation(projects.libraries.uiStrings)
api(projects.features.selectusers.api)
ksp(libs.showkase.processor)
testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test)
testImplementation(libs.molecule.runtime)
testImplementation(libs.test.truth)
testImplementation(libs.test.turbine)
testImplementation(projects.libraries.matrix.test)
androidTestImplementation(libs.test.junitext)
}

20
features/selectusers/impl/src/main/AndroidManifest.xml

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

89
features/selectusers/impl/src/main/kotlin/io/element/android/features/selectusers/impl/DefaultSelectUsersPresenter.kt

@ -0,0 +1,89 @@ @@ -0,0 +1,89 @@
/*
* 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 androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
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.SelectUsersPresenter
import io.element.android.features.selectusers.api.SelectUsersState
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.core.MatrixPatterns
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import javax.inject.Inject
// TODO add unit tests
@ContributesBinding(SessionScope::class)
class DefaultSelectUsersPresenter @Inject constructor() : SelectUsersPresenter {
@Composable
override fun present(): SelectUsersState {
val selectedUsers: MutableState<ImmutableList<MatrixUser>> = remember { mutableStateOf(persistentListOf()) }
var searchQuery by rememberSaveable { mutableStateOf("") }
val searchResults: MutableState<ImmutableList<MatrixUser>> = remember {
mutableStateOf(persistentListOf())
}
fun handleEvents(event: SelectUsersEvents) {
when (event) {
is SelectUsersEvents.UpdateSearchQuery -> searchQuery = event.query
is SelectUsersEvents.AddToSelection -> selectedUsers.value = selectedUsers.value.plus(event.matrixUser).toImmutableList()
is SelectUsersEvents.RemoveFromSelection -> selectedUsers.value = selectedUsers.value.minus(event.matrixUser).toImmutableList()
}
}
LaunchedEffect(searchQuery) {
searchResults.value = if (MatrixPatterns.isUserId(searchQuery)) {
persistentListOf(MatrixUser(UserId(searchQuery)))
} else {
persistentListOf()
}
if (searchQuery.isNotEmpty()) {
searchResults.value = performSearch(searchQuery)
}
}
return SelectUsersState(
searchQuery = searchQuery,
searchResults = searchResults.value,
selectedUsers = selectedUsers.value,
eventSink = ::handleEvents,
)
}
private fun performSearch(query: String): ImmutableList<MatrixUser> {
val isMatrixId = MatrixPatterns.isUserId(query)
val results = mutableListOf<MatrixUser>()// TODO trigger /search request
if (isMatrixId && results.none { it.id.value == query }) {
val getProfileResult: MatrixUser? = null // TODO trigger /profile request
val profile = getProfileResult ?: MatrixUser(UserId(query))
results.add(0, profile)
}
return results.toImmutableList()
}
}

2
settings.gradle.kts

@ -92,3 +92,5 @@ include(":features:createroom:api") @@ -92,3 +92,5 @@ include(":features:createroom:api")
include(":features:createroom:impl")
include(":features:verifysession:api")
include(":features:verifysession:impl")
include(":features:selectusers:api")
include(":features:selectusers:impl")

Loading…
Cancel
Save