diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 632e4944..7ed6a8a6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,6 +20,7 @@ android:name=".presentation.MainActivity" android:exported="true" android:label="@string/app_name" + android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.SpoonyAndroid"> diff --git a/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt b/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt index e2ffa6cd..c3852122 100644 --- a/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt +++ b/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt @@ -5,61 +5,58 @@ import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Build -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight 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.layout.sizeIn -import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect 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.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.spoony.spoony.R -import com.spoony.spoony.core.designsystem.component.button.SpoonyButton +import androidx.lifecycle.flowWithLifecycle +import com.spoony.spoony.core.designsystem.component.snackbar.TextSnackbar import com.spoony.spoony.core.designsystem.component.tag.IconTag import com.spoony.spoony.core.designsystem.component.topappbar.TagTopAppBar import com.spoony.spoony.core.designsystem.theme.SpoonyAndroidTheme -import com.spoony.spoony.core.designsystem.type.ButtonSize -import com.spoony.spoony.core.designsystem.type.ButtonStyle import com.spoony.spoony.core.state.UiState import com.spoony.spoony.core.util.extension.hexToColor -import com.spoony.spoony.core.util.extension.noRippleClickable import com.spoony.spoony.core.util.extension.toValidHexColor import com.spoony.spoony.domain.entity.CategoryEntity import com.spoony.spoony.domain.entity.PostEntity import com.spoony.spoony.domain.entity.UserEntity +import com.spoony.spoony.presentation.main.SHOW_SNACKBAR_TIMEMILLIS import com.spoony.spoony.presentation.placeDetail.component.IconDropdownMenu +import com.spoony.spoony.presentation.placeDetail.component.PlaceDetailBottomBar import com.spoony.spoony.presentation.placeDetail.component.PlaceDetailImageLazyRow import com.spoony.spoony.presentation.placeDetail.component.ScoopDialog import com.spoony.spoony.presentation.placeDetail.component.StoreInfo import com.spoony.spoony.presentation.placeDetail.component.UserProfileInfo import com.spoony.spoony.presentation.placeDetail.type.DropdownOption import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch @Composable fun PlaceDetailRoute( @@ -72,11 +69,41 @@ fun PlaceDetailRoute( val state by viewModel.state.collectAsStateWithLifecycle(lifecycleOwner = lifecycleOwner) + val coroutineScope = rememberCoroutineScope() + val snackBarHostState = remember { SnackbarHostState() } + var scoopDialogVisibility by remember { mutableStateOf(false) } + + val onShowSnackBar: (String) -> Unit = { message -> + coroutineScope.launch { + snackBarHostState.currentSnackbarData?.dismiss() + val job = launch { + snackBarHostState.showSnackbar(message) + } + delay(SHOW_SNACKBAR_TIMEMILLIS) + job.cancel() + } + } + val spoonAmount = when (state.spoonAmountEntity) { is UiState.Success -> (state.spoonAmountEntity as UiState.Success).data else -> 99 } + LaunchedEffect(viewModel.sideEffect, lifecycleOwner) { + viewModel.sideEffect.flowWithLifecycle(lifecycleOwner.lifecycle).collect { effect -> + when (effect) { + is PlaceDetailSideEffect.ShowSnackbar -> { + onShowSnackBar(effect.message) + } + } + } + } + + val postId = (state.postId as? UiState.Success)?.data ?: return + val userId = (state.userId as? UiState.Success)?.data ?: return + + val context = LocalContext.current + val userProfile = when (state.userEntity) { is UiState.Success -> (state.userEntity as UiState.Success).data else -> UserEntity( @@ -87,39 +114,79 @@ fun PlaceDetailRoute( userRegion = "성북구" ) } + + if (scoopDialogVisibility) { + ScoopDialog( + onClickPositive = { + viewModel.useSpoon(postId, userId) + scoopDialogVisibility = false + }, + onClickNegative = { + scoopDialogVisibility = false + } + ) + } + when (state.postEntity) { is UiState.Empty -> {} is UiState.Loading -> {} is UiState.Failure -> {} is UiState.Success -> { with(state.postEntity as UiState.Success) { - val postId = (state.postId as? UiState.Success)?.data ?: return - val userId = (state.userId as? UiState.Success)?.data ?: return - PlaceDetailScreen( - paddingValues = paddingValues, - menuList = data.menuList, - title = data.title, - description = data.description, - userProfileUrl = userProfile.userProfileUrl, - userName = userProfile.userName, - userRegion = userProfile.userRegion, - photoUrlList = data.photoUrlList, - category = data.category, - date = data.date, - placeAddress = data.placeAddress, - placeName = data.placeName, - spoonAmount = spoonAmount, - addMapCount = data.addMapCount, - isScooped = data.isScooped, - isAddMap = data.isAddMap, - latitude = data.latitude, - longitude = data.longitude, - onScoopButtonClick = { viewModel.useSpoon(postId, userId) }, - onAddMapButtonClick = { viewModel.addMyMap(postId, userId) }, - onDeletePinMapButtonClick = { viewModel.deletePinMap(postId, userId) }, - dropdownMenuList = state.dropDownMenuList, - onBackButtonClick = navigateUp, - onReportButtonClick = navigateToReport + Scaffold( + snackbarHost = { + SnackbarHost(hostState = snackBarHostState) { snackbarData -> + TextSnackbar(text = snackbarData.visuals.message) + } + }, + topBar = { + TagTopAppBar( + count = spoonAmount, + showBackButton = true, + onBackButtonClick = navigateUp + ) + }, + bottomBar = { + PlaceDetailBottomBar( + modifier = Modifier + .navigationBarsPadding(), + addMapCount = data.addMapCount, + isScooped = data.isScooped, + isAddMap = data.isAddMap, + onScoopButtonClick = { + scoopDialogVisibility = true + }, + onSearchMapClick = { + searchPlaceNaverMap( + latitude = data.latitude, + longitude = data.longitude, + placeName = data.placeName, + context = context + ) + }, + onAddMapButtonClick = { viewModel.addMyMap(postId, userId) }, + onDeletePinMapButtonClick = { viewModel.deletePinMap(postId, userId) } + ) + }, + content = { paddingValues -> + PlaceDetailScreen( + paddingValues = paddingValues, + menuList = data.menuList, + title = data.title, + description = data.description, + userProfileUrl = userProfile.userProfileUrl, + userName = userProfile.userName, + userRegion = userProfile.userRegion, + photoUrlList = data.photoUrlList, + category = data.category, + date = data.date, + placeAddress = data.placeAddress, + placeName = data.placeName, + isScooped = data.isScooped, + dropdownMenuList = state.dropDownMenuList, + onReportButtonClick = navigateToReport + ) + } ) } } @@ -140,149 +207,96 @@ private fun PlaceDetailScreen( date: String, placeAddress: String, placeName: String, - spoonAmount: Int, - addMapCount: Int, - isAddMap: Boolean, isScooped: Boolean, - latitude: Double, - longitude: Double, - onScoopButtonClick: () -> Unit, - onAddMapButtonClick: () -> Unit, - onDeletePinMapButtonClick: () -> Unit, - onBackButtonClick: () -> Unit, dropdownMenuList: ImmutableList, onReportButtonClick: () -> Unit ) { val scrollState = rememberScrollState() - val context = LocalContext.current - - var scoopDialogVisibility by remember { mutableStateOf(false) } - - if (scoopDialogVisibility) { - ScoopDialog( - onClickPositive = { - onScoopButtonClick() - scoopDialogVisibility = false - }, - onClickNegative = { - scoopDialogVisibility = false - } - ) - } - Column( modifier = Modifier .fillMaxSize() - .padding(bottom = paddingValues.calculateBottomPadding()) + .padding(paddingValues) + .verticalScroll(scrollState) ) { - TagTopAppBar( - count = spoonAmount, - showBackButton = true, - onBackButtonClick = onBackButtonClick - ) - Column( + Row( modifier = Modifier - .weight(1f) - .verticalScroll(scrollState) + .fillMaxWidth() + .padding( + vertical = 8.dp, + horizontal = 20.dp + ), + verticalAlignment = Alignment.CenterVertically ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding( - vertical = 8.dp, - horizontal = 20.dp - ), - verticalAlignment = Alignment.CenterVertically - ) { - UserProfileInfo( - imageUrl = userProfileUrl, - name = userName, - location = "$userRegion 수저", - modifier = Modifier.weight(1f) - ) + UserProfileInfo( + imageUrl = userProfileUrl, + name = userName, + location = "$userRegion 수저", + modifier = Modifier.weight(1f) + ) - IconDropdownMenu( - menuItems = dropdownMenuList, - onMenuItemClick = { menu -> - when (menu) { - DropdownOption.REPORT.string -> { - onReportButtonClick() - } + IconDropdownMenu( + menuItems = dropdownMenuList, + onMenuItemClick = { menu -> + when (menu) { + DropdownOption.REPORT.string -> { + onReportButtonClick() } } - ) - } + } + ) + } - Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.height(24.dp)) - PlaceDetailImageLazyRow( - imageList = photoUrlList, - isBlurred = !isScooped - ) + PlaceDetailImageLazyRow( + imageList = photoUrlList, + isBlurred = !isScooped + ) - Spacer(modifier = Modifier.height(32.dp)) + Spacer(modifier = Modifier.height(32.dp)) - Column(modifier = Modifier.padding(horizontal = 20.dp)) { - IconTag( - text = category.categoryName, - backgroundColor = Color.hexToColor(category.backgroundColor.toValidHexColor()), - textColor = Color.hexToColor(category.textColor.toValidHexColor()), - iconUrl = category.iconUrl - ) + Column(modifier = Modifier.padding(horizontal = 20.dp)) { + IconTag( + text = category.categoryName, + backgroundColor = Color.hexToColor(category.backgroundColor.toValidHexColor()), + textColor = Color.hexToColor(category.textColor.toValidHexColor()), + iconUrl = category.iconUrl + ) - Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.height(8.dp)) - Text( - text = title, - style = SpoonyAndroidTheme.typography.title1, - color = SpoonyAndroidTheme.colors.black - ) + Text( + text = title, + style = SpoonyAndroidTheme.typography.title1, + color = SpoonyAndroidTheme.colors.black + ) - Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.height(8.dp)) - Text( - text = date, - style = SpoonyAndroidTheme.typography.caption1m, - color = SpoonyAndroidTheme.colors.gray400 - ) + Text( + text = date, + style = SpoonyAndroidTheme.typography.caption1m, + color = SpoonyAndroidTheme.colors.gray400 + ) - Spacer(modifier = Modifier.height(20.dp)) + Spacer(modifier = Modifier.height(20.dp)) - Text( - text = description, - style = SpoonyAndroidTheme.typography.body2m, - color = SpoonyAndroidTheme.colors.gray900 - ) + Text( + text = description, + style = SpoonyAndroidTheme.typography.body2m, + color = SpoonyAndroidTheme.colors.gray900 + ) - Spacer(modifier = Modifier.height(32.dp)) + Spacer(modifier = Modifier.height(32.dp)) - StoreInfo( - isBlurred = !isScooped, - menuList = menuList, - locationSubTitle = placeName, - location = placeAddress - ) - } - Spacer(modifier = Modifier.height(27.dp)) + StoreInfo( + isBlurred = !isScooped, + menuList = menuList, + locationSubTitle = placeName, + location = placeAddress + ) } - PlaceDetailBottomBar( - addMapCount = addMapCount, - isScooped = isScooped, - isAddMap = isAddMap, - onScoopButtonClick = { - scoopDialogVisibility = true - }, - onSearchMapClick = { - searchPlaceNaverMap( - latitude = latitude, - longitude = longitude, - placeName = placeName, - context = context - ) - }, - onAddMapButtonClick = onAddMapButtonClick, - onDeletePinMapButtonClick = onDeletePinMapButtonClick - ) + Spacer(modifier = Modifier.height(27.dp)) } } @@ -317,86 +331,3 @@ private fun searchPlaceNaverMap( } } } - -@Composable -private fun PlaceDetailBottomBar( - addMapCount: Int, - onScoopButtonClick: () -> Unit, - onSearchMapClick: () -> Unit, - onAddMapButtonClick: () -> Unit, - onDeletePinMapButtonClick: () -> Unit, - modifier: Modifier = Modifier, - isScooped: Boolean = false, - isAddMap: Boolean = false -) { - Row( - modifier = modifier - .fillMaxWidth() - .background(SpoonyAndroidTheme.colors.white) - .padding( - horizontal = 20.dp, - vertical = 10.dp - ) - .height(IntrinsicSize.Max), - verticalAlignment = Alignment.CenterVertically - ) { - if (isScooped) { - SpoonyButton( - text = "길찾기", - onClick = onSearchMapClick, - style = ButtonStyle.Secondary, - size = ButtonSize.Medium, - modifier = Modifier.weight(1f) - ) - - Spacer(modifier = Modifier.width(15.dp)) - - Column( - modifier = Modifier - .fillMaxHeight() - .sizeIn(minWidth = 56.dp) - .noRippleClickable( - if (isAddMap) { - onDeletePinMapButtonClick - } else { - onAddMapButtonClick - } - ), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Icon( - imageVector = if (isAddMap) ImageVector.vectorResource(id = R.drawable.ic_add_map_main400_24) else ImageVector.vectorResource(id = R.drawable.ic_add_map_gray400_24), - modifier = Modifier - .size(32.dp), - contentDescription = null, - tint = Color.Unspecified - ) - - Spacer(modifier = Modifier.height(4.dp)) - - Text( - text = addMapCount.toString(), - style = SpoonyAndroidTheme.typography.caption1m, - color = SpoonyAndroidTheme.colors.gray800 - ) - } - } else { - SpoonyButton( - text = "떠먹기", - style = ButtonStyle.Secondary, - size = ButtonSize.Medium, - onClick = onScoopButtonClick, - modifier = Modifier.weight(1f), - icon = { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.ic_button_spoon_32), - modifier = Modifier.size(32.dp), - contentDescription = null, - tint = Color.Unspecified - ) - } - ) - } - } -} diff --git a/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailSideEffect.kt b/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailSideEffect.kt new file mode 100644 index 00000000..584e24fe --- /dev/null +++ b/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailSideEffect.kt @@ -0,0 +1,5 @@ +package com.spoony.spoony.presentation.placeDetail + +sealed class PlaceDetailSideEffect { + data class ShowSnackbar(val message: String) : PlaceDetailSideEffect() +} diff --git a/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailViewModel.kt b/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailViewModel.kt index eaad3b81..645fa81d 100644 --- a/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailViewModel.kt +++ b/app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailViewModel.kt @@ -9,7 +9,9 @@ import com.spoony.spoony.domain.repository.PostRepository import com.spoony.spoony.presentation.placeDetail.navigation.PlaceDetail import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @@ -22,6 +24,10 @@ class PlaceDetailViewModel @Inject constructor( val state: StateFlow get() = _state + private val _sideEffect = MutableSharedFlow() + val sideEffect: SharedFlow + get() = _sideEffect + init { val postArgs = savedStateHandle.toRoute() _state.value = _state.value.copy( @@ -69,6 +75,7 @@ class PlaceDetailViewModel @Inject constructor( viewModelScope.launch { postRepository.postAddMap(userId = userId, postId = postId) .onSuccess { response -> + _sideEffect.emit(PlaceDetailSideEffect.ShowSnackbar("내 지도에 추가되었어요.")) (_state.value.postEntity as? UiState.Success)?.data?.let { currentPostEntity -> with(currentPostEntity) { _state.value = _state.value.copy( @@ -92,6 +99,7 @@ class PlaceDetailViewModel @Inject constructor( viewModelScope.launch { postRepository.deletePinMap(userId = userId, postId = postId) .onSuccess { response -> + _sideEffect.emit(PlaceDetailSideEffect.ShowSnackbar("내 지도에서 삭제되었어요.")) (_state.value.postEntity as? UiState.Success)?.data?.let { currentPostEntity -> with(currentPostEntity) { _state.value = _state.value.copy( diff --git a/app/src/main/java/com/spoony/spoony/presentation/placeDetail/component/PlaceDetailBottomBar.kt b/app/src/main/java/com/spoony/spoony/presentation/placeDetail/component/PlaceDetailBottomBar.kt new file mode 100644 index 00000000..f1528946 --- /dev/null +++ b/app/src/main/java/com/spoony/spoony/presentation/placeDetail/component/PlaceDetailBottomBar.kt @@ -0,0 +1,113 @@ +package com.spoony.spoony.presentation.placeDetail.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import com.spoony.spoony.R +import com.spoony.spoony.core.designsystem.component.button.SpoonyButton +import com.spoony.spoony.core.designsystem.theme.SpoonyAndroidTheme +import com.spoony.spoony.core.designsystem.type.ButtonSize +import com.spoony.spoony.core.designsystem.type.ButtonStyle +import com.spoony.spoony.core.util.extension.noRippleClickable + +@Composable +fun PlaceDetailBottomBar( + addMapCount: Int, + onScoopButtonClick: () -> Unit, + onSearchMapClick: () -> Unit, + onAddMapButtonClick: () -> Unit, + onDeletePinMapButtonClick: () -> Unit, + modifier: Modifier = Modifier, + isScooped: Boolean = false, + isAddMap: Boolean = false +) { + Row( + modifier = modifier + .fillMaxWidth() + .background(SpoonyAndroidTheme.colors.white) + .padding( + horizontal = 20.dp, + vertical = 10.dp + ) + .height(IntrinsicSize.Max), + verticalAlignment = Alignment.CenterVertically + ) { + if (isScooped) { + SpoonyButton( + text = "길찾기", + onClick = onSearchMapClick, + style = ButtonStyle.Secondary, + size = ButtonSize.Medium, + modifier = Modifier.weight(1f) + ) + + Spacer(modifier = Modifier.width(15.dp)) + + Column( + modifier = Modifier + .fillMaxHeight() + .sizeIn(minWidth = 56.dp) + .noRippleClickable( + if (isAddMap) { + onDeletePinMapButtonClick + } else { + onAddMapButtonClick + } + ), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + imageVector = ImageVector.vectorResource(id = if (isAddMap) R.drawable.ic_add_map_main400_24 else R.drawable.ic_add_map_gray400_24), + modifier = Modifier + .size(32.dp), + contentDescription = null, + tint = Color.Unspecified + ) + + Spacer(modifier = Modifier.height(4.dp)) + + Text( + text = addMapCount.toString(), + style = SpoonyAndroidTheme.typography.caption1m, + color = if (isAddMap) SpoonyAndroidTheme.colors.main400 else SpoonyAndroidTheme.colors.gray800 + ) + } + } else { + SpoonyButton( + text = "떠먹기", + style = ButtonStyle.Secondary, + size = ButtonSize.Medium, + onClick = onScoopButtonClick, + modifier = Modifier.weight(1f), + icon = { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_button_spoon_32), + modifier = Modifier.size(32.dp), + contentDescription = null, + tint = Color.Unspecified + ) + } + ) + } + } +} diff --git a/app/src/main/java/com/spoony/spoony/presentation/report/ReportRoute.kt b/app/src/main/java/com/spoony/spoony/presentation/report/ReportRoute.kt index d884578b..02db413a 100644 --- a/app/src/main/java/com/spoony/spoony/presentation/report/ReportRoute.kt +++ b/app/src/main/java/com/spoony/spoony/presentation/report/ReportRoute.kt @@ -5,8 +5,13 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.ime +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState @@ -16,12 +21,14 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview @@ -91,11 +98,20 @@ private fun ReportScreen( onOpenDialogClick: () -> Unit ) { val focusManager = LocalFocusManager.current + val scrollState = rememberScrollState() + val imeInsets = WindowInsets.ime // 키보드 상태를 관찰 + val imeHeight = imeInsets.getBottom(LocalDensity.current) + + LaunchedEffect(imeHeight) { + if (imeHeight > 0) { + scrollState.scrollTo(scrollState.maxValue) + } + } Column( modifier = Modifier .addFocusCleaner(focusManager) - .padding(bottom = paddingValues.calculateBottomPadding()) + .navigationBarsPadding() ) { TitleTopAppBar( title = "신고하기", @@ -109,10 +125,11 @@ private fun ReportScreen( Column( modifier = Modifier - .fillMaxWidth() + .fillMaxSize() .background(color = SpoonyAndroidTheme.colors.white) .padding(horizontal = 20.dp) - .verticalScroll(rememberScrollState()) + .verticalScroll(scrollState) + .imePadding() ) { Spacer(modifier = Modifier.height(31.dp)) @@ -177,6 +194,8 @@ private fun ReportScreen( Spacer(modifier = Modifier.height(20.dp)) + Spacer(modifier = Modifier.weight(1f)) + SpoonyButton( text = "신고하기", onClick = onOpenDialogClick, diff --git a/app/src/main/java/com/spoony/spoony/presentation/report/component/ReportRadioButton.kt b/app/src/main/java/com/spoony/spoony/presentation/report/component/ReportRadioButton.kt index 96851d57..b97d457a 100644 --- a/app/src/main/java/com/spoony/spoony/presentation/report/component/ReportRadioButton.kt +++ b/app/src/main/java/com/spoony/spoony/presentation/report/component/ReportRadioButton.kt @@ -53,6 +53,8 @@ fun ReportRadioButton( Text( text = option.text, modifier = Modifier.noRippleClickable { onOptionSelected(option) }, + style = SpoonyAndroidTheme.typography.body1m, + color = SpoonyAndroidTheme.colors.gray900, maxLines = 1, overflow = TextOverflow.Ellipsis )