From 193081a55468307a26c5d08118f932b7d8d00de6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 15 May 2024 16:20:47 +0200 Subject: [PATCH] Migrate AvatarActionBottomSheet to Material3 BottomSheet Also correctly handle the back press when this bottom sheet is opened, previously it was leaving the room edition screen. ModalBottomSheetLayout can now be deleted. --- .../impl/configureroom/ConfigureRoomView.kt | 19 +-- .../user/editprofile/EditUserProfileView.kt | 20 +-- .../impl/edit/RoomDetailsEditView.kt | 21 +-- .../components/ModalBottomSheetLayout.kt | 132 ------------------ .../ui/components/AvatarActionBottomSheet.kt | 69 +++++---- 5 files changed, 56 insertions(+), 205 deletions(-) delete mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheetLayout.kt diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt index 5852103b92..813378e497 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt @@ -29,12 +29,10 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.selection.selectableGroup import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.ModalBottomSheetValue -import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalFocusManager @@ -63,9 +61,7 @@ import io.element.android.libraries.matrix.ui.components.SelectedUsersRowList import io.element.android.libraries.matrix.ui.components.UnsavedAvatar import io.element.android.libraries.permissions.api.PermissionsView import io.element.android.libraries.ui.strings.CommonStrings -import kotlinx.coroutines.launch -@OptIn(ExperimentalMaterialApi::class) @Composable fun ConfigureRoomView( state: ConfigureRoomState, @@ -73,17 +69,12 @@ fun ConfigureRoomView( onRoomCreated: (RoomId) -> Unit, modifier: Modifier = Modifier, ) { - val coroutineScope = rememberCoroutineScope() val focusManager = LocalFocusManager.current - val itemActionsBottomSheetState = rememberModalBottomSheetState( - initialValue = ModalBottomSheetValue.Hidden, - ) + val isAvatarActionsSheetVisible = remember { mutableStateOf(false) } fun onAvatarClicked() { focusManager.clearFocus() - coroutineScope.launch { - itemActionsBottomSheetState.show() - } + isAvatarActionsSheetVisible.value = true } Scaffold( @@ -143,7 +134,7 @@ fun ConfigureRoomView( AvatarActionBottomSheet( actions = state.avatarActions, - modalBottomSheetState = itemActionsBottomSheetState, + isVisible = isAvatarActionsSheetVisible, onActionSelected = { state.eventSink(ConfigureRoomEvents.HandleAvatarAction(it)) } ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt index 58d2afb77a..fb8318653e 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt @@ -25,12 +25,10 @@ import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.ModalBottomSheetValue -import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalFocusManager @@ -57,9 +55,8 @@ import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet import io.element.android.libraries.matrix.ui.components.EditableAvatarView import io.element.android.libraries.permissions.api.PermissionsView import io.element.android.libraries.ui.strings.CommonStrings -import kotlinx.coroutines.launch -@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable fun EditUserProfileView( state: EditUserProfileState, @@ -67,17 +64,12 @@ fun EditUserProfileView( onProfileEdited: () -> Unit, modifier: Modifier = Modifier, ) { - val coroutineScope = rememberCoroutineScope() val focusManager = LocalFocusManager.current - val itemActionsBottomSheetState = rememberModalBottomSheetState( - initialValue = ModalBottomSheetValue.Hidden, - ) + val isAvatarActionsSheetVisible = remember { mutableStateOf(false) } fun onAvatarClicked() { focusManager.clearFocus() - coroutineScope.launch { - itemActionsBottomSheetState.show() - } + isAvatarActionsSheetVisible.value = true } Scaffold( @@ -140,7 +132,7 @@ fun EditUserProfileView( AvatarActionBottomSheet( actions = state.avatarActions, - modalBottomSheetState = itemActionsBottomSheetState, + isVisible = isAvatarActionsSheetVisible, onActionSelected = { state.eventSink(EditUserProfileEvents.HandleAvatarAction(it)) } ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditView.kt index 6e4c0bea25..5885a5092b 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditView.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) +@file:OptIn(ExperimentalMaterial3Api::class) package io.element.android.features.roomdetails.impl.edit @@ -29,13 +29,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.ModalBottomSheetValue -import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource @@ -61,9 +59,7 @@ import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet import io.element.android.libraries.matrix.ui.components.EditableAvatarView import io.element.android.libraries.permissions.api.PermissionsView import io.element.android.libraries.ui.strings.CommonStrings -import kotlinx.coroutines.launch -@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @Composable fun RoomDetailsEditView( state: RoomDetailsEditState, @@ -71,17 +67,12 @@ fun RoomDetailsEditView( onRoomEdited: () -> Unit, modifier: Modifier = Modifier, ) { - val coroutineScope = rememberCoroutineScope() val focusManager = LocalFocusManager.current - val itemActionsBottomSheetState = rememberModalBottomSheetState( - initialValue = ModalBottomSheetValue.Hidden, - ) + val isAvatarActionsSheetVisible = remember { mutableStateOf(false) } fun onAvatarClicked() { focusManager.clearFocus() - coroutineScope.launch { - itemActionsBottomSheetState.show() - } + isAvatarActionsSheetVisible.value = true } Scaffold( @@ -167,7 +158,7 @@ fun RoomDetailsEditView( AvatarActionBottomSheet( actions = state.avatarActions, - modalBottomSheetState = itemActionsBottomSheetState, + isVisible = isAvatarActionsSheetVisible, onActionSelected = { state.eventSink(RoomDetailsEditEvents.HandleAvatarAction(it)) } ) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheetLayout.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheetLayout.kt deleted file mode 100644 index 8dd63d1cc6..0000000000 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheetLayout.kt +++ /dev/null @@ -1,132 +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. - */ - -// This is actually expected, as we should remove this component soon and use ModalBottomSheet instead -@file:Suppress("UsingMaterialAndMaterial3Libraries") - -package io.element.android.libraries.designsystem.theme.components - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ColumnScope -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CornerSize -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.ModalBottomSheetDefaults -import androidx.compose.material.ModalBottomSheetState -import androidx.compose.material.ModalBottomSheetValue -import androidx.compose.material.rememberModalBottomSheetState -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.contentColorFor -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage -import io.element.android.libraries.designsystem.modifiers.applyIf -import io.element.android.libraries.designsystem.preview.ElementPreviewDark -import io.element.android.libraries.designsystem.preview.ElementPreviewLight -import io.element.android.libraries.designsystem.preview.PreviewGroup - -@OptIn(ExperimentalMaterialApi::class) -@Composable -fun ModalBottomSheetLayout( - sheetContent: @Composable ColumnScope.() -> Unit, - modifier: Modifier = Modifier, - sheetState: ModalBottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden), - sheetShape: Shape = MaterialTheme.shapes.large.copy(bottomStart = CornerSize(0.dp), bottomEnd = CornerSize(0.dp)), - sheetElevation: Dp = ModalBottomSheetDefaults.Elevation, - sheetBackgroundColor: Color = MaterialTheme.colorScheme.surface, - sheetContentColor: Color = contentColorFor(sheetBackgroundColor), - scrimColor: Color = ModalBottomSheetDefaults.scrimColor, - displayHandle: Boolean = false, - useSystemPadding: Boolean = true, - content: @Composable () -> Unit = {} -) { - androidx.compose.material.ModalBottomSheetLayout( - sheetContent = { - Column( - Modifier.fillMaxWidth() - .applyIf(useSystemPadding, ifTrue = { - navigationBarsPadding() - }) - ) { - if (displayHandle) { - Spacer(modifier = Modifier.height(16.dp)) - Box( - modifier = Modifier - .background(MaterialTheme.colorScheme.onSurfaceVariant, RoundedCornerShape(2.dp)) - .size(width = 32.dp, height = 4.dp) - .align(Alignment.CenterHorizontally), - ) - Spacer(modifier = Modifier.height(24.dp)) - } - sheetContent() - } - }, - modifier = modifier, - sheetState = sheetState, - sheetShape = sheetShape, - sheetElevation = sheetElevation, - sheetBackgroundColor = sheetBackgroundColor, - sheetContentColor = sheetContentColor, - scrimColor = scrimColor, - content = content, - ) -} - -@Preview(group = PreviewGroup.BottomSheets) -@Composable -internal fun ModalBottomSheetLayoutLightPreview() = - ElementPreviewLight { ContentToPreview() } - -@Preview(group = PreviewGroup.BottomSheets) -@Composable -internal fun ModalBottomSheetLayoutDarkPreview() = - ElementPreviewDark { ContentToPreview() } - -@OptIn(ExperimentalMaterialApi::class) -@ExcludeFromCoverage -@Composable -private fun ContentToPreview() { - ModalBottomSheetLayout( - modifier = Modifier.height(140.dp), - displayHandle = true, - sheetState = ModalBottomSheetState(ModalBottomSheetValue.Expanded, density = LocalDensity.current), - sheetContent = { - Text( - text = "Sheet Content", - modifier = Modifier - .padding(start = 16.dp, end = 16.dp, bottom = 20.dp) - .background(color = Color.Green) - ) - } - ) { - Text(text = "Content", modifier = Modifier.background(color = Color.Red)) - } -} diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarActionBottomSheet.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarActionBottomSheet.kt index fd154928ef..0d92c48375 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarActionBottomSheet.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarActionBottomSheet.kt @@ -14,25 +14,26 @@ * limitations under the License. */ -@file:OptIn(ExperimentalMaterialApi::class) -@file:Suppress("UsingMaterialAndMaterial3Libraries") +@file:OptIn(ExperimentalMaterial3Api::class) package io.element.android.libraries.matrix.ui.components +import androidx.activity.compose.BackHandler import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.ModalBottomSheetState -import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.components.list.ListItemContent @@ -41,42 +42,53 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.IconSource import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.ListItemStyle -import io.element.android.libraries.designsystem.theme.components.ModalBottomSheetLayout +import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.hide import io.element.android.libraries.matrix.ui.media.AvatarAction import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -import kotlinx.coroutines.launch +@OptIn(ExperimentalMaterial3Api::class) @Composable fun AvatarActionBottomSheet( actions: ImmutableList, - modalBottomSheetState: ModalBottomSheetState, + isVisible: MutableState, onActionSelected: (action: AvatarAction) -> Unit, modifier: Modifier = Modifier, ) { val coroutineScope = rememberCoroutineScope() + val sheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ) + + BackHandler(enabled = isVisible.value) { + sheetState.hide(coroutineScope, then = { isVisible.value = false }) + } + fun onItemActionClicked(itemAction: AvatarAction) { onActionSelected(itemAction) - coroutineScope.launch { - modalBottomSheetState.hide() - } + sheetState.hide(coroutineScope, then = { isVisible.value = false }) } - ModalBottomSheetLayout( - modifier = modifier, - sheetState = modalBottomSheetState, - displayHandle = true, - sheetContent = { - AvatarActionBottomSheetContent( - actions = actions, - onActionClicked = ::onItemActionClicked, - modifier = Modifier - .navigationBarsPadding() - .imePadding() - ) - } - ) + if (isVisible.value) { + ModalBottomSheet( + onDismissRequest = { + sheetState.hide(coroutineScope, then = { isVisible.value = false }) + }, + modifier = modifier, + sheetState = sheetState, + content = { + AvatarActionBottomSheetContent( + actions = actions, + onActionClicked = ::onItemActionClicked, + modifier = Modifier + .navigationBarsPadding() + .imePadding() + ) + } + ) + } } @Composable @@ -115,10 +127,7 @@ private fun AvatarActionBottomSheetContent( internal fun AvatarActionBottomSheetPreview() = ElementPreview { AvatarActionBottomSheet( actions = persistentListOf(AvatarAction.TakePhoto, AvatarAction.ChoosePhoto, AvatarAction.Remove), - modalBottomSheetState = ModalBottomSheetState( - initialValue = ModalBottomSheetValue.Expanded, - density = LocalDensity.current, - ), + isVisible = remember { mutableStateOf(true) }, onActionSelected = { }, ) }