From 630375704a0d5e170d24b10167c4028703632748 Mon Sep 17 00:00:00 2001 From: Ju Yungyeom Date: Fri, 3 Oct 2025 22:46:43 +0900 Subject: [PATCH 1/2] #692 [bug] Replace Scaffold with BottomSheetScaffold --- .../screen/account/AccountScreen.kt | 101 ++++++++++++------ .../screen/account/SignInNavigation.kt | 4 + .../screen/account/SignUpEmailScreen.kt | 47 ++++---- 3 files changed, 96 insertions(+), 56 deletions(-) diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/account/AccountScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/account/AccountScreen.kt index da8ebb45..5fa42731 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/account/AccountScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/account/AccountScreen.kt @@ -1,21 +1,32 @@ package daily.dayo.presentation.screen.account import android.annotation.SuppressLint +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding -import androidx.compose.material.Scaffold +import androidx.compose.material3.BottomSheetScaffold import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.SheetValue import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import androidx.navigation.compose.NavHost +import daily.dayo.presentation.theme.Dark import daily.dayo.presentation.view.dialog.getBottomSheetDialogState +import kotlinx.coroutines.launch @SuppressLint("UnusedMaterialScaffoldPaddingParameter") @OptIn(ExperimentalMaterial3Api::class) @@ -25,43 +36,65 @@ internal fun AccountScreen( ) { val coroutineScope = rememberCoroutineScope() val snackBarHostState = remember { SnackbarHostState() } + var bottomSheetContent by remember { mutableStateOf<(@Composable () -> Unit)?>(null) } val bottomSheetState = getBottomSheetDialogState() - var bottomSheet: (@Composable () -> Unit)? by remember { mutableStateOf(null) } - val bottomSheetContent: (@Composable () -> Unit) -> Unit = { - bottomSheet = it + val bottomSheetDimAlpha by remember { + derivedStateOf { if (bottomSheetState.bottomSheetState.currentValue == SheetValue.Expanded) 0.6f else 0f } } - Scaffold( - snackbarHost = { SnackbarHost(hostState = snackBarHostState) } - ) { - Scaffold( - bottomBar = { bottomSheet?.let { it() } } - ) { - Scaffold( - content = { innerPadding -> - Box(Modifier.padding(innerPadding)) { - NavHost( - navController = navigator.navController, - startDestination = AccountScreen.SignIn.route - ) { - signInNavGraph( - coroutineScope = coroutineScope, - snackBarHostState = snackBarHostState, - navController = navigator.navController, - onBackClick = { navigator.popBackStack() }, - navigateToSignIn = { navigator.navigateSignIn() }, - navigateToSignInEmail = { navigator.navigateSignInEmail() }, - navigateToResetPassword = { navigator.navigateResetPassword() }, - navigateToSignUpEmail = { navigator.navigateSignUpEmail() }, - navigateToProfileSetting = { navigator.navigateProfileSetting() }, - navigateToRules = { route -> navigator.navigateRules(route) }, - bottomSheetState = bottomSheetState, - bottomSheetContent = bottomSheetContent - ) + val animatedDimAlpha by animateFloatAsState(targetValue = bottomSheetDimAlpha) + + BottomSheetScaffold( + scaffoldState = bottomSheetState, + sheetDragHandle = null, + sheetContent = { + Box(modifier = Modifier.navigationBarsPadding()) { + bottomSheetContent?.invoke() + } + }, + sheetPeekHeight = 0.dp, + snackbarHost = { + SnackbarHost( + hostState = snackBarHostState, + modifier = Modifier.navigationBarsPadding() + ) + }, + content = { innerPadding -> + Box(Modifier.padding(innerPadding)) { + NavHost( + navController = navigator.navController, + startDestination = AccountScreen.SignIn.route + ) { + signInNavGraph( + coroutineScope = coroutineScope, + snackBarHostState = snackBarHostState, + navController = navigator.navController, + onBackClick = { navigator.popBackStack() }, + navigateToSignIn = { navigator.navigateSignIn() }, + navigateToSignInEmail = { navigator.navigateSignInEmail() }, + navigateToResetPassword = { navigator.navigateResetPassword() }, + navigateToSignUpEmail = { navigator.navigateSignUpEmail() }, + navigateToProfileSetting = { navigator.navigateProfileSetting() }, + navigateToRules = { route -> navigator.navigateRules(route) }, + bottomSheetState = bottomSheetState, + bottomSheetContent = { content -> + bottomSheetContent = content } - } - }) + ) + } + + if (animatedDimAlpha > 0f) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Dark.copy(alpha = animatedDimAlpha)) + .clickable(indication = null, interactionSource = remember { MutableInteractionSource() }) { + coroutineScope.launch { bottomSheetState.bottomSheetState.hide() } + } + ) + } + } } - } + ) } sealed class AccountScreen(val route: String) { diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/account/SignInNavigation.kt b/presentation/src/main/java/daily/dayo/presentation/screen/account/SignInNavigation.kt index 500a08d4..56b09ef1 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/account/SignInNavigation.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/account/SignInNavigation.kt @@ -96,6 +96,8 @@ fun NavGraphBuilder.signInNavGraph( SignUpEmailRoute( coroutineScope = coroutineScope, snackBarHostState = snackBarHostState, + bottomSheetState = bottomSheetState, + bottomSheetContent = bottomSheetContent, onBackClick = onBackClick, accountViewModel = hiltViewModel(parentStackEntry), profileSettingViewModel = hiltViewModel(parentStackEntry), @@ -109,6 +111,8 @@ fun NavGraphBuilder.signInNavGraph( SignUpEmailRoute( coroutineScope = coroutineScope, snackBarHostState = snackBarHostState, + bottomSheetState = bottomSheetState, + bottomSheetContent = bottomSheetContent, onBackClick = onBackClick, accountViewModel = hiltViewModel(parentStackEntry), profileSettingViewModel = hiltViewModel(parentStackEntry), diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/account/SignUpEmailScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/account/SignUpEmailScreen.kt index 4746159c..3ddb414b 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/account/SignUpEmailScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/account/SignUpEmailScreen.kt @@ -81,6 +81,8 @@ const val IMAGE_TEMP_FILE_EXTENSION = ".jpg" internal fun SignUpEmailRoute( coroutineScope: CoroutineScope = rememberCoroutineScope(), snackBarHostState: SnackbarHostState, + bottomSheetState: BottomSheetScaffoldState, + bottomSheetContent: (@Composable () -> Unit) -> Unit, onBackClick: () -> Unit = {}, accountViewModel: AccountViewModel = hiltViewModel(), profileSettingViewModel: ProfileSettingViewModel = hiltViewModel(), @@ -88,7 +90,6 @@ internal fun SignUpEmailRoute( ) { val context = LocalContext.current val contentResolver = context.contentResolver - val bottomSheetState = getBottomSheetDialogState() val keyboardController = LocalSoftwareKeyboardController.current val bitmapOptions = BitmapFactory.Options().apply { inPreferredConfig = Bitmap.Config.ARGB_8888 } @@ -224,27 +225,29 @@ internal fun SignUpEmailRoute( message = stringResource(R.string.signup_email_alert_message_loading) ) - ProfileImageBottomSheetDialog( - bottomSheetState, - onClickProfileSelect = { - coroutineScope.launch { - showProfileGallery = true - bottomSheetState.bottomSheetState.hide() - } - }, - onClickProfileCapture = { - coroutineScope.launch { - showProfileCapture = true - bottomSheetState.bottomSheetState.hide() - } - }, - onClickProfileReset = { - profileImgState.value = null - coroutineScope.launch { - bottomSheetState.bottomSheetState.hide() - } - }, - ) + bottomSheetContent { + ProfileImageBottomSheetDialog( + bottomSheetState = bottomSheetState, + onClickProfileSelect = { + coroutineScope.launch { + showProfileGallery = true + bottomSheetState.bottomSheetState.hide() + } + }, + onClickProfileCapture = { + coroutineScope.launch { + showProfileCapture = true + bottomSheetState.bottomSheetState.hide() + } + }, + onClickProfileReset = { + profileImgState.value = null + coroutineScope.launch { + bottomSheetState.bottomSheetState.hide() + } + }, + ) + } } @Composable From b8aec6889aa1b3598caf961b2efd21afc30191f8 Mon Sep 17 00:00:00 2001 From: Ju Yungyeom Date: Thu, 16 Oct 2025 22:37:27 +0900 Subject: [PATCH 2/2] #692 [bug] Migrate to BottomSheetScaffold for bottom sheet --- .../screen/mypage/MyPageEditScreen.kt | 184 ++++++++++-------- 1 file changed, 107 insertions(+), 77 deletions(-) diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/mypage/MyPageEditScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/mypage/MyPageEditScreen.kt index 5cdd68a4..790f1c49 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/mypage/MyPageEditScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/mypage/MyPageEditScreen.kt @@ -11,30 +11,36 @@ import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.launch +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize 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.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Text +import androidx.compose.material3.BottomSheetScaffold import androidx.compose.material3.BottomSheetScaffoldState -import androidx.compose.material3.Divider import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold +import androidx.compose.material3.SheetValue import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf @@ -195,7 +201,7 @@ internal fun MyPageEditScreen( ) } -@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun MyPageEditScreen( profileInfo: MutableState, @@ -208,18 +214,18 @@ private fun MyPageEditScreen( onBackClick: () -> Unit, onConfirmClick: () -> Unit, ) { + val bottomSheetDimAlpha by remember { + derivedStateOf { if (bottomSheetState.bottomSheetState.currentValue == SheetValue.Expanded) 0.6f else 0f } + } + val animatedDimAlpha by animateFloatAsState(targetValue = bottomSheetDimAlpha) val scrollState = rememberScrollState() val coroutineScope = rememberCoroutineScope() - Scaffold( - topBar = { - MyPageEditTopNavigation( - confirmEnabled = nickNameErrorMessage.isEmpty(), - onBackClick = onBackClick, - onConfirmClick = onConfirmClick - ) - }, - bottomBar = { + + BottomSheetScaffold( + scaffoldState = bottomSheetState, + sheetDragHandle = null, + sheetContent = { ProfileImageBottomSheetDialog( bottomSheetState, onClickProfileSelect, @@ -227,76 +233,100 @@ private fun MyPageEditScreen( onClickProfileReset ) }, - content = { contentPadding -> - Column( - modifier = Modifier - .background(DayoTheme.colorScheme.background) - .fillMaxSize() - .verticalScroll(scrollState) - .padding(contentPadding) - .padding(vertical = 32.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - val placeholderResId = remember { R.drawable.ic_profile_default_user_profile } - BadgeRoundImageView( - context = LocalContext.current, - imageUrl = modifiedProfileImage, - imageDescription = "my page profile image", - placeholderResId = placeholderResId, - contentModifier = Modifier - .size(100.dp) - .aspectRatio(1f) - .clip(RoundedCornerShape(percent = 50)) - .clickableSingle( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = { - coroutineScope.launch { bottomSheetState.bottomSheetState.expand() } - } - ) - ) - - Spacer(modifier = Modifier.height(36.dp)) - - DayoTextField( - value = profileInfo.value?.nickname ?: "", - onValueChange = { textValue -> - profileInfo.value = profileInfo.value?.copy( - nickname = textValue + content = { + Box { + Scaffold( + modifier = Modifier.navigationBarsPadding(), + topBar = { + MyPageEditTopNavigation( + confirmEnabled = nickNameErrorMessage.isEmpty(), + onBackClick = onBackClick, + onConfirmClick = onConfirmClick ) - }, - label = stringResource(id = R.string.nickname), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 18.dp), - isError = nickNameErrorMessage.isNotEmpty(), - errorMessage = nickNameErrorMessage - ) - - Spacer(modifier = Modifier.height(32.dp)) - - Column(modifier = Modifier.padding(horizontal = 18.dp)) { - Text( - text = stringResource(id = R.string.email), - style = DayoTheme.typography.caption3.copy( - color = Gray4_C5CAD2, - fontWeight = FontWeight.SemiBold + } + ) { contentPadding -> + + Column( + modifier = Modifier + .background(DayoTheme.colorScheme.background) + .fillMaxSize() + .verticalScroll(scrollState) + .padding(contentPadding) + .padding(vertical = 32.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + val placeholderResId = remember { R.drawable.ic_profile_default_user_profile } + BadgeRoundImageView( + context = LocalContext.current, + imageUrl = modifiedProfileImage, + imageDescription = "my page profile image", + placeholderResId = placeholderResId, + contentModifier = Modifier + .size(100.dp) + .aspectRatio(1f) + .clip(RoundedCornerShape(percent = 50)) + .clickableSingle( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = { + coroutineScope.launch { bottomSheetState.bottomSheetState.expand() } + } + ) ) - ) - Text( - text = profileInfo.value?.email ?: "", - modifier = Modifier.padding(vertical = 8.dp), - style = DayoTheme.typography.b4.copy( - color = Gray2_767B83, - fontWeight = FontWeight.SemiBold + Spacer(modifier = Modifier.height(36.dp)) + + DayoTextField( + value = profileInfo.value?.nickname ?: "", + onValueChange = { textValue -> + profileInfo.value = profileInfo.value?.copy( + nickname = textValue + ) + }, + label = stringResource(id = R.string.nickname), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 18.dp), + isError = nickNameErrorMessage.isNotEmpty(), + errorMessage = nickNameErrorMessage ) - ) - Divider( - modifier = Modifier.fillMaxWidth(), - thickness = 1.dp, - color = Gray6_F0F1F3 + Spacer(modifier = Modifier.height(32.dp)) + + Column(modifier = Modifier.padding(horizontal = 18.dp)) { + Text( + text = stringResource(id = R.string.email), + style = DayoTheme.typography.caption3.copy( + color = Gray4_C5CAD2, + fontWeight = FontWeight.SemiBold + ) + ) + + Text( + text = profileInfo.value?.email ?: "", + modifier = Modifier.padding(vertical = 8.dp), + style = DayoTheme.typography.b4.copy( + color = Gray2_767B83, + fontWeight = FontWeight.SemiBold + ) + ) + + HorizontalDivider( + modifier = Modifier.fillMaxWidth(), + thickness = 1.dp, + color = Gray6_F0F1F3 + ) + } + } + } + if (animatedDimAlpha > 0f) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Dark.copy(alpha = animatedDimAlpha)) + .clickable(indication = null, interactionSource = remember { MutableInteractionSource() }) { + coroutineScope.launch { bottomSheetState.bottomSheetState.hide() } + } ) } }