Rework and add remove button

This commit is contained in:
Florian Renaud 2023-05-09 22:08:47 +02:00
parent 6641afdd17
commit 554ce9f650
10 changed files with 55 additions and 125 deletions

View file

@ -17,6 +17,7 @@
package io.element.android.features.createroom.impl.configureroom package io.element.android.features.createroom.impl.configureroom
import io.element.android.features.createroom.impl.CreateRoomConfig import io.element.android.features.createroom.impl.CreateRoomConfig
import io.element.android.features.createroom.impl.configureroom.avatar.AvatarAction
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
sealed interface ConfigureRoomEvents { sealed interface ConfigureRoomEvents {
@ -25,5 +26,6 @@ sealed interface ConfigureRoomEvents {
data class RoomPrivacyChanged(val privacy: RoomPrivacy?) : ConfigureRoomEvents data class RoomPrivacyChanged(val privacy: RoomPrivacy?) : ConfigureRoomEvents
data class RemoveFromSelection(val matrixUser: MatrixUser) : ConfigureRoomEvents data class RemoveFromSelection(val matrixUser: MatrixUser) : ConfigureRoomEvents
data class CreateRoom(val config: CreateRoomConfig) : ConfigureRoomEvents data class CreateRoom(val config: CreateRoomConfig) : ConfigureRoomEvents
data class HandleAvatarAction(val action: AvatarAction) : ConfigureRoomEvents
object CancelCreateRoom : ConfigureRoomEvents object CancelCreateRoom : ConfigureRoomEvents
} }

View file

@ -27,8 +27,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import io.element.android.features.createroom.impl.CreateRoomConfig import io.element.android.features.createroom.impl.CreateRoomConfig
import io.element.android.features.createroom.impl.CreateRoomDataStore import io.element.android.features.createroom.impl.CreateRoomDataStore
import io.element.android.features.createroom.impl.configureroom.avatar.AvatarAction import io.element.android.features.createroom.impl.configureroom.avatar.AvatarAction
import io.element.android.features.createroom.impl.configureroom.avatar.AvatarActionListEvents
import io.element.android.features.createroom.impl.configureroom.avatar.AvatarActionListState
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.execute import io.element.android.libraries.architecture.execute
@ -38,7 +36,7 @@ import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
import io.element.android.libraries.matrix.api.createroom.RoomPreset import io.element.android.libraries.matrix.api.createroom.RoomPreset
import io.element.android.libraries.matrix.api.createroom.RoomVisibility import io.element.android.libraries.matrix.api.createroom.RoomVisibility
import io.element.android.libraries.mediapickers.PickerProvider import io.element.android.libraries.mediapickers.PickerProvider
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@ -65,6 +63,19 @@ class ConfigureRoomPresenter @Inject constructor(
if (uri != null) dataStore.setAvatarUrl(uri.toString()) if (uri != null) dataStore.setAvatarUrl(uri.toString())
}) })
val avatarActions by remember(createRoomConfig.value.avatarUrl) {
derivedStateOf {
mutableListOf(
AvatarAction.TakePhoto,
AvatarAction.ChoosePhoto,
).apply {
if (createRoomConfig.value.avatarUrl != null) {
add(AvatarAction.Remove)
}
}.toImmutableList()
}
}
val localCoroutineScope = rememberCoroutineScope() val localCoroutineScope = rememberCoroutineScope()
val createRoomAction: MutableState<Async<RoomId>> = remember { mutableStateOf(Async.Uninitialized) } val createRoomAction: MutableState<Async<RoomId>> = remember { mutableStateOf(Async.Uninitialized) }
@ -80,31 +91,22 @@ class ConfigureRoomPresenter @Inject constructor(
is ConfigureRoomEvents.RoomPrivacyChanged -> dataStore.setPrivacy(event.privacy) is ConfigureRoomEvents.RoomPrivacyChanged -> dataStore.setPrivacy(event.privacy)
is ConfigureRoomEvents.RemoveFromSelection -> dataStore.selectedUserListDataStore.removeUserFromSelection(event.matrixUser) is ConfigureRoomEvents.RemoveFromSelection -> dataStore.selectedUserListDataStore.removeUserFromSelection(event.matrixUser)
is ConfigureRoomEvents.CreateRoom -> createRoom(event.config) is ConfigureRoomEvents.CreateRoom -> createRoom(event.config)
is ConfigureRoomEvents.HandleAvatarAction -> {
when (event.action) {
AvatarAction.ChoosePhoto -> galleryImagePicker.launch()
AvatarAction.TakePhoto -> cameraPhotoPicker.launch()
AvatarAction.Remove -> dataStore.setAvatarUrl(null)
}
}
ConfigureRoomEvents.CancelCreateRoom -> createRoomAction.value = Async.Uninitialized ConfigureRoomEvents.CancelCreateRoom -> createRoomAction.value = Async.Uninitialized
} }
} }
fun handleAvatarEvents(event: AvatarActionListEvents) {
when (event) {
is AvatarActionListEvents.HandleAction -> when (event.action) {
AvatarAction.ChoosePhoto -> galleryImagePicker.launch()
AvatarAction.TakePhoto -> cameraPhotoPicker.launch()
}
}
}
val avatarActionListState = AvatarActionListState(
actions = persistentListOf(
AvatarAction.ChoosePhoto,
AvatarAction.TakePhoto,
),
eventSink = ::handleAvatarEvents,
)
return ConfigureRoomState( return ConfigureRoomState(
config = createRoomConfig.value, config = createRoomConfig.value,
isCreateButtonEnabled = isCreateButtonEnabled, isCreateButtonEnabled = isCreateButtonEnabled,
avatarActionListState = avatarActionListState, avatarActions = avatarActions,
createRoomAction = createRoomAction.value, createRoomAction = createRoomAction.value,
eventSink = ::handleEvents, eventSink = ::handleEvents,
) )

