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
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
sealed interface ConfigureRoomEvents {
@@ -25,5 +26,6 @@ sealed interface ConfigureRoomEvents {
data class RoomPrivacyChanged(val privacy: RoomPrivacy?) : ConfigureRoomEvents
data class RemoveFromSelection(val matrixUser: MatrixUser) : ConfigureRoomEvents
data class CreateRoom(val config: CreateRoomConfig) : ConfigureRoomEvents
data class HandleAvatarAction(val action: AvatarAction) : 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.CreateRoomDataStore
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.Presenter
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.RoomVisibility
import io.element.android.libraries.mediapickers.PickerProvider
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -65,6 +63,19 @@ class ConfigureRoomPresenter @Inject constructor(
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 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.RemoveFromSelection -> dataStore.selectedUserListDataStore.removeUserFromSelection(event.matrixUser)
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
}
}
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(
config = createRoomConfig.value,
isCreateButtonEnabled = isCreateButtonEnabled,
avatarActionListState = avatarActionListState,
avatarActions = avatarActions,
createRoomAction = createRoomAction.value,
eventSink = ::handleEvents,
)

View File

@@ -17,14 +17,15 @@
package io.element.android.features.createroom.impl.configureroom
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.matrix.api.core.RoomId
import kotlinx.collections.immutable.ImmutableList
data class ConfigureRoomState(
val config: CreateRoomConfig,
val isCreateButtonEnabled: Boolean,
val avatarActionListState: AvatarActionListState,
val avatarActions: ImmutableList<AvatarAction>,
val createRoomAction: Async<RoomId>,
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 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.libraries.architecture.Async
import kotlinx.collections.immutable.persistentListOf
@@ -43,10 +41,7 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomSt
fun aConfigureRoomState() = ConfigureRoomState(
config = CreateRoomConfig(),
isCreateButtonEnabled = false,
avatarActionListState = AvatarActionListState(
actions = persistentListOf(AvatarAction.TakePhoto, AvatarAction.ChoosePhoto),
eventSink = {},
),
avatarActions = persistentListOf(),
createRoomAction = Async.Uninitialized,
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.LabelledTextField
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.userlist.api.components.SelectedUsersList
import io.element.android.libraries.architecture.Async
@@ -130,9 +129,9 @@ fun ConfigureRoomView(
}
AvatarActionListView(
state = state.avatarActionListState,
actions = state.avatarActions,
modalBottomSheetState = itemActionsBottomSheetState,
onActionSelected = { state.avatarActionListState.eventSink(AvatarActionListEvents.HandleAction(it)) }
onActionSelected = { state.eventSink(ConfigureRoomEvents.HandleAvatarAction(it)) }
)
when (state.createRoomAction) {

View File

@@ -18,8 +18,9 @@ package io.element.android.features.createroom.impl.configureroom.avatar
import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PhotoCamera
import androidx.compose.material.icons.filled.PhotoLibrary
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.PhotoCamera
import androidx.compose.material.icons.outlined.PhotoLibrary
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.vector.ImageVector
import io.element.android.libraries.ui.strings.R
@@ -28,7 +29,9 @@ import io.element.android.libraries.ui.strings.R
sealed class AvatarAction(
@StringRes val titleResId: Int,
val icon: ImageVector,
val destructive: Boolean = false,
) {
object TakePhoto : AvatarAction(titleResId = R.string.action_take_photo, icon = Icons.Default.PhotoCamera)
object ChoosePhoto : AvatarAction(titleResId = R.string.action_choose_photo, icon = Icons.Default.PhotoLibrary)
object TakePhoto : AvatarAction(titleResId = R.string.action_take_photo, icon = Icons.Outlined.PhotoCamera)
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.ModalBottomSheetValue
import androidx.compose.material.Text
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
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.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheetLayout
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.launch
@Composable
fun AvatarActionListView(
state: AvatarActionListState,
actions: ImmutableList<AvatarAction>,
modalBottomSheetState: ModalBottomSheetState,
modifier: Modifier = Modifier,
onActionSelected: (action: AvatarAction) -> Unit = {},
@@ -61,7 +63,7 @@ fun AvatarActionListView(
sheetState = modalBottomSheetState,
sheetContent = {
SheetContent(
state = state,
actions = actions,
onActionClicked = ::onItemActionClicked,
modifier = Modifier
.navigationBarsPadding()
@@ -73,11 +75,10 @@ fun AvatarActionListView(
@Composable
private fun SheetContent(
state: AvatarActionListState,
actions: ImmutableList<AvatarAction>,
modifier: Modifier = Modifier,
onActionClicked: (AvatarAction) -> Unit = { },
) {
val actions = state.actions
LazyColumn(
modifier = modifier.fillMaxWidth()
) {
@@ -87,12 +88,16 @@ private fun SheetContent(
ListItem(
modifier = Modifier.clickable { onActionClicked(action) },
text = {
Text(text = stringResource(action.titleResId))
Text(
text = stringResource(action.titleResId),
color = if (action.destructive) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary,
)
},
icon = {
Icon(
imageVector = action.icon,
contentDescription = stringResource(action.titleResId),
tint = if (action.destructive) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary,
)
}
)
@@ -102,18 +107,18 @@ private fun SheetContent(
@Preview
@Composable
fun SheetContentLightPreview(@PreviewParameter(ActionListStateProvider::class) state: AvatarActionListState) =
ElementPreviewLight { ContentToPreview(state) }
fun SheetContentLightPreview() =
ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
fun SheetContentDarkPreview(@PreviewParameter(ActionListStateProvider::class) state: AvatarActionListState) =
ElementPreviewDark { ContentToPreview(state) }
fun SheetContentDarkPreview() =
ElementPreviewDark { ContentToPreview() }
@Composable
private fun ContentToPreview(state: AvatarActionListState) {
private fun ContentToPreview() {
AvatarActionListView(
state = state,
actions = persistentListOf(AvatarAction.ChoosePhoto, AvatarAction.TakePhoto, AvatarAction.Remove),
modalBottomSheetState = ModalBottomSheetState(
initialValue = ModalBottomSheetValue.Expanded
),