diff --git a/presentation/build.gradle b/presentation/build.gradle index 35dff526..053d552f 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -78,6 +78,7 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.7.1' implementation 'com.google.android.material:material:1.13.0' + implementation 'androidx.compose.runtime:runtime:1.9.5' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0' diff --git a/presentation/src/main/java/daily/dayo/presentation/common/BottomSheetController.kt b/presentation/src/main/java/daily/dayo/presentation/common/BottomSheetController.kt new file mode 100644 index 00000000..cb8d0308 --- /dev/null +++ b/presentation/src/main/java/daily/dayo/presentation/common/BottomSheetController.kt @@ -0,0 +1,31 @@ +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue + +@Stable +class BottomSheetController { + var sheetContent: @Composable (() -> Unit) by mutableStateOf({}) + private set + + var isVisible by mutableStateOf(false) + private set + + fun setContent(content: @Composable () -> Unit) { + sheetContent = content + } + + fun show() { + isVisible = true + } + + fun hide() { + isVisible = false + } +} + +val LocalBottomSheetController = compositionLocalOf { + error("No BottomSheetController provided") +} 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 ce527ee4..2f9f808b 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,32 +1,23 @@ package daily.dayo.presentation.screen.account -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource +import BottomSheetController +import LocalBottomSheetController 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.foundation.layout.systemBarsPadding -import androidx.compose.material3.BottomSheetScaffold import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.SheetValue +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.CompositionLocalProvider 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 @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -35,30 +26,19 @@ internal fun AccountScreen( ) { val coroutineScope = rememberCoroutineScope() val snackBarHostState = remember { SnackbarHostState() } - var bottomSheetContent by remember { mutableStateOf<(@Composable () -> Unit)?>(null) } - val bottomSheetState = getBottomSheetDialogState() - val bottomSheetDimAlpha by remember { - derivedStateOf { if (bottomSheetState.bottomSheetState.currentValue == SheetValue.Expanded) 0.6f else 0f } - } - val animatedDimAlpha by animateFloatAsState(targetValue = bottomSheetDimAlpha) + val bottomSheetController = remember { BottomSheetController() } + val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) - BottomSheetScaffold( - modifier = Modifier.systemBarsPadding(), - scaffoldState = bottomSheetState, - sheetDragHandle = null, - sheetContent = { - Box(modifier = Modifier.navigationBarsPadding()) { - bottomSheetContent?.invoke() + CompositionLocalProvider(LocalBottomSheetController provides bottomSheetController) { + Scaffold( + modifier = Modifier.systemBarsPadding(), + snackbarHost = { + SnackbarHost( + hostState = snackBarHostState, + modifier = Modifier.navigationBarsPadding() + ) } - }, - sheetPeekHeight = 0.dp, - snackbarHost = { - SnackbarHost( - hostState = snackBarHostState, - modifier = Modifier.navigationBarsPadding() - ) - }, - content = { innerPadding -> + ) { innerPadding -> Box(Modifier.padding(innerPadding)) { NavHost( navController = navigator.navController, @@ -74,27 +54,23 @@ internal fun AccountScreen( navigateToResetPassword = { navigator.navigateResetPassword() }, navigateToSignUpEmail = { navigator.navigateSignUpEmail() }, navigateToProfileSetting = { navigator.navigateProfileSetting() }, - navigateToRules = { route -> navigator.navigateRules(route) }, - bottomSheetState = bottomSheetState, - bottomSheetContent = { content -> - bottomSheetContent = content - } + navigateToRules = { route -> navigator.navigateRules(route) } ) } - if (animatedDimAlpha > 0f) { - Box( - modifier = Modifier - .fillMaxSize() - .background(Dark.copy(alpha = animatedDimAlpha)) - .clickable(indication = null, interactionSource = remember { MutableInteractionSource() }) { - coroutineScope.launch { bottomSheetState.bottomSheetState.hide() } - } - ) + if (bottomSheetController.isVisible) { + ModalBottomSheet( + onDismissRequest = { bottomSheetController.hide() }, + modifier = Modifier.navigationBarsPadding(), + sheetState = bottomSheetState, + dragHandle = null + ) { + bottomSheetController.sheetContent() + } } } } - ) + } } sealed class AccountScreen(val route: String) { diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/account/SetProfileSetupView.kt b/presentation/src/main/java/daily/dayo/presentation/screen/account/SetProfileSetupView.kt index b68f105c..b6aca1b9 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/account/SetProfileSetupView.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/account/SetProfileSetupView.kt @@ -1,5 +1,6 @@ package daily.dayo.presentation.screen.account +import LocalBottomSheetController import android.content.Context import android.graphics.Bitmap import androidx.compose.foundation.interaction.MutableInteractionSource @@ -11,13 +12,11 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.BottomSheetScaffoldState import androidx.compose.material3.ExperimentalMaterial3Api 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.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -30,17 +29,12 @@ import daily.dayo.presentation.common.extension.clickableSingle import daily.dayo.presentation.screen.account.model.NicknameCertificationState import daily.dayo.presentation.view.BadgeRoundImageView import daily.dayo.presentation.view.DayoTextField -import daily.dayo.presentation.view.dialog.getBottomSheetDialogState -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Preview @Composable fun SetProfileSetupView( context: Context = LocalContext.current, - bottomSheetState: BottomSheetScaffoldState = getBottomSheetDialogState(), - coroutineScope: CoroutineScope = rememberCoroutineScope(), isNextButtonEnabled: MutableState = remember { mutableStateOf(false) }, isNextButtonClickable: MutableState = remember { mutableStateOf(false) }, nicknameState: MutableState = remember { mutableStateOf("") }, @@ -52,6 +46,7 @@ fun SetProfileSetupView( requestIsNicknameDuplicate: (String) -> Unit = {}, profileImg: Bitmap? = null, ) { + val bottomSheetController = LocalBottomSheetController.current val placeholderResId = remember { R.drawable.ic_profile_default } val interactionSource = remember { MutableInteractionSource() } val profileImageClickModifier = remember { @@ -62,9 +57,7 @@ fun SetProfileSetupView( .clickableSingle( interactionSource = interactionSource, indication = null, - onClick = { - coroutineScope.launch { bottomSheetState.bottomSheetState.expand() } - } + onClick = { bottomSheetController.show() } ) } 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 56b09ef1..ef7ebc6f 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 @@ -1,9 +1,7 @@ package daily.dayo.presentation.screen.account -import androidx.compose.material3.BottomSheetScaffoldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController @@ -47,9 +45,7 @@ fun NavGraphBuilder.signInNavGraph( navigateToResetPassword: () -> Unit, navigateToSignUpEmail: () -> Unit, navigateToRules: (RuleType) -> Unit, - navigateToProfileSetting: () -> Unit, - bottomSheetState: BottomSheetScaffoldState, - bottomSheetContent: (@Composable () -> Unit) -> Unit, + navigateToProfileSetting: () -> Unit ) { composable(route = SignInRoute.route) { val parentStackEntry = remember(it) { @@ -96,8 +92,6 @@ fun NavGraphBuilder.signInNavGraph( SignUpEmailRoute( coroutineScope = coroutineScope, snackBarHostState = snackBarHostState, - bottomSheetState = bottomSheetState, - bottomSheetContent = bottomSheetContent, onBackClick = onBackClick, accountViewModel = hiltViewModel(parentStackEntry), profileSettingViewModel = hiltViewModel(parentStackEntry), @@ -111,8 +105,6 @@ 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 3d2cf2f2..c42bbbed 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 @@ -1,5 +1,6 @@ package daily.dayo.presentation.screen.account +import LocalBottomSheetController import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory @@ -17,11 +18,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.Text -import androidx.compose.material3.BottomSheetScaffoldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue @@ -57,7 +58,6 @@ import daily.dayo.presentation.view.Loading import daily.dayo.presentation.view.NoRippleIconButton import daily.dayo.presentation.view.TopNavigation import daily.dayo.presentation.view.dialog.ProfileImageBottomSheetDialog -import daily.dayo.presentation.view.dialog.getBottomSheetDialogState import daily.dayo.presentation.viewmodel.AccountViewModel import daily.dayo.presentation.viewmodel.AccountViewModel.Companion.EMAIL_CERTIFICATE_AUTH_CODE_INITIAL import daily.dayo.presentation.viewmodel.AccountViewModel.Companion.SIGN_UP_EMAIL_CERTIFICATE_AUTH_CODE_FAIL @@ -81,8 +81,6 @@ 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(), @@ -91,8 +89,7 @@ internal fun SignUpEmailRoute( val context = LocalContext.current val contentResolver = context.contentResolver val keyboardController = LocalSoftwareKeyboardController.current - val bitmapOptions = - BitmapFactory.Options().apply { inPreferredConfig = Bitmap.Config.ARGB_8888 } + val bitmapOptions = BitmapFactory.Options().apply { inPreferredConfig = Bitmap.Config.ARGB_8888 } var signUpStep by remember { mutableStateOf(startSignUpStep) } val isEmailDuplicate by accountViewModel.isEmailDuplicate.collectAsStateWithLifecycle() @@ -136,6 +133,34 @@ internal fun SignUpEmailRoute( } ) + // bottom sheet + val bottomSheetController = LocalBottomSheetController.current + val bottomSheetContent: @Composable () -> Unit = remember { + { + ProfileImageBottomSheetDialog( + onClickProfileSelect = { + showProfileGallery = true + bottomSheetController.hide() + }, + onClickProfileCapture = { + showProfileCapture = true + bottomSheetController.hide() + + }, + onClickProfileReset = { + profileImgState.value = null + bottomSheetController.hide() + } + ) + } + } + DisposableEffect(Unit) { + bottomSheetController.setContent(bottomSheetContent) + onDispose { + bottomSheetController.hide() + } + } + if (showProfileGallery) { openGallery() showProfileGallery = false @@ -148,8 +173,6 @@ internal fun SignUpEmailRoute( SignUpEmailScreen( context = context, - coroutineScope = coroutineScope, - bottomSheetState = bottomSheetState, hideKeyboard = { keyboardController?.hide() }, onBackClick = onBackClick, requestIsEmailDuplicate = { accountViewModel.requestCheckEmailDuplicate(it) }, @@ -224,30 +247,6 @@ internal fun SignUpEmailRoute( isVisible = (signUpStatus == Status.LOADING || updateProfileStatus == Status.LOADING), message = stringResource(R.string.signup_email_alert_message_loading) ) - - 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 @@ -280,8 +279,6 @@ fun SignUpEmailTitleLayout( @Preview fun SignUpEmailScreen( context: Context = LocalContext.current, - coroutineScope: CoroutineScope = rememberCoroutineScope(), - bottomSheetState: BottomSheetScaffoldState = getBottomSheetDialogState(), hideKeyboard: () -> Unit = {}, onBackClick: () -> Unit = {}, requestIsEmailDuplicate: (email: String) -> Unit = {}, @@ -538,8 +535,6 @@ fun SignUpEmailScreen( SignUpStep.PROFILE_SETUP -> { SetProfileSetupView( - bottomSheetState = bottomSheetState, - coroutineScope = coroutineScope, isNextButtonEnabled = isNextButtonEnabled, isNextButtonClickable = isNextButtonClickable, nicknameState = nicknameState, diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/account/WithdrawScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/account/WithdrawScreen.kt index bdc121c0..657f1a90 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/account/WithdrawScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/account/WithdrawScreen.kt @@ -1,5 +1,7 @@ package daily.dayo.presentation.screen.account +import BottomSheetController +import LocalBottomSheetController import android.content.Context import android.content.Intent import androidx.activity.compose.BackHandler @@ -25,24 +27,21 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField -import androidx.compose.material3.BottomSheetScaffoldState import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold -import androidx.compose.material3.SheetValue import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect 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.rememberCoroutineScope import androidx.compose.runtime.setValue -import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -68,6 +67,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil.compose.AsyncImage import coil.request.ImageRequest +import daily.dayo.domain.model.WithdrawalReason import daily.dayo.presentation.R import daily.dayo.presentation.activity.LoginActivity import daily.dayo.presentation.activity.MainActivity @@ -88,9 +88,6 @@ import daily.dayo.presentation.view.NoRippleIconButton import daily.dayo.presentation.view.TopNavigation import daily.dayo.presentation.view.dialog.ConfirmDialog import daily.dayo.presentation.viewmodel.AccountViewModel -import daily.dayo.domain.model.WithdrawalReason -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch const val OTHER_REASON_TEXT_MAX_LENGTH = 100 @@ -98,26 +95,27 @@ const val OTHER_REASON_TEXT_MAX_LENGTH = 100 @Composable fun WithdrawScreen( onBackClick: () -> Unit, - bottomSheetState: BottomSheetScaffoldState, - bottomSheetContent: (@Composable () -> Unit) -> Unit, snackBarHostState: SnackbarHostState = remember { SnackbarHostState() }, accountViewModel: AccountViewModel = hiltViewModel(), onNavigateToHome: () -> Unit = {}, onNavigateToMyPage: () -> Unit = {}, ) { val context = LocalContext.current - val coroutineScope = rememberCoroutineScope() val keyboardController = LocalSoftwareKeyboardController.current val focusManager = LocalFocusManager.current val withdrawStep = remember { mutableStateOf(WithdrawStep.REASON_SELECT) } var selectedReason by remember { mutableStateOf(null) } + + val bottomSheetController = LocalBottomSheetController.current val onClickWithdrawContinue: (WithdrawalReason) -> Unit = { reason -> - coroutineScope.launch { bottomSheetState.bottomSheetState.expand() } - bottomSheetContent { + bottomSheetController.show() + selectedReason = SelectedWithdrawReason(reason) + } + val bottomSheetContent: @Composable () -> Unit = remember { + { selectedReason?.let { selected -> WithdrawContinueBottomSheetDialog( - coroutineScope = coroutineScope, - bottomSheetState = bottomSheetState, + bottomSheetController = bottomSheetController, keyboardController = keyboardController, focusManager = focusManager, setWithdrawStep = { withdrawStep.value = it }, @@ -126,32 +124,27 @@ fun WithdrawScreen( snackBarHostState = snackBarHostState, accountViewModel = accountViewModel, onNavigateToHome = onNavigateToHome, - onNavigateToMyPage = onNavigateToMyPage, + onNavigateToMyPage = onNavigateToMyPage ) } } } - LaunchedEffect(bottomSheetState) { - snapshotFlow { bottomSheetState.bottomSheetState.currentValue } - .collect { value -> - if (value == SheetValue.Hidden) { - focusManager.clearFocus(force = true) - keyboardController?.hide() - if (withdrawStep.value == WithdrawStep.REASON_SELECT) { - selectedReason = null - } - } - } + DisposableEffect(Unit) { + bottomSheetController.setContent(bottomSheetContent) + onDispose { + keyboardController?.hide() + focusManager.clearFocus() + bottomSheetController.hide() + } } - // BottomSheet가 열려있을 때의 뒤로가기 처리 - if (bottomSheetState.bottomSheetState.currentValue == SheetValue.Expanded) { - BackHandler { - coroutineScope.launch { - keyboardController?.hide() - focusManager.clearFocus() - bottomSheetState.bottomSheetState.hide() + LaunchedEffect(bottomSheetController) { + if (!bottomSheetController.isVisible) { + focusManager.clearFocus(force = true) + keyboardController?.hide() + if (withdrawStep.value == WithdrawStep.REASON_SELECT) { + selectedReason = null } } } @@ -324,8 +317,7 @@ private fun WithdrawReasonItem( @OptIn(ExperimentalMaterial3Api::class) @Composable fun WithdrawContinueBottomSheetDialog( - coroutineScope: CoroutineScope, - bottomSheetState: BottomSheetScaffoldState, + bottomSheetController: BottomSheetController, keyboardController: SoftwareKeyboardController?, focusManager: FocusManager, setWithdrawStep: (WithdrawStep) -> Unit, @@ -384,10 +376,8 @@ fun WithdrawContinueBottomSheetDialog( if (selected.otherReasonText.isBlank()) { return@WithdrawHoldBottomSheet } else { - coroutineScope.launch { - keyboardController?.hide() - focusManager.clearFocus() - } + keyboardController?.hide() + focusManager.clearFocus() } } @@ -395,18 +385,13 @@ fun WithdrawContinueBottomSheetDialog( } } - - coroutineScope.launch { - setWithdrawStep(WithdrawStep.CONFIRM) - bottomSheetState.bottomSheetState.hide() - } + setWithdrawStep(WithdrawStep.CONFIRM) + bottomSheetController.hide() }, onCancel = { - coroutineScope.launch { - keyboardController?.hide() - focusManager.clearFocus() - bottomSheetState.bottomSheetState.hide() - } + keyboardController?.hide() + focusManager.clearFocus() + bottomSheetController.hide() when (selected.reason) { WithdrawalReason.WANT_TO_DELETE_HISTORY -> { onNavigateToMyPage() @@ -448,7 +433,7 @@ fun WithdrawHoldBottomSheet( ) val isOtherReason = (content.reasonTextResId == R.string.withdraw_reason_other) var otherReasonContentValue by remember { mutableStateOf(TextFieldValue(otherReasonText)) } - + // otherReasonText가 변경될 때 로컬 상태도 동기화 LaunchedEffect(otherReasonText) { if (otherReasonContentValue.text != otherReasonText) { diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/feed/FeedNavigation.kt b/presentation/src/main/java/daily/dayo/presentation/screen/feed/FeedNavigation.kt index 2e8ac3a8..4e03667b 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/feed/FeedNavigation.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/feed/FeedNavigation.kt @@ -1,9 +1,7 @@ package daily.dayo.presentation.screen.feed -import androidx.compose.material3.BottomSheetScaffoldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.Composable import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable @@ -15,8 +13,6 @@ fun NavGraphBuilder.feedNavGraph( onProfileClick: (String) -> Unit, onPostLikeUsersClick: (Long) -> Unit, onPostHashtagClick: (String) -> Unit, - bottomSheetState: BottomSheetScaffoldState, - bottomSheetContent: (@Composable () -> Unit) -> Unit, ) { composable(FeedRoute.route) { FeedScreen( @@ -25,9 +21,7 @@ fun NavGraphBuilder.feedNavGraph( onPostClick = onPostClick, onProfileClick = onProfileClick, onPostLikeUsersClick = onPostLikeUsersClick, - onPostHashtagClick = onPostHashtagClick, - bottomSheetState = bottomSheetState, - bottomSheetContent = bottomSheetContent + onPostHashtagClick = onPostHashtagClick ) } } diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/feed/FeedScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/feed/FeedScreen.kt index ccd7d6c3..b2ce5c71 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/feed/FeedScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/feed/FeedScreen.kt @@ -1,5 +1,6 @@ package daily.dayo.presentation.screen.feed +import LocalBottomSheetController import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -14,12 +15,12 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState -import androidx.compose.material3.BottomSheetScaffoldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -46,6 +47,7 @@ import daily.dayo.presentation.view.CategoryHorizontalGroup import daily.dayo.presentation.view.FeedPostView import daily.dayo.presentation.view.FilledButton import daily.dayo.presentation.view.TopNavigation +import daily.dayo.presentation.view.dialog.CommentBottomSheetDialog import daily.dayo.presentation.viewmodel.FeedViewModel @OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @@ -57,8 +59,6 @@ fun FeedScreen( onProfileClick: (String) -> Unit, onPostLikeUsersClick: (Long) -> Unit, onPostHashtagClick: (String) -> Unit, - bottomSheetState: BottomSheetScaffoldState, - bottomSheetContent: (@Composable () -> Unit) -> Unit, feedViewModel: FeedViewModel = hiltViewModel() ) { val feedPosts = feedViewModel.feedPosts.collectAsLazyPagingItems() @@ -82,6 +82,44 @@ fun FeedScreen( feedViewModel.loadFeedPosts() } + // bottom sheet + val bottomSheetController = LocalBottomSheetController.current + var currentCommentPostId by remember { mutableStateOf(null) } + val onClickComment: (Long) -> Unit = { postId -> + currentCommentPostId = postId + bottomSheetController.show() + } + val bottomSheetContent: @Composable () -> Unit = remember(currentCommentPostId) { + { + currentCommentPostId?.let { postId -> + CommentBottomSheetDialog( + postId = postId, + snackBarHostState = snackBarHostState, + onClickProfile = { memberId -> + bottomSheetController.hide() + onProfileClick(memberId) + }, + onClickClose = { + bottomSheetController.hide() + } + ) + } + } + } + + LaunchedEffect(currentCommentPostId) { + if (currentCommentPostId != null) { + bottomSheetController.setContent(bottomSheetContent) + } + } + + DisposableEffect(Unit) { + bottomSheetController.setContent(bottomSheetContent) + onDispose { + bottomSheetController.hide() + } + } + Box( modifier = Modifier .fillMaxSize() @@ -94,9 +132,7 @@ fun FeedScreen( Text( text = stringResource(id = R.string.feed), modifier = Modifier.padding(start = 18.dp), - style = DayoTheme.typography.h1.copy( - color = Dark - ) + style = DayoTheme.typography.h1.copy(color = Dark) ) } ) @@ -133,8 +169,7 @@ fun FeedScreen( onClickBookmark = { feedViewModel.toggleBookmarkPost(post = post) }, onPostLikeUsersClick = onPostLikeUsersClick, onPostHashtagClick = onPostHashtagClick, - bottomSheetState = bottomSheetState, - bottomSheetContent = bottomSheetContent + onClickComment = { post.postId?.let { onClickComment(it) } }, ) } } diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNavigation.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNavigation.kt index d1db9dc3..73fd42bf 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNavigation.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNavigation.kt @@ -1,14 +1,9 @@ package daily.dayo.presentation.screen.home -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.ModalBottomSheetState -import androidx.compose.material3.BottomSheetScaffoldState import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.runtime.Composable import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable -import kotlinx.coroutines.CoroutineScope fun NavController.navigateHome() { this.navigate(HomeRoute.route) @@ -19,18 +14,12 @@ fun NavGraphBuilder.homeNavGraph( onPostClick: (Long) -> Unit, onProfileClick: (String) -> Unit, onSearchClick: () -> Unit, - coroutineScope: CoroutineScope, - bottomSheetState: BottomSheetScaffoldState, - bottomSheetContent: (@Composable () -> Unit) -> Unit ) { composable(HomeRoute.route) { HomeScreen( onPostClick = onPostClick, onProfileClick = onProfileClick, - onSearchClick = onSearchClick, - coroutineScope = coroutineScope, - bottomSheetState = bottomSheetState, - bottomSheetContent = bottomSheetContent + onSearchClick = onSearchClick ) } } diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt index 159e0dab..60d3df40 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt @@ -1,20 +1,21 @@ package daily.dayo.presentation.screen.home -import androidx.activity.compose.BackHandler +import BottomSheetController +import LocalBottomSheetController import androidx.annotation.DrawableRes import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.material3.BottomSheetScaffoldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier @@ -37,10 +38,7 @@ import daily.dayo.presentation.view.DayoTextButton import daily.dayo.presentation.view.NoRippleIconButton import daily.dayo.presentation.view.TopNavigation import daily.dayo.presentation.view.dialog.BottomSheetDialog -import daily.dayo.presentation.view.dialog.getBottomSheetDialogState import daily.dayo.presentation.viewmodel.HomeViewModel -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch const val HOME_DAYOPICK_PAGE_TAB_ID = 0 const val HOME_NEW_PAGE_TAB_ID = 1 @@ -51,25 +49,37 @@ fun HomeScreen( onPostClick: (Long) -> Unit, onProfileClick: (String) -> Unit, onSearchClick: () -> Unit, - coroutineScope: CoroutineScope, - bottomSheetState: BottomSheetScaffoldState, - bottomSheetContent: (@Composable () -> Unit) -> Unit, homeViewModel: HomeViewModel = hiltViewModel() ) { var homeTabState by rememberSaveable { mutableIntStateOf(HOME_DAYOPICK_PAGE_TAB_ID) } var selectedCategory by rememberSaveable { mutableStateOf(Pair(CategoryMenu.All.name, 0)) } // name, index + + // bottom sheet + val bottomSheetController = LocalBottomSheetController.current val onCategoryClick: () -> Unit = { - coroutineScope.launch { bottomSheetState.bottomSheetState.expand() } + bottomSheetController.show() } val onCategorySelect: (CategoryMenu, Int) -> Unit = { categoryMenu, index -> selectedCategory = Pair(categoryMenu.name, index) homeViewModel.setCategory(categoryMenu.category) - coroutineScope.launch { bottomSheetState.bottomSheetState.hide() } + bottomSheetController.hide() + } + val bottomSheetContent: @Composable () -> Unit = remember { + { + CategoryBottomSheetDialog( + onCategorySelect = onCategorySelect, + selectedCategory = selectedCategory, + bottomSheetController = bottomSheetController + ) + } } - BackHandler(enabled = bottomSheetState.bottomSheetState.isVisible) { - coroutineScope.launch { bottomSheetState.bottomSheetState.hide() } + DisposableEffect(Unit) { + bottomSheetController.setContent(bottomSheetContent) + onDispose { + bottomSheetController.hide() + } } Scaffold( @@ -143,19 +153,14 @@ fun HomeScreen( } } } - - bottomSheetContent { - CategoryBottomSheetDialog(onCategorySelect, selectedCategory, coroutineScope, bottomSheetState) - } } @OptIn(ExperimentalMaterial3Api::class) @Composable private fun CategoryBottomSheetDialog( - onCategorySelected: (CategoryMenu, Int) -> Unit, + onCategorySelect: (CategoryMenu, Int) -> Unit, selectedCategory: Pair, - coroutineScope: CoroutineScope, - bottomSheetState: BottomSheetScaffoldState + bottomSheetController: BottomSheetController ) { val categoryMenus = listOf( CategoryMenu.All, @@ -168,10 +173,9 @@ private fun CategoryBottomSheetDialog( ) BottomSheetDialog( - sheetState = bottomSheetState, buttons = categoryMenus.mapIndexed { index, category -> Pair(category.name) { - onCategorySelected(category, index) + onCategorySelect(category, index) } }, title = stringResource(id = R.string.filter), @@ -184,7 +188,7 @@ private fun CategoryBottomSheetDialog( normalColor = Gray2_767B83, checkedColor = Primary_23C882, checkedButtonIndex = selectedCategory.second, - closeButtonAction = { coroutineScope.launch { bottomSheetState.bottomSheetState.hide() } } + closeButtonAction = { bottomSheetController.hide() } ) } @@ -196,10 +200,7 @@ private fun PreviewHomeScreen() { HomeScreen( onPostClick = {}, onProfileClick = {}, - onSearchClick = {}, - coroutineScope = rememberCoroutineScope(), - bottomSheetState = getBottomSheetDialogState(), - bottomSheetContent = {}, + onSearchClick = {} ) } } diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/main/MainScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/main/MainScreen.kt index c9fc510a..6b6f7ed0 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/main/MainScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/main/MainScreen.kt @@ -1,19 +1,17 @@ package daily.dayo.presentation.screen.main +import BottomSheetController +import LocalBottomSheetController +import android.annotation.SuppressLint import android.widget.Toast import androidx.activity.compose.BackHandler import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionLayout -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.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding @@ -24,16 +22,16 @@ import androidx.compose.material.BottomNavigationItem import androidx.compose.material.Divider import androidx.compose.material.Icon import androidx.compose.material.Text -import androidx.compose.material3.BottomSheetScaffold import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.SheetValue +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableLongStateOf -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue @@ -73,11 +71,10 @@ import daily.dayo.presentation.theme.DayoTheme import daily.dayo.presentation.theme.Gray2_767B83 import daily.dayo.presentation.theme.Gray7_F6F6F7 import daily.dayo.presentation.theme.White_FFFFFF -import daily.dayo.presentation.view.dialog.getBottomSheetDialogState import daily.dayo.presentation.viewmodel.NoticeViewModel import daily.dayo.presentation.viewmodel.ProfileViewModel -import kotlinx.coroutines.launch +@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @OptIn(ExperimentalSharedTransitionApi::class, ExperimentalMaterial3Api::class) @Composable internal fun MainScreen( @@ -91,12 +88,9 @@ internal fun MainScreen( val noticeViewModel = hiltViewModel() val snackBarHostState = remember { SnackbarHostState() } - var bottomSheetContent by remember { mutableStateOf<(@Composable () -> Unit)?>(null) } - val bottomSheetState = getBottomSheetDialogState() - val bottomSheetDimAlpha by remember { - derivedStateOf { if (bottomSheetState.bottomSheetState.currentValue == SheetValue.Expanded) 0.6f else 0f } - } - val animatedDimAlpha by animateFloatAsState(targetValue = bottomSheetDimAlpha) + val bottomSheetController = remember { BottomSheetController() } + val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + val context = LocalContext.current var lastBackPressedTime by remember { mutableLongStateOf(0L) } val toast = remember { Toast.makeText(context, context.getString(R.string.main_finish_toast), Toast.LENGTH_SHORT) } @@ -114,216 +108,196 @@ internal fun MainScreen( } } - SharedTransitionLayout { - BottomSheetScaffold( - scaffoldState = bottomSheetState, - sheetDragHandle = null, - sheetContent = { - Box(modifier = Modifier.navigationBarsPadding()) { - bottomSheetContent?.invoke() + CompositionLocalProvider(LocalBottomSheetController provides bottomSheetController) { + SharedTransitionLayout { + Scaffold( + snackbarHost = { + SnackbarHost( + hostState = snackBarHostState, + modifier = Modifier.navigationBarsPadding() + ) } - }, - sheetPeekHeight = 0.dp, - snackbarHost = { - SnackbarHost( - hostState = snackBarHostState, - modifier = Modifier.navigationBarsPadding() - ) - } - ) { - Box { - Column(modifier = Modifier.navigationBarsPadding()) { - NavHost( - modifier = Modifier.weight(1f), - navController = navigator.navController, - startDestination = Screen.Home.route, - ) { - homeNavGraph( - onPostClick = { navigator.navigatePost(it) }, - onProfileClick = { memberId -> - navigator.navigateProfile( - currentMemberId, - memberId - ) - }, - onSearchClick = { navigator.navigateSearch() }, - coroutineScope = coroutineScope, - bottomSheetState = bottomSheetState, - bottomSheetContent = { content -> - bottomSheetContent = content - }, - ) - feedNavGraph( - snackBarHostState = snackBarHostState, - onEmptyViewClick = { navigator.navigateHome() }, - onPostClick = { navigator.navigatePost(it) }, - onProfileClick = { memberId -> - navigator.navigateProfile( - currentMemberId, - memberId - ) - }, - onPostLikeUsersClick = { navigator.navigatePostLikeUsers(it) }, - onPostHashtagClick = { navigator.navigateSearchPostHashtag(it) }, - bottomSheetState = bottomSheetState, - bottomSheetContent = { content -> bottomSheetContent = content } - ) - postNavGraph( - snackBarHostState = snackBarHostState, - onPostEditClick = { navigator.navigatePostEdit(it) }, - onProfileClick = { memberId -> - navigator.navigateProfile( - currentMemberId, - memberId - ) - }, - onPostLikeUsersClick = { navigator.navigatePostLikeUsers(it) }, - onPostHashtagClick = { navigator.navigateSearchPostHashtag(it) }, - onBackClick = { navigator.popBackStack() } - ) - searchNavGraph( - onBackClick = { navigator.popBackStack() }, - onSearch = { navigator.navigateSearchResult(it) }, - onPostClick = { navigator.navigatePost(it) }, - onProfileClick = { memberId -> - navigator.navigateProfile( - currentMemberId, - memberId - ) - }, + ) { + Box { + Column(modifier = Modifier.navigationBarsPadding()) { + NavHost( + modifier = Modifier.weight(1f), navController = navigator.navController, - ) - writeNavGraph( - snackBarHostState = snackBarHostState, + startDestination = Screen.Home.route, + ) { + homeNavGraph( + onPostClick = { navigator.navigatePost(it) }, + onProfileClick = { memberId -> + navigator.navigateProfile( + currentMemberId, + memberId + ) + }, + onSearchClick = { navigator.navigateSearch() } + ) + feedNavGraph( + snackBarHostState = snackBarHostState, + onEmptyViewClick = { navigator.navigateHome() }, + onPostClick = { navigator.navigatePost(it) }, + onProfileClick = { memberId -> + navigator.navigateProfile( + currentMemberId, + memberId + ) + }, + onPostLikeUsersClick = { navigator.navigatePostLikeUsers(it) }, + onPostHashtagClick = { navigator.navigateSearchPostHashtag(it) } + ) + postNavGraph( + snackBarHostState = snackBarHostState, + onPostEditClick = { navigator.navigatePostEdit(it) }, + onProfileClick = { memberId -> + navigator.navigateProfile( + currentMemberId, + memberId + ) + }, + onPostLikeUsersClick = { navigator.navigatePostLikeUsers(it) }, + onPostHashtagClick = { navigator.navigateSearchPostHashtag(it) }, + onBackClick = { navigator.popBackStack() } + ) + searchNavGraph( + onBackClick = { navigator.popBackStack() }, + onSearch = { navigator.navigateSearchResult(it) }, + onPostClick = { navigator.navigatePost(it) }, + onProfileClick = { memberId -> + navigator.navigateProfile( + currentMemberId, + memberId + ) + }, + navController = navigator.navController, + ) + writeNavGraph( + snackBarHostState = snackBarHostState, + navController = navigator.navController, + navigateToWritePost = { navigator.navigatePost(it) }, + onBackClick = { navigator.navigateUp() }, + onTagClick = { navigator.navigateWriteTag() }, + onWriteFolderClick = { navigator.navigateWriteFolder() }, + onWriteFolderNewClick = { navigator.navigateWriteFolderNew() }, + onAdRequest = onAdRequest + ) + myPageNavGraph( + navController = navigator.navController, + onBackClick = { navigator.popBackStack() }, + onSettingsClick = { navigator.navigateSettings() }, + onFollowButtonClick = { memberId, tabNum -> + navigator.navigateFollowMenu( + memberId, + tabNum + ) + }, + onProfileClick = { memberId -> + navigator.navigateProfile( + currentMemberId, + memberId + ) + }, + onProfileEditClick = { navigator.navigateProfileEdit() }, + onBookmarkClick = { navigator.navigateBookmark() }, + onFolderClick = { folderId -> navigator.navigateFolder(folderId) }, + onFolderCreateClick = { navigator.navigateFolderCreate() }, + onFolderEditClick = { folderId -> + navigator.navigateFolderEdit( + folderId + ) + }, + onWritePostWithFolderClick = { folderId -> + navigator.navigateToWritePostWithFolder( + folderId + ) + }, + onPostClick = { postId -> navigator.navigatePost(postId) }, + onPostMoveClick = { folderId -> + navigator.navigateFolderPostMove( + folderId + ) + }, + onAdRequest = onAdRequest, + navigateBackToFolder = { folderId -> + navigator.navigateBackToFolder( + folderId + ) + } + ) + profileNavGraph( + snackBarHostState = snackBarHostState, + onFollowMenuClick = { memberId, tabNum -> + navigator.navigateFollowMenu( + memberId, + tabNum + ) + }, + onFolderClick = { folderId -> navigator.navigateFolder(folderId) }, + onPostClick = { postId -> navigator.navigatePost(postId) }, + onBackClick = { navigator.popBackStack() } + ) + notificationNavGraph( + onPostClick = { navigator.navigatePost(it) }, + onProfileClick = { memberId -> + navigator.navigateProfile( + currentMemberId, + memberId + ) + }, + onNoticeClick = { noticeId -> + navigator.navigateNoticeDetail( + noticeId + ) + }, + ) + settingsNavGraph( + coroutineScope = coroutineScope, + snackBarHostState = snackBarHostState, + onProfileEditClick = { navigator.navigateProfileEdit() }, + onWithdrawClick = { navigator.navigateWithdraw() }, + onBlockUsersClick = { navigator.navigateBlockedUsers() }, + onPasswordChangeClick = { navigator.navigateChangePassword() }, + onSettingNotificationClick = { navigator.navigateSettingsNotification() }, + onNoticesClick = { navigator.navigateNotices() }, + onInformationClick = { navigator.navigateInformation() }, + onRulesClick = { ruleType -> navigator.navigateRules(ruleType) }, + onBackClick = { navigator.popBackStack() }, + onNavigateToHome = { navigator.navigateToBottomTabWithClearStack(Screen.Home.route) }, + onNavigateToMyPage = { navigator.navigateToBottomTabWithClearStack(Screen.MyPage.route) }, + ) + noticeNavGraph( + noticeViewModel = noticeViewModel, + onBackClick = { navigator.popBackStack() }, + onNoticeDetailClick = { noticeId -> + navigator.navigateNoticeDetail( + noticeId + ) + }, + sharedTransitionScope = this@SharedTransitionLayout + ) + } + + // bottom navigation + MainBottomNavigation( + visible = navigator.shouldShowBottomBar(), navController = navigator.navController, - navigateToWritePost = { navigator.navigatePost(it) }, - onBackClick = { navigator.navigateUp() }, - onTagClick = { navigator.navigateWriteTag() }, - onWriteFolderClick = { navigator.navigateWriteFolder() }, - onWriteFolderNewClick = { navigator.navigateWriteFolderNew() }, - onAdRequest = onAdRequest, - bottomSheetState = bottomSheetState, - bottomSheetContent = { content -> - bottomSheetContent = content - } - ) - myPageNavGraph( - navController = navigator.navController, - onBackClick = { navigator.popBackStack() }, - onSettingsClick = { navigator.navigateSettings() }, - onFollowButtonClick = { memberId, tabNum -> - navigator.navigateFollowMenu( - memberId, - tabNum - ) - }, - onProfileClick = { memberId -> - navigator.navigateProfile( - currentMemberId, - memberId - ) - }, - onProfileEditClick = { navigator.navigateProfileEdit() }, - onBookmarkClick = { navigator.navigateBookmark() }, - onFolderClick = { folderId -> navigator.navigateFolder(folderId) }, - onFolderCreateClick = { navigator.navigateFolderCreate() }, - onFolderEditClick = { folderId -> - navigator.navigateFolderEdit( - folderId - ) - }, - onWritePostWithFolderClick = { folderId -> - navigator.navigateToWritePostWithFolder( - folderId - ) - }, - onPostClick = { postId -> navigator.navigatePost(postId) }, - onPostMoveClick = { folderId -> - navigator.navigateFolderPostMove( - folderId - ) - }, - onAdRequest = onAdRequest, - navigateBackToFolder = { folderId -> - navigator.navigateBackToFolder( - folderId - ) - } - ) - profileNavGraph( - snackBarHostState = snackBarHostState, - onFollowMenuClick = { memberId, tabNum -> - navigator.navigateFollowMenu( - memberId, - tabNum - ) - }, - onFolderClick = { folderId -> navigator.navigateFolder(folderId) }, - onPostClick = { postId -> navigator.navigatePost(postId) }, - onBackClick = { navigator.popBackStack() } - ) - notificationNavGraph( - onPostClick = { navigator.navigatePost(it) }, - onProfileClick = { memberId -> - navigator.navigateProfile( - currentMemberId, - memberId - ) - }, - onNoticeClick = { noticeId -> - navigator.navigateNoticeDetail( - noticeId - ) - }, - ) - settingsNavGraph( - coroutineScope = coroutineScope, - snackBarHostState = snackBarHostState, - onProfileEditClick = { navigator.navigateProfileEdit() }, - onWithdrawClick = { navigator.navigateWithdraw() }, - onBlockUsersClick = { navigator.navigateBlockedUsers() }, - onPasswordChangeClick = { navigator.navigateChangePassword() }, - onSettingNotificationClick = { navigator.navigateSettingsNotification() }, - onNoticesClick = { navigator.navigateNotices() }, - onInformationClick = { navigator.navigateInformation() }, - onRulesClick = { ruleType -> navigator.navigateRules(ruleType) }, - onBackClick = { navigator.popBackStack() }, - bottomSheetState = bottomSheetState, - bottomSheetContent = { content -> - bottomSheetContent = content - }, - onNavigateToHome = { navigator.navigateToBottomTabWithClearStack(Screen.Home.route) }, - onNavigateToMyPage = { navigator.navigateToBottomTabWithClearStack(Screen.MyPage.route) }, - ) - noticeNavGraph( - noticeViewModel = noticeViewModel, - onBackClick = { navigator.popBackStack() }, - onNoticeDetailClick = { noticeId -> - navigator.navigateNoticeDetail( - noticeId - ) - }, - sharedTransitionScope = this@SharedTransitionLayout, + modifier = Modifier.fillMaxWidth() ) } - // bottom navigation - MainBottomNavigation( - visible = navigator.shouldShowBottomBar(), - navController = navigator.navController, - modifier = Modifier.fillMaxWidth() - ) - } - - if (animatedDimAlpha > 0f) { - Box( - modifier = Modifier - .fillMaxSize() - .background(Dark.copy(alpha = animatedDimAlpha)) - .clickable(indication = null, interactionSource = remember { MutableInteractionSource() }) { - coroutineScope.launch { bottomSheetState.bottomSheetState.hide() } - } - ) + // modal bottom sheet + if (bottomSheetController.isVisible) { + ModalBottomSheet( + onDismissRequest = { bottomSheetController.hide() }, + modifier = Modifier.navigationBarsPadding(), + sheetState = bottomSheetState, + dragHandle = null + ) { + bottomSheetController.sheetContent() + } + } } } } 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 3d4a51e3..e1e9aceb 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 @@ -1,5 +1,7 @@ package daily.dayo.presentation.screen.mypage +import BottomSheetController +import LocalBottomSheetController import android.Manifest import android.content.Context import android.content.pm.PackageManager @@ -11,9 +13,7 @@ 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 @@ -29,23 +29,19 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.Text -import androidx.compose.material3.BottomSheetScaffold -import androidx.compose.material3.BottomSheetScaffoldState 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.DisposableEffect 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 import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -82,9 +78,7 @@ import daily.dayo.presentation.view.DayoTextField import daily.dayo.presentation.view.TopNavigation import daily.dayo.presentation.view.TopNavigationAlign import daily.dayo.presentation.view.dialog.BottomSheetDialog -import daily.dayo.presentation.view.dialog.getBottomSheetDialogState import daily.dayo.presentation.viewmodel.ProfileSettingViewModel -import kotlinx.coroutines.launch import java.io.File import java.io.FileOutputStream import java.util.regex.Pattern @@ -97,9 +91,8 @@ internal fun MyPageEditScreen( ) { val context = LocalContext.current val focusManager = LocalFocusManager.current - val bottomSheetState = getBottomSheetDialogState() - val coroutineScope = rememberCoroutineScope() val alertDialog = remember { mutableStateOf(createLoadingDialog(context)) } + val bottomSheetController = LocalBottomSheetController.current val profileUiState by profileSettingViewModel.profileInfo.observeAsState(Resource.loading(null)) val isNicknameDuplicate by profileSettingViewModel.isNicknameDuplicate.collectAsStateWithLifecycle(false) @@ -164,26 +157,20 @@ internal fun MyPageEditScreen( MyPageEditScreen( profileInfo = profileInfo, - bottomSheetState = bottomSheetState, modifiedProfileImage = modifiedProfileImage.value, nickNameErrorMessage = nickNameErrorMessage.value, + bottomSheetController = bottomSheetController, onClickProfileSelect = { - coroutineScope.launch { - showProfileGallery = true - bottomSheetState.bottomSheetState.hide() - } + showProfileGallery = true + bottomSheetController.hide() }, onClickProfileCapture = { - coroutineScope.launch { - showProfileCapture = true - bottomSheetState.bottomSheetState.hide() - } + showProfileCapture = true + bottomSheetController.hide() }, onClickProfileReset = { modifiedProfileImage.value = "" - coroutineScope.launch { - bottomSheetState.bottomSheetState.hide() - } + bottomSheetController.hide() }, onBackClick = onBackClick, onConfirmClick = { @@ -207,131 +194,116 @@ private fun MyPageEditScreen( profileInfo: MutableState, modifiedProfileImage: String, nickNameErrorMessage: String, - bottomSheetState: BottomSheetScaffoldState, + bottomSheetController: BottomSheetController, onClickProfileSelect: () -> Unit, onClickProfileCapture: () -> Unit, onClickProfileReset: () -> Unit, 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() - - - BottomSheetScaffold( - scaffoldState = bottomSheetState, - sheetDragHandle = null, - sheetContent = { + val bottomSheetContent: @Composable () -> Unit = remember { + { ProfileImageBottomSheetDialog( - bottomSheetState, onClickProfileSelect, onClickProfileCapture, onClickProfileReset ) - }, - content = { - Box { - Scaffold( - modifier = Modifier.navigationBarsPadding(), - topBar = { - MyPageEditTopNavigation( - confirmEnabled = nickNameErrorMessage.isEmpty(), - onBackClick = onBackClick, - onConfirmClick = onConfirmClick - ) - } - ) { 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 } - 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() } - } - ) - ) + } + } + DisposableEffect(Unit) { + bottomSheetController.setContent(bottomSheetContent) + onDispose { + bottomSheetController.hide() + } + } - 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 - ) + Scaffold { + Box { + Scaffold( + modifier = Modifier.navigationBarsPadding(), + topBar = { + MyPageEditTopNavigation( + confirmEnabled = nickNameErrorMessage.isEmpty(), + onBackClick = onBackClick, + onConfirmClick = onConfirmClick + ) + } + ) { 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 } + 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 = { bottomSheetController.show() } + ) + ) - Spacer(modifier = Modifier.height(32.dp)) + Spacer(modifier = Modifier.height(36.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 - ) + 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 + ) - 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(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 ) + ) - HorizontalDivider( - modifier = Modifier.fillMaxWidth(), - thickness = 1.dp, - color = Gray6_F0F1F3 + 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() } - } - ) - } } } - ) + } } @Composable @@ -373,13 +345,11 @@ private fun MyPageEditTopNavigation( @OptIn(ExperimentalMaterial3Api::class) @Composable private fun ProfileImageBottomSheetDialog( - bottomSheetState: BottomSheetScaffoldState, onClickProfileSelect: () -> Unit, onClickProfileCapture: () -> Unit, onClickProfileReset: () -> Unit, ) { BottomSheetDialog( - sheetState = bottomSheetState, buttons = listOf( Pair(stringResource(id = R.string.my_profile_edit_image_select_gallery)) { onClickProfileSelect() @@ -505,7 +475,8 @@ fun bitmapToUri(context: Context, bitmap: Bitmap): Uri? { @OptIn(ExperimentalMaterial3Api::class) @Preview @Composable -internal fun PreviewMyPageEditScreen() { +private fun PreviewMyPageEditScreen() { + val bottomSheetController = remember { BottomSheetController() } DayoTheme { MyPageEditScreen( profileInfo = remember { @@ -524,10 +495,10 @@ internal fun PreviewMyPageEditScreen() { }, modifiedProfileImage = "", nickNameErrorMessage = "", - bottomSheetState = getBottomSheetDialogState(), + bottomSheetController = bottomSheetController, onClickProfileSelect = {}, onClickProfileCapture = {}, - onClickProfileReset = { }, + onClickProfileReset = {}, onBackClick = {}, onConfirmClick = {} ) diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/settings/SettingsNavigation.kt b/presentation/src/main/java/daily/dayo/presentation/screen/settings/SettingsNavigation.kt index 4265e3ec..490467b6 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/settings/SettingsNavigation.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/settings/SettingsNavigation.kt @@ -40,8 +40,6 @@ fun NavGraphBuilder.settingsNavGraph( onBlockUsersClick: () -> Unit, onWithdrawClick: () -> Unit, onBackClick: () -> Unit, - bottomSheetState: BottomSheetScaffoldState, - bottomSheetContent: (@Composable () -> Unit) -> Unit, onSettingNotificationClick: () -> Unit, onPasswordChangeClick: () -> Unit, onNoticesClick: () -> Unit, @@ -101,8 +99,6 @@ fun NavGraphBuilder.settingsNavGraph( composable(SettingsRoute.withdraw) { WithdrawScreen( onBackClick = onBackClick, - bottomSheetState = bottomSheetState, - bottomSheetContent = bottomSheetContent, snackBarHostState = snackBarHostState, onNavigateToHome = onNavigateToHome, onNavigateToMyPage = onNavigateToMyPage diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/write/WriteNavigation.kt b/presentation/src/main/java/daily/dayo/presentation/screen/write/WriteNavigation.kt index 938dace2..7b29687e 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/write/WriteNavigation.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/write/WriteNavigation.kt @@ -1,9 +1,7 @@ package daily.dayo.presentation.screen.write -import androidx.compose.material3.BottomSheetScaffoldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController @@ -51,9 +49,7 @@ fun NavGraphBuilder.writeNavGraph( onTagClick: () -> Unit, onWriteFolderClick: () -> Unit, onWriteFolderNewClick: () -> Unit, - onAdRequest: (onRewardSuccess: () -> Unit) -> Unit, - bottomSheetState: BottomSheetScaffoldState, - bottomSheetContent: (@Composable () -> Unit) -> Unit, + onAdRequest: (onRewardSuccess: () -> Unit) -> Unit ) { navigation( startDestination = WriteRoute.route, @@ -80,9 +76,7 @@ fun NavGraphBuilder.writeNavGraph( onTagClick = onTagClick, onWriteFolderClick = onWriteFolderClick, onCropImageClick = { imgIdx -> navController.navigateCrop(imgIdx) }, - writeViewModel = hiltViewModel(parentStackEntry), - bottomSheetState = bottomSheetState, - bottomSheetContent = bottomSheetContent + writeViewModel = hiltViewModel(parentStackEntry) ) } @@ -105,9 +99,7 @@ fun NavGraphBuilder.writeNavGraph( onTagClick = onTagClick, onWriteFolderClick = onWriteFolderClick, onCropImageClick = { imgIdx -> navController.navigateCrop(imgIdx) }, - writeViewModel = hiltViewModel(parentStackEntry), - bottomSheetState = bottomSheetState, - bottomSheetContent = bottomSheetContent + writeViewModel = hiltViewModel(parentStackEntry) ) } } diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/write/WriteScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/write/WriteScreen.kt index 16f45968..693aa1fe 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/write/WriteScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/write/WriteScreen.kt @@ -1,10 +1,11 @@ package daily.dayo.presentation.screen.write +import BottomSheetController +import LocalBottomSheetController import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.net.Uri -import androidx.activity.compose.BackHandler import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.Image @@ -31,13 +32,12 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.material.Surface -import androidx.compose.material3.BottomSheetScaffoldState import androidx.compose.material3.Divider import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.SheetValue import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -92,7 +92,6 @@ import daily.dayo.presentation.view.TopNavigation import daily.dayo.presentation.view.TopNavigationAlign import daily.dayo.presentation.view.dialog.BottomSheetDialog import daily.dayo.presentation.viewmodel.WriteViewModel -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch const val WRITE_POST_IMAGE_MIN_SIZE = 1 @@ -110,9 +109,7 @@ internal fun WriteRoute( onTagClick: () -> Unit, onWriteFolderClick: () -> Unit, onCropImageClick: (Int) -> Unit = {}, - writeViewModel: WriteViewModel = hiltViewModel(), - bottomSheetState: BottomSheetScaffoldState, - bottomSheetContent: (@Composable () -> Unit) -> Unit, + writeViewModel: WriteViewModel = hiltViewModel() ) { val isPostEditMode = postId != null val context = LocalContext.current @@ -129,9 +126,11 @@ internal fun WriteRoute( val folderName by writeViewModel.writeFolderName.collectAsStateWithLifecycle() val writePostId by writeViewModel.writePostId.collectAsState(null) val selectedCategory by writeViewModel.writeCategory.collectAsStateWithLifecycle() // name, index + + val bottomSheetController = LocalBottomSheetController.current val onClickCategory: (CategoryMenu, Int) -> Unit = { categoryMenu, index -> writeViewModel.setPostCategory(Pair(categoryMenu.category, index)) - coroutineScope.launch { bottomSheetState.bottomSheetState.hide() } + bottomSheetController.hide() } val categoryMenus = listOf( CategoryMenu.Scheduler, @@ -141,17 +140,27 @@ internal fun WriteRoute( CategoryMenu.Digital, CategoryMenu.ETC ) - val uploadSuccess by writeViewModel.uploadSuccess.collectAsStateWithLifecycle() - val postInfoSuccess by writeViewModel.postInfoSuccess.collectAsStateWithLifecycle(null) + val bottomSheetContent: @Composable () -> Unit = remember { + { + CategoryBottomSheetDialog( + categoryMenus, + onClickCategory, + selectedCategory, + bottomSheetController + ) + } + } - BackHandler { - if (bottomSheetState.bottomSheetState.currentValue == SheetValue.Expanded) { - coroutineScope.launch { bottomSheetState.bottomSheetState.hide() } - } else { - onBackClick() + DisposableEffect(Unit) { + bottomSheetController.setContent(bottomSheetContent) + onDispose { + bottomSheetController.hide() } } + val uploadSuccess by writeViewModel.uploadSuccess.collectAsStateWithLifecycle() + val postInfoSuccess by writeViewModel.postInfoSuccess.collectAsStateWithLifecycle(null) + LaunchedEffect(postId) { if (isPostEditMode && postEditId == null) { writeViewModel.requestPostDetail(postId!!, categoryMenus) @@ -219,7 +228,7 @@ internal fun WriteRoute( }, processedImages = imageAssets, navigateToCategory = { - coroutineScope.launch { bottomSheetState.bottomSheetState.expand() } + coroutineScope.launch { bottomSheetController.show() } }, categoryMenus = categoryMenus, category = selectedCategory, @@ -230,16 +239,6 @@ internal fun WriteRoute( folderName = folderName, uploadSuccess = uploadSuccess, ) - - bottomSheetContent { - CategoryBottomSheetDialog( - categoryMenus, - onClickCategory, - selectedCategory, - coroutineScope, - bottomSheetState - ) - } } @OptIn(ExperimentalMaterial3Api::class) @@ -248,12 +247,10 @@ private fun CategoryBottomSheetDialog( categoryMenus: List, onCategorySelected: (CategoryMenu, Int) -> Unit, selectedCategory: Pair, - coroutineScope: CoroutineScope, - bottomSheetState: BottomSheetScaffoldState + bottomSheetController: BottomSheetController ) { BottomSheetDialog( - sheetState = bottomSheetState, buttons = categoryMenus.mapIndexed { index, category -> Pair(category.name) { onCategorySelected(category, index) @@ -269,7 +266,7 @@ private fun CategoryBottomSheetDialog( normalColor = Gray2_767B83, checkedColor = Primary_23C882, checkedButtonIndex = selectedCategory.second, - closeButtonAction = { coroutineScope.launch { bottomSheetState.bottomSheetState.hide() } } + closeButtonAction = { bottomSheetController.hide() } ) } diff --git a/presentation/src/main/java/daily/dayo/presentation/theme/Color.kt b/presentation/src/main/java/daily/dayo/presentation/theme/Color.kt index 0f2581d6..b07597a1 100644 --- a/presentation/src/main/java/daily/dayo/presentation/theme/Color.kt +++ b/presentation/src/main/java/daily/dayo/presentation/theme/Color.kt @@ -4,6 +4,7 @@ import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.Color +val Transparent = Color(0x00000000) val Dark = Color(0xFF313131) val Primary_23C882 = Color(0xFF23C882) val White_FFFFFF = Color(0xFFFFFFFF) diff --git a/presentation/src/main/java/daily/dayo/presentation/view/FeedPostView.kt b/presentation/src/main/java/daily/dayo/presentation/view/FeedPostView.kt index 527e61ec..28d557db 100644 --- a/presentation/src/main/java/daily/dayo/presentation/view/FeedPostView.kt +++ b/presentation/src/main/java/daily/dayo/presentation/view/FeedPostView.kt @@ -24,7 +24,6 @@ import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.BottomSheetScaffoldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -74,7 +73,6 @@ import daily.dayo.presentation.theme.Gray4_C5CAD2 import daily.dayo.presentation.theme.Primary_23C882 import daily.dayo.presentation.theme.Transparent_White30 import daily.dayo.presentation.theme.White_FFFFFF -import daily.dayo.presentation.view.dialog.CommentBottomSheetDialog import daily.dayo.presentation.view.dialog.PostReportDialog import daily.dayo.presentation.viewmodel.ReportViewModel import kotlinx.coroutines.launch @@ -92,8 +90,7 @@ fun FeedPostView( onClickBookmark: () -> Unit, onPostLikeUsersClick: (Long) -> Unit, onPostHashtagClick: (String) -> Unit, - bottomSheetState: BottomSheetScaffoldState, - bottomSheetContent: (@Composable () -> Unit) -> Unit, + onClickComment: () -> Unit, reportViewModel: ReportViewModel = hiltViewModel() ) { val imageInteractionSource = remember { MutableInteractionSource() } @@ -102,19 +99,6 @@ fun FeedPostView( val coroutineScope = rememberCoroutineScope() val context = LocalContext.current - val onClickComment: (Long) -> Unit = { postId -> - coroutineScope.launch { bottomSheetState.bottomSheetState.expand() } - bottomSheetContent { - CommentBottomSheetDialog( - postId = postId, - sheetState = bottomSheetState, - snackBarHostState = snackBarHostState, - onClickProfile = onClickProfile, - onClickClose = { coroutineScope.launch { bottomSheetState.bottomSheetState.hide() } }, - ) - } - } - Column(modifier = modifier) { // publisher info Row( @@ -269,7 +253,7 @@ fun FeedPostView( .clickableSingle( indication = ripple(bounded = false), interactionSource = remember { MutableInteractionSource() }, - onClick = { post.postId?.let { onClickComment(it) } } + onClick = onClickComment ), contentDescription = "comment", ) diff --git a/presentation/src/main/java/daily/dayo/presentation/view/dialog/BottomSheetDialog.kt b/presentation/src/main/java/daily/dayo/presentation/view/dialog/BottomSheetDialog.kt index 6e5ec11e..80340c23 100644 --- a/presentation/src/main/java/daily/dayo/presentation/view/dialog/BottomSheetDialog.kt +++ b/presentation/src/main/java/daily/dayo/presentation/view/dialog/BottomSheetDialog.kt @@ -17,22 +17,16 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Button import androidx.compose.material.Divider import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Image import androidx.compose.material.icons.filled.ImageSearch -import androidx.compose.material3.BottomSheetScaffoldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon -import androidx.compose.material3.SheetValue import androidx.compose.material3.Surface -import androidx.compose.material3.rememberBottomSheetScaffoldState -import androidx.compose.material3.rememberStandardBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -48,7 +42,6 @@ import daily.dayo.presentation.theme.DayoTheme import daily.dayo.presentation.theme.Gray6_F0F1F3 import daily.dayo.presentation.theme.Primary_23C882 import daily.dayo.presentation.theme.White_FFFFFF -import kotlinx.coroutines.launch // 1. 기본과 Hover 상태 구분 // 2. primary가 설정되는 경우 가장 1번째 색이 항상 prmiary color로 설정, 아닌 경우 모두 같은 색 @@ -58,7 +51,6 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable fun BottomSheetDialog( - sheetState: BottomSheetScaffoldState, buttons: List Unit>>, leftIconButtons: List? = null, leftIconCheckedButtons: List? = null, @@ -184,76 +176,14 @@ fun BottomSheetDialog( } } -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun getBottomSheetDialogState( - disableFullExpanded: Boolean = false, - skipHiddenState: Boolean = false -): BottomSheetScaffoldState { - val bottomSheetState = rememberStandardBottomSheetState( - initialValue = SheetValue.Hidden, - confirmValueChange = { - if (disableFullExpanded) { - it != SheetValue.Expanded - } else { - true - } - }, - skipHiddenState = skipHiddenState - ) - - return rememberBottomSheetScaffoldState( - bottomSheetState = bottomSheetState - ) -} - @OptIn(ExperimentalMaterial3Api::class) @Preview @Composable fun PreviewMyBottomSheetDialog() { // BottomSheetDialog를 사용하는 경우 Box를 이용해서 겹쳐보일 수 있도록 설정해야 합니다 - Box(modifier = Modifier.fillMaxSize()) { - val coroutineScope = rememberCoroutineScope() - - // BottomSheetDialog를 사용하는 경우에 같이 사용하는 State - val bottomSheetState1 = getBottomSheetDialogState() - val bottomSheetState2 = getBottomSheetDialogState() - val bottomSheetState3 = getBottomSheetDialogState() - val bottomSheetState4 = getBottomSheetDialogState() - val bottomSheetState5 = getBottomSheetDialogState() - val bottomSheetState6 = getBottomSheetDialogState() - val bottomSheetState7 = getBottomSheetDialogState() - - Column(modifier = Modifier.fillMaxSize()) { - // 구현 본문 내용 - - // BottomSheetDialog를 보여주는 경우 클릭하는 Button - Button(onClick = { coroutineScope.launch { bottomSheetState1.bottomSheetState.expand() } }) { - Text(text = "Bottom Sheet Dialog / buton 1 / non-primary") - } - Button(onClick = { coroutineScope.launch { bottomSheetState2.bottomSheetState.expand() } }) { - Text(text = "Bottom Sheet Dialog / buton 1 / primary") - } - Button(onClick = { coroutineScope.launch { bottomSheetState3.bottomSheetState.expand() } }) { - Text(text = "Bottom Sheet Dialog / buton 2 / non-primary") - } - Button(onClick = { coroutineScope.launch { bottomSheetState4.bottomSheetState.expand() } }) { - Text(text = "Bottom Sheet Dialog / buton 2 / primary") - } - Button(onClick = { coroutineScope.launch { bottomSheetState5.bottomSheetState.expand() } }) { - Text(text = "Bottom Sheet Dialog / buton 3 / non-primary") - } - Button(onClick = { coroutineScope.launch { bottomSheetState6.bottomSheetState.expand() } }) { - Text(text = "Bottom Sheet Dialog / buton 3 / primary") - } - Button(onClick = { coroutineScope.launch { bottomSheetState7.bottomSheetState.expand() } }) { - Text(text = "Bottom Sheet Dialog / Category") - } - } - + Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(10.dp)) { BottomSheetDialog( - sheetState = bottomSheetState1, buttons = listOf( Pair("text") { // 버튼 클릭시 동작 의 @@ -261,35 +191,29 @@ fun PreviewMyBottomSheetDialog() { isFirstButtonColored = false, ) BottomSheetDialog( - sheetState = bottomSheetState2, buttons = listOf(Pair("text") { }), isFirstButtonColored = true ) BottomSheetDialog( - sheetState = bottomSheetState3, buttons = listOf(Pair("text") { }, Pair("text") { }), isFirstButtonColored = false, ) BottomSheetDialog( - sheetState = bottomSheetState4, buttons = listOf(Pair("text") { }, Pair("text") { }), isFirstButtonColored = true ) BottomSheetDialog( - sheetState = bottomSheetState5, buttons = listOf(Pair("text") { }, Pair("text") { }, Pair("text") { }), isFirstButtonColored = false, ) BottomSheetDialog( - sheetState = bottomSheetState6, buttons = listOf(Pair("text") { }, Pair("text") { }, Pair("text") { }), isFirstButtonColored = true ) BottomSheetDialog( - sheetState = bottomSheetState7, buttons = listOf(Pair("contents") { }, Pair("contents") { }, Pair("contents") { }), title = "title", leftIconButtons = listOf(Icons.Default.Image, Icons.Default.Image, Icons.Default.Image), diff --git a/presentation/src/main/java/daily/dayo/presentation/view/dialog/CommentBottomSheetDialog.kt b/presentation/src/main/java/daily/dayo/presentation/view/dialog/CommentBottomSheetDialog.kt index 51ce7ca5..3984e606 100644 --- a/presentation/src/main/java/daily/dayo/presentation/view/dialog/CommentBottomSheetDialog.kt +++ b/presentation/src/main/java/daily/dayo/presentation/view/dialog/CommentBottomSheetDialog.kt @@ -1,6 +1,5 @@ package daily.dayo.presentation.view.dialog -import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -11,9 +10,7 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Text -import androidx.compose.material3.BottomSheetScaffoldState import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.SheetValue import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface import androidx.compose.runtime.Composable @@ -65,7 +62,6 @@ import kotlinx.coroutines.launch @Composable fun CommentBottomSheetDialog( postId: Long, - sheetState: BottomSheetScaffoldState, snackBarHostState: SnackbarHostState, onClickProfile: (String) -> Unit, onClickClose: () -> Unit, @@ -179,19 +175,6 @@ fun CommentBottomSheetDialog( postViewModel.requestPostComment(postId) } - BackHandler(enabled = sheetState.bottomSheetState.isVisible) { - coroutineScope.launch { - clearComment() - sheetState.bottomSheetState.hide() - } - } - - LaunchedEffect(sheetState.bottomSheetState.currentValue) { - if (sheetState.bottomSheetState.currentValue == SheetValue.Hidden) { - keyboardController?.hide() - } - } - Surface( modifier = modifier, shape = RoundedCornerShape(12.dp, 12.dp, 0.dp, 0.dp), diff --git a/presentation/src/main/java/daily/dayo/presentation/view/dialog/ProfileImageBottomSheetDialog.kt b/presentation/src/main/java/daily/dayo/presentation/view/dialog/ProfileImageBottomSheetDialog.kt index 37877d63..5251aab3 100644 --- a/presentation/src/main/java/daily/dayo/presentation/view/dialog/ProfileImageBottomSheetDialog.kt +++ b/presentation/src/main/java/daily/dayo/presentation/view/dialog/ProfileImageBottomSheetDialog.kt @@ -1,6 +1,5 @@ package daily.dayo.presentation.view.dialog -import androidx.compose.material3.BottomSheetScaffoldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource @@ -9,13 +8,11 @@ import daily.dayo.presentation.R @OptIn(ExperimentalMaterial3Api::class) @Composable fun ProfileImageBottomSheetDialog( - bottomSheetState: BottomSheetScaffoldState, onClickProfileSelect: () -> Unit, onClickProfileCapture: () -> Unit, onClickProfileReset: () -> Unit, ) { BottomSheetDialog( - sheetState = bottomSheetState, buttons = listOf( Pair(stringResource(id = R.string.image_option_gallery)) { onClickProfileSelect()