View file

@ -17,14 +17,15 @@
package io.element.android.features.createroom.impl.configureroom package io.element.android.features.createroom.impl.configureroom
import io.element.android.features.createroom.impl.CreateRoomConfig import io.element.android.features.createroom.impl.CreateRoomConfig
import io.element.android.features.createroom.impl.configureroom.avatar.AvatarActionListState import io.element.android.features.createroom.impl.configureroom.avatar.AvatarAction
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.collections.immutable.ImmutableList
data class ConfigureRoomState( data class ConfigureRoomState(
val config: CreateRoomConfig, val config: CreateRoomConfig,
val isCreateButtonEnabled: Boolean, val isCreateButtonEnabled: Boolean,
val avatarActionListState: AvatarActionListState, val avatarActions: ImmutableList<AvatarAction>,
val createRoomAction: Async<RoomId>, val createRoomAction: Async<RoomId>,
val eventSink: (ConfigureRoomEvents) -> Unit val eventSink: (ConfigureRoomEvents) -> Unit
) )

View file

@ -18,8 +18,6 @@ package io.element.android.features.createroom.impl.configureroom
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.createroom.impl.CreateRoomConfig import io.element.android.features.createroom.impl.CreateRoomConfig
import io.element.android.features.createroom.impl.configureroom.avatar.AvatarAction
import io.element.android.features.createroom.impl.configureroom.avatar.AvatarActionListState
import io.element.android.features.userlist.api.aListOfSelectedUsers import io.element.android.features.userlist.api.aListOfSelectedUsers
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@ -43,10 +41,7 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomSt
fun aConfigureRoomState() = ConfigureRoomState( fun aConfigureRoomState() = ConfigureRoomState(
config = CreateRoomConfig(), config = CreateRoomConfig(),
isCreateButtonEnabled = false, isCreateButtonEnabled = false,
avatarActionListState = AvatarActionListState( avatarActions = persistentListOf(),
actions = persistentListOf(AvatarAction.TakePhoto, AvatarAction.ChoosePhoto),
eventSink = {},
),
createRoomAction = Async.Uninitialized, createRoomAction = Async.Uninitialized,
eventSink = { }, eventSink = { },
) )

View file

@ -46,7 +46,6 @@ import io.element.android.features.createroom.impl.R
import io.element.android.features.createroom.impl.components.Avatar import io.element.android.features.createroom.impl.components.Avatar
import io.element.android.features.createroom.impl.components.LabelledTextField import io.element.android.features.createroom.impl.components.LabelledTextField
import io.element.android.features.createroom.impl.components.RoomPrivacyOption import io.element.android.features.createroom.impl.components.RoomPrivacyOption
import io.element.android.features.createroom.impl.configureroom.avatar.AvatarActionListEvents
import io.element.android.features.createroom.impl.configureroom.avatar.AvatarActionListView import io.element.android.features.createroom.impl.configureroom.avatar.AvatarActionListView
import io.element.android.features.userlist.api.components.SelectedUsersList import io.element.android.features.userlist.api.components.SelectedUsersList
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
@ -130,9 +129,9 @@ fun ConfigureRoomView(
} }
AvatarActionListView( AvatarActionListView(
state = state.avatarActionListState, actions = state.avatarActions,
modalBottomSheetState = itemActionsBottomSheetState, modalBottomSheetState = itemActionsBottomSheetState,
onActionSelected = { state.avatarActionListState.eventSink(AvatarActionListEvents.HandleAction(it)) } onActionSelected = { state.eventSink(ConfigureRoomEvents.HandleAvatarAction(it)) }
) )
when (state.createRoomAction) { when (state.createRoomAction) {

View file

@ -18,8 +18,9 @@ package io.element.android.features.createroom.impl.configureroom.avatar
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PhotoCamera import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.filled.PhotoLibrary import androidx.compose.material.icons.outlined.PhotoCamera
import androidx.compose.material.icons.outlined.PhotoLibrary
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import io.element.android.libraries.ui.strings.R import io.element.android.libraries.ui.strings.R
@ -28,7 +29,9 @@ import io.element.android.libraries.ui.strings.R
sealed class AvatarAction( sealed class AvatarAction(
@StringRes val titleResId: Int, @StringRes val titleResId: Int,
val icon: ImageVector, val icon: ImageVector,
val destructive: Boolean = false,
) { ) {
object TakePhoto : AvatarAction(titleResId = R.string.action_take_photo, icon = Icons.Default.PhotoCamera) object TakePhoto : AvatarAction(titleResId = R.string.action_take_photo, icon = Icons.Outlined.PhotoCamera)
object ChoosePhoto : AvatarAction(titleResId = R.string.action_choose_photo, icon = Icons.Default.PhotoLibrary) object ChoosePhoto : AvatarAction(titleResId = R.string.action_choose_photo, icon = Icons.Outlined.PhotoLibrary)
object Remove : AvatarAction(titleResId = R.string.action_remove, icon = Icons.Outlined.Delete, destructive = true)
} }

View file

@ -1,21 +0,0 @@
/*
* 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.configureroom.avatar
sealed interface AvatarActionListEvents {
data class HandleAction(val action: AvatarAction) : AvatarActionListEvents
}

View file

@ -1,26 +0,0 @@
/*
* 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.configureroom.avatar
import androidx.compose.runtime.Immutable
import kotlinx.collections.immutable.ImmutableList
@Immutable
data class AvatarActionListState(
val actions: ImmutableList<AvatarAction>,
val eventSink: (AvatarActionListEvents) -> Unit,
)

View file

@ -1,30 +0,0 @@
/*
* 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.configureroom.avatar
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import kotlinx.collections.immutable.persistentListOf
open class ActionListStateProvider : PreviewParameterProvider<AvatarActionListState> {
override val values: Sequence<AvatarActionListState>
get() = sequenceOf(anActionListState())
}
fun anActionListState() = AvatarActionListState(
actions = persistentListOf(AvatarAction.TakePhoto, AvatarAction.ChoosePhoto),
eventSink = {}
)

View file

@ -29,21 +29,23 @@ import androidx.compose.material.ListItem
import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.ModalBottomSheetState
import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
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.Icon import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheetLayout import io.element.android.libraries.designsystem.theme.components.ModalBottomSheetLayout
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable @Composable
fun AvatarActionListView( fun AvatarActionListView(
state: AvatarActionListState, actions: ImmutableList<AvatarAction>,
modalBottomSheetState: ModalBottomSheetState, modalBottomSheetState: ModalBottomSheetState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onActionSelected: (action: AvatarAction) -> Unit = {}, onActionSelected: (action: AvatarAction) -> Unit = {},
@ -61,7 +63,7 @@ fun AvatarActionListView(
sheetState = modalBottomSheetState, sheetState = modalBottomSheetState,
sheetContent = { sheetContent = {
SheetContent( SheetContent(
state = state, actions = actions,
onActionClicked = ::onItemActionClicked, onActionClicked = ::onItemActionClicked,
modifier = Modifier modifier = Modifier
.navigationBarsPadding() .navigationBarsPadding()
@ -73,11 +75,10 @@ fun AvatarActionListView(
@Composable @Composable
private fun SheetContent( private fun SheetContent(
state: AvatarActionListState, actions: ImmutableList<AvatarAction>,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onActionClicked: (AvatarAction) -> Unit = { }, onActionClicked: (AvatarAction) -> Unit = { },
) { ) {
val actions = state.actions
LazyColumn( LazyColumn(
modifier = modifier.fillMaxWidth() modifier = modifier.fillMaxWidth()
) { ) {
@ -87,12 +88,16 @@ private fun SheetContent(
ListItem( ListItem(
modifier = Modifier.clickable { onActionClicked(action) }, modifier = Modifier.clickable { onActionClicked(action) },
text = { text = {
Text(text = stringResource(action.titleResId)) Text(
text = stringResource(action.titleResId),
color = if (action.destructive) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary,
)
}, },
icon = { icon = {
Icon( Icon(
imageVector = action.icon, imageVector = action.icon,
contentDescription = stringResource(action.titleResId), contentDescription = stringResource(action.titleResId),
tint = if (action.destructive) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary,
) )
} }
) )
@ -102,18 +107,18 @@ private fun SheetContent(
@Preview @Preview
@Composable @Composable
fun SheetContentLightPreview(@PreviewParameter(ActionListStateProvider::class) state: AvatarActionListState) = fun SheetContentLightPreview() =
ElementPreviewLight { ContentToPreview(state) } ElementPreviewLight { ContentToPreview() }
@Preview @Preview
@Composable @Composable
fun SheetContentDarkPreview(@PreviewParameter(ActionListStateProvider::class) state: AvatarActionListState) = fun SheetContentDarkPreview() =
ElementPreviewDark { ContentToPreview(state) } ElementPreviewDark { ContentToPreview() }
@Composable @Composable
private fun ContentToPreview(state: AvatarActionListState) { private fun ContentToPreview() {
AvatarActionListView( AvatarActionListView(
state = state, actions = persistentListOf(AvatarAction.ChoosePhoto, AvatarAction.TakePhoto, AvatarAction.Remove),
modalBottomSheetState = ModalBottomSheetState( modalBottomSheetState = ModalBottomSheetState(
initialValue = ModalBottomSheetValue.Expanded initialValue = ModalBottomSheetValue.Expanded
), ),