diff --git a/core/ui/src/main/kotlin/com/bff/wespot/ui/component/AutoSizeText.kt b/core/ui/src/main/kotlin/com/bff/wespot/ui/component/AutoSizeText.kt index 67fefd2c..96b4a828 100644 --- a/core/ui/src/main/kotlin/com/bff/wespot/ui/component/AutoSizeText.kt +++ b/core/ui/src/main/kotlin/com/bff/wespot/ui/component/AutoSizeText.kt @@ -3,7 +3,6 @@ package com.bff.wespot.ui.component import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.BoxWithConstraintsScope import androidx.compose.foundation.text.InlineTextContent -import androidx.compose.foundation.text.InternalFoundationTextApi import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -128,7 +127,6 @@ fun AutoSizeText( } } -@OptIn(InternalFoundationTextApi::class) @Composable private fun BoxWithConstraintsScope.shouldShrink( text: AnnotatedString, diff --git a/core/ui/src/main/kotlin/com/bff/wespot/ui/component/BottomButtonLayout.kt b/core/ui/src/main/kotlin/com/bff/wespot/ui/component/BottomButtonLayout.kt new file mode 100644 index 00000000..2cdbc504 --- /dev/null +++ b/core/ui/src/main/kotlin/com/bff/wespot/ui/component/BottomButtonLayout.kt @@ -0,0 +1,38 @@ +package com.bff.wespot.ui.component + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.SubcomposeLayout + +@Composable +fun BottomButtonLayout( + modifier: Modifier = Modifier, + showGradient: Boolean = false, + button: @Composable () -> Unit, + content: @Composable () -> Unit, +) { + SubcomposeLayout(modifier) { constraints -> + val gradientPlaceable = subcompose("gradient") { + ListBottomGradient(124) + }.first().measure(constraints) + + val buttonPlaceable = subcompose("button") { + button() + }.first().measure(constraints) + + val contentMaxHeight = constraints.maxHeight - buttonPlaceable.height + val contentPlaceable = subcompose("content") { + content() + }.first().measure(constraints.copy(maxHeight = contentMaxHeight)) + + layout(constraints.maxWidth, constraints.maxHeight) { + contentPlaceable.placeRelative(0, 0) + + if (showGradient) { + gradientPlaceable.placeRelative(0, constraints.maxHeight - gradientPlaceable.height) + } + + buttonPlaceable.placeRelative(0, contentMaxHeight) + } + } +} diff --git a/core/ui/src/main/kotlin/com/bff/wespot/ui/component/DotIndicators.kt b/core/ui/src/main/kotlin/com/bff/wespot/ui/component/DotIndicators.kt index 1ea35048..9660421b 100644 --- a/core/ui/src/main/kotlin/com/bff/wespot/ui/component/DotIndicators.kt +++ b/core/ui/src/main/kotlin/com/bff/wespot/ui/component/DotIndicators.kt @@ -1,6 +1,5 @@ package com.bff.wespot.ui.component -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -17,7 +16,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -@OptIn(ExperimentalFoundationApi::class) @Composable fun DotIndicators( pagerState: PagerState, diff --git a/core/ui/src/main/kotlin/com/bff/wespot/ui/component/WSCarousel.kt b/core/ui/src/main/kotlin/com/bff/wespot/ui/component/WSCarousel.kt index 7e5d0a2f..9bf2d5bb 100644 --- a/core/ui/src/main/kotlin/com/bff/wespot/ui/component/WSCarousel.kt +++ b/core/ui/src/main/kotlin/com/bff/wespot/ui/component/WSCarousel.kt @@ -1,6 +1,5 @@ package com.bff.wespot.ui.component -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -22,7 +21,6 @@ import com.bff.wespot.designsystem.theme.WeSpotTheme import com.bff.wespot.designsystem.util.OrientationPreviews import com.bff.wespot.ui.util.carouselTransition -@OptIn(ExperimentalFoundationApi::class) @Composable fun WSCarousel( pageCount: Int = 10, @@ -47,7 +45,6 @@ fun WSCarousel( } } -@OptIn(ExperimentalFoundationApi::class) @OrientationPreviews @Composable private fun PreviewWSCarousel() { diff --git a/core/ui/src/main/kotlin/com/bff/wespot/ui/util/ModifierUtil.kt b/core/ui/src/main/kotlin/com/bff/wespot/ui/util/ModifierUtil.kt index ade5610e..ffbc24b1 100644 --- a/core/ui/src/main/kotlin/com/bff/wespot/ui/util/ModifierUtil.kt +++ b/core/ui/src/main/kotlin/com/bff/wespot/ui/util/ModifierUtil.kt @@ -1,6 +1,5 @@ package com.bff.wespot.ui.util -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -14,7 +13,6 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.util.lerp import kotlin.math.absoluteValue -@OptIn(ExperimentalFoundationApi::class) fun Modifier.carouselTransition(pagerState: PagerState, page: Int) = graphicsLayer { val pageOffset = diff --git a/feature/entire/src/main/java/com/bff/wespot/entire/screen/edit/ProfileEditScreen.kt b/feature/entire/src/main/java/com/bff/wespot/entire/screen/edit/ProfileEditScreen.kt index 62424ed8..6186550f 100644 --- a/feature/entire/src/main/java/com/bff/wespot/entire/screen/edit/ProfileEditScreen.kt +++ b/feature/entire/src/main/java/com/bff/wespot/entire/screen/edit/ProfileEditScreen.kt @@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row 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.padding @@ -65,8 +64,8 @@ import com.bff.wespot.entire.state.edit.ProfileEditAction import com.bff.wespot.entire.state.edit.ProfileEditSideEffect import com.bff.wespot.entire.viewmodel.ProfileEditViewModel import com.bff.wespot.navigation.Navigator +import com.bff.wespot.ui.component.BottomButtonLayout import com.bff.wespot.ui.component.LetterCountIndicator -import com.bff.wespot.ui.component.ListBottomGradient import com.bff.wespot.ui.component.LoadingAnimation import com.bff.wespot.ui.component.TopToast import com.bff.wespot.ui.component.WSBottomSheet @@ -138,115 +137,112 @@ fun ProfileEditScreen( ) }, ) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(it) - .padding(horizontal = 20.dp) - .verticalScroll(scrollState), - horizontalAlignment = Alignment.CenterHorizontally, + BottomButtonLayout( + modifier = Modifier.padding(it), + button = { + val isEdited = state.profile.introduction != state.introductionInput || + state.profilePath != state.profile.profileCharacter.iconUrl + WSButton( + onClick = { + action(ProfileEditAction.OnProfileEditDoneButtonClicked) + }, + enabled = + isEdited && + state.hasProfanity.not() && + state.introductionInput.length in 0..20, + text = stringResource(id = R.string.edit_done), + content = { it() }, + ) + }, ) { - Box( + Column( modifier = Modifier - .padding(top = 16.dp) - .clickableSingle { - if (state.profilePath.isNullOrEmpty()) { - action(ProfileEditAction.OpenPicker) - } else { - action(ProfileEditAction.ChangeBottomSheetState(true)) - } - }, + .padding(horizontal = 20.dp) + .verticalScroll(scrollState), + horizontalAlignment = Alignment.CenterHorizontally, ) { - AsyncImage( - modifier = Modifier - .size(90.dp) - .clip(CircleShape), - model = ImageRequest.Builder(LocalContext.current) - .data(state.profilePath) - .error(com.bff.wespot.designsystem.R.drawable.default_image) - .fallback(com.bff.wespot.designsystem.R.drawable.default_image) - .placeholder(com.bff.wespot.designsystem.R.drawable.default_image) - .crossfade(true) - .build(), - contentDescription = stringResource( - com.bff.wespot.ui.R.string.user_character_image, - ), - ) - Box( modifier = Modifier - .size(24.dp) - .align(Alignment.BottomEnd) - .clip(WeSpotThemeManager.shapes.small) - .background(WeSpotThemeManager.colors.secondaryBtnColor) - .zIndex(1f), + .padding(top = 16.dp) + .clickableSingle { + if (state.profilePath.isNullOrEmpty()) { + action(ProfileEditAction.OpenPicker) + } else { + action(ProfileEditAction.ChangeBottomSheetState(true)) + } + }, ) { - Image( + AsyncImage( modifier = Modifier - .align(Alignment.Center) - .size(14.dp), - painter = painterResource(id = R.drawable.album), - contentDescription = stringResource(R.string.edit_icon), + .size(90.dp) + .clip(CircleShape), + model = ImageRequest.Builder(LocalContext.current) + .data(state.profilePath) + .error(com.bff.wespot.designsystem.R.drawable.default_image) + .fallback(com.bff.wespot.designsystem.R.drawable.default_image) + .placeholder(com.bff.wespot.designsystem.R.drawable.default_image) + .crossfade(true) + .build(), + contentDescription = stringResource( + com.bff.wespot.ui.R.string.user_character_image, + ), ) - } - } - - ProfileEditLockedItem( - title = stringResource(R.string.name), - content = state.profile.name, - onClick = { - focusManager.clearFocus() - action(ProfileEditAction.OnRequestDialogShown) - }, - ) - ProfileEditLockedItem( - title = stringResource(R.string.gender), - content = state.profile.toGenderKorean(), - onClick = { - focusManager.clearFocus() - action(ProfileEditAction.OnRequestDialogShown) - }, - ) + Box( + modifier = Modifier + .size(24.dp) + .align(Alignment.BottomEnd) + .clip(WeSpotThemeManager.shapes.small) + .background(WeSpotThemeManager.colors.secondaryBtnColor) + .zIndex(1f), + ) { + Image( + modifier = Modifier + .align(Alignment.Center) + .size(14.dp), + painter = painterResource(id = R.drawable.album), + contentDescription = stringResource(R.string.edit_icon), + ) + } + } - ProfileEditLockedItem( - title = stringResource(R.string.school_info), - content = state.profile.toSchoolInfo(), - onClick = { - focusManager.clearFocus() - action(ProfileEditAction.OnRequestDialogShown) - }, - ) + ProfileEditLockedItem( + title = stringResource(R.string.name), + content = state.profile.name, + onClick = { + focusManager.clearFocus() + action(ProfileEditAction.OnRequestDialogShown) + }, + ) - ProfileIntroductionItem( - title = stringResource(com.bff.wespot.ui.R.string.introduction), - content = state.introductionInput, - hasProfanity = state.hasProfanity, - onValueChange = { value -> action(ProfileEditAction.OnIntroductionChanged(value)) }, - onFocusChanged = { focusState -> - action(ProfileEditAction.OnProfileEditTextFieldFocused(focusState.isFocused)) - }, - ) + ProfileEditLockedItem( + title = stringResource(R.string.gender), + content = state.profile.toGenderKorean(), + onClick = { + focusManager.clearFocus() + action(ProfileEditAction.OnRequestDialogShown) + }, + ) - Spacer(modifier = Modifier.height(72.dp)) - } + ProfileEditLockedItem( + title = stringResource(R.string.school_info), + content = state.profile.toSchoolInfo(), + onClick = { + focusManager.clearFocus() + action(ProfileEditAction.OnRequestDialogShown) + }, + ) - Box( - modifier = Modifier - .fillMaxSize() - .padding(top = 10.dp), - contentAlignment = Alignment.BottomCenter, - ) { - ListBottomGradient(height = 124) - - WSButton( - onClick = { - action(ProfileEditAction.OnProfileEditDoneButtonClicked) - }, - enabled = state.isEditedProfile() && state.isValidIntroduce() && state.isLoading.not(), - text = stringResource(id = R.string.edit_done), - content = { it() }, - ) + ProfileIntroductionItem( + title = stringResource(com.bff.wespot.ui.R.string.introduction), + content = state.introductionInput, + hasProfanity = state.hasProfanity, + onValueChange = { value -> action(ProfileEditAction.OnIntroductionChanged(value)) }, + onFocusChanged = { focusState -> + action(ProfileEditAction.OnProfileEditTextFieldFocused(focusState.isFocused)) + }, + ) + } } } diff --git a/feature/entire/src/main/java/com/bff/wespot/entire/screen/setting/RevokeConfirmScreen.kt b/feature/entire/src/main/java/com/bff/wespot/entire/screen/setting/RevokeConfirmScreen.kt index bc7a76fc..3b0b99e3 100644 --- a/feature/entire/src/main/java/com/bff/wespot/entire/screen/setting/RevokeConfirmScreen.kt +++ b/feature/entire/src/main/java/com/bff/wespot/entire/screen/setting/RevokeConfirmScreen.kt @@ -2,13 +2,9 @@ package com.bff.wespot.entire.screen.setting import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -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.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -27,7 +23,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.compose.ui.zIndex import androidx.hilt.navigation.compose.hiltViewModel import com.bff.wespot.designsystem.component.button.WSButton import com.bff.wespot.designsystem.component.button.WSButtonType @@ -41,7 +36,7 @@ import com.bff.wespot.entire.state.EntireSideEffect import com.bff.wespot.entire.viewmodel.EntireViewModel import com.bff.wespot.navigation.Navigator import com.bff.wespot.navigation.util.EXTRA_TOAST_MESSAGE -import com.bff.wespot.ui.component.ListBottomGradient +import com.bff.wespot.ui.component.BottomButtonLayout import com.bff.wespot.ui.component.WSBottomSheet import com.bff.wespot.ui.component.WSSelectionItem import com.bff.wespot.ui.util.handleSideEffect @@ -91,69 +86,64 @@ fun RevokeConfirmScreen( ) }, ) { - Column( - modifier = Modifier - .padding(it) - .verticalScroll(scrollState), - verticalArrangement = Arrangement.spacedBy(12.dp), + BottomButtonLayout( + modifier = Modifier.padding(it), + showGradient = true, + button = { + WSButton( + text = stringResource(R.string.select_done), + buttonType = WSButtonType.Primary, + enabled = state.revokeReasonList.isNotEmpty(), + content = { it() }, + onClick = { showBottomSheet = true }, + ) + }, ) { - Text( - modifier = Modifier.padding(bottom = 16.dp, start = 24.dp, end = 24.dp), - text = stringResource(R.string.revoke_confirm_title), - style = StaticTypeScale.Default.header1, - color = WeSpotThemeManager.colors.txtTitleColor, - ) + Column( + modifier = Modifier.verticalScroll(scrollState), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Text( + modifier = Modifier.padding(bottom = 16.dp, start = 24.dp, end = 24.dp), + text = stringResource(R.string.revoke_confirm_title), + style = StaticTypeScale.Default.header1, + color = WeSpotThemeManager.colors.txtTitleColor, + ) - /** - * 아이템이 선택되었는지 판단하는 기준 - * 유저 입력 아이템인 경우, 따로 Boolean State를 통해 관리한다. - * 그외 아이템의 경우, 동적 리스트를 통해 관리한다. - */ - persistentListOf( - stringResource(R.string.feature_variety_lacking), - stringResource(R.string.lacking_friends), - stringResource(R.string.choices_variety_lacking), - stringResource(R.string.difficult_to_use), - ).forEach { reason -> + /** + * 아이템이 선택되었는지 판단하는 기준 + * 유저 입력 아이템인 경우, 따로 Boolean State를 통해 관리한다. + * 그외 아이템의 경우, 동적 리스트를 통해 관리한다. + */ + persistentListOf( + stringResource(R.string.feature_variety_lacking), + stringResource(R.string.lacking_friends), + stringResource(R.string.choices_variety_lacking), + stringResource(R.string.difficult_to_use), + ).forEach { reason -> + WSSelectionItem( + title = reason, + selected = reason in state.revokeReasonList, + onClick = { + action(EntireAction.OnRevokeReasonSelected(reason)) + }, + ) + } WSSelectionItem( - title = reason, - selected = reason in state.revokeReasonList, + title = state.inputRevokeReason, + selected = state.isInputRevokeReasonSelected, + isEditable = true, + onTitleChanged = { title -> + action(EntireAction.OnRevokeReasonChanged(title)) + }, onClick = { - action(EntireAction.OnRevokeReasonSelected(reason)) + action(EntireAction.OnInputRevokeReasonSelected) }, ) } - WSSelectionItem( - title = state.inputRevokeReason, - selected = state.isInputRevokeReasonSelected, - isEditable = true, - onTitleChanged = { title -> - action(EntireAction.OnRevokeReasonChanged(title)) - }, - onClick = { - action(EntireAction.OnInputRevokeReasonSelected) - }, - ) - - Spacer(modifier = Modifier.height(72.dp)) } } - Box( - modifier = Modifier.fillMaxSize().zIndex(1f), - contentAlignment = Alignment.BottomCenter, - ) { - ListBottomGradient(height = 120) - - WSButton( - text = stringResource(R.string.select_done), - buttonType = WSButtonType.Primary, - enabled = state.revokeReasonList.isNotEmpty(), - content = { it() }, - onClick = { showBottomSheet = true }, - ) - } - if (showBottomSheet) { WSBottomSheet(closeSheet = { showBottomSheet = false }) { RevokeBottomSheetContent( diff --git a/feature/message/src/main/kotlin/com/bff/wespot/message/screen/MessageReportScreen.kt b/feature/message/src/main/kotlin/com/bff/wespot/message/screen/MessageReportScreen.kt index 2eb2d7e3..524ceab4 100644 --- a/feature/message/src/main/kotlin/com/bff/wespot/message/screen/MessageReportScreen.kt +++ b/feature/message/src/main/kotlin/com/bff/wespot/message/screen/MessageReportScreen.kt @@ -2,11 +2,9 @@ package com.bff.wespot.message.screen import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -18,12 +16,10 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.compose.ui.zIndex import androidx.hilt.navigation.compose.hiltViewModel import com.bff.wespot.designsystem.component.button.WSButton import com.bff.wespot.designsystem.component.header.WSTopBar @@ -35,7 +31,7 @@ import com.bff.wespot.message.model.ReportReason import com.bff.wespot.message.state.report.ReportAction import com.bff.wespot.message.state.report.ReportSideEffect import com.bff.wespot.message.viewmodel.ReportViewModel -import com.bff.wespot.ui.component.ListBottomGradient +import com.bff.wespot.ui.component.BottomButtonLayout import com.bff.wespot.ui.component.WSSelectionItem import com.bff.wespot.ui.model.ToastState import com.bff.wespot.ui.util.handleSideEffect @@ -83,99 +79,91 @@ fun MessageReportScreen( ) }, ) { innerPadding -> - Column( - modifier = Modifier - .padding(innerPadding) - .verticalScroll(scrollState), - verticalArrangement = Arrangement.spacedBy(16.dp), + BottomButtonLayout( + modifier = Modifier.padding(innerPadding), + showGradient = true, + button = { + WSButton( + text = stringResource(R.string.choice_done), + content = { it() }, + onClick = { + if (state.reportReason.index != -1) { + action(ReportAction.OnMessageReportButtonClicked) + } + }, + ) + }, ) { - Text( - modifier = Modifier.padding(bottom = 16.dp, start = 30.dp, end = 30.dp), - text = stringResource(R.string.message_report_title), - style = StaticTypeScale.Default.header1, - color = WeSpotThemeManager.colors.txtTitleColor, - ) + Column( + modifier = Modifier.verticalScroll(scrollState), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + Text( + modifier = Modifier.padding(bottom = 16.dp, start = 30.dp, end = 30.dp), + text = stringResource(R.string.message_report_title), + style = StaticTypeScale.Default.header1, + color = WeSpotThemeManager.colors.txtTitleColor, + ) - Column(modifier = Modifier.padding(horizontal = 30.dp)) { - Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { - Image( - modifier = Modifier.size(24.dp), - painter = painterResource(id = R.drawable.error), - contentDescription = "error_icon", - ) + Column(modifier = Modifier.padding(horizontal = 30.dp)) { + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + Image( + modifier = Modifier.size(24.dp), + painter = painterResource(id = R.drawable.error), + contentDescription = "error_icon", + ) + + Text( + text = stringResource(id = R.string.notice_warning), + style = StaticTypeScale.Default.body3, + color = WeSpotThemeManager.colors.txtTitleColor, + ) + } + + Spacer(modifier = Modifier.height(8.dp)) Text( - text = stringResource(id = R.string.notice_warning), - style = StaticTypeScale.Default.body3, + text = stringResource(id = R.string.notice_restriction_warning), + style = StaticTypeScale.Default.body6, color = WeSpotThemeManager.colors.txtTitleColor, ) } - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = stringResource(id = R.string.notice_restriction_warning), - style = StaticTypeScale.Default.body6, - color = WeSpotThemeManager.colors.txtTitleColor, + modifier = Modifier.padding(horizontal = 30.dp), + text = stringResource(id = R.string.notice_message_block), + style = StaticTypeScale.Default.body8, + color = WeSpotThemeManager.colors.txtSubColor, ) - } - Text( - modifier = Modifier.padding(horizontal = 30.dp), - text = stringResource(id = R.string.notice_message_block), - style = StaticTypeScale.Default.body8, - color = WeSpotThemeManager.colors.txtSubColor, - ) + Spacer(modifier = Modifier.height(8.dp)) - Spacer(modifier = Modifier.height(8.dp)) - - Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { - persistentListOf( - stringResource(id = R.string.report_category_leakage), - stringResource(id = R.string.report_category_obscenity), - stringResource(id = R.string.report_category_abuse), - stringResource(id = R.string.report_category_advertisement), - stringResource(id = R.string.report_category_custom), - ).forEachIndexed { index, reason -> - val isUserInputItem = stringResource(R.string.report_category_custom) == reason - val selected = index == state.reportReason.index - - WSSelectionItem( - title = if (isUserInputItem && selected) state.inputReportReason else reason, - selected = selected, - isEditable = isUserInputItem, - onTitleChanged = { - action(ReportAction.OnReportReasonChanged(it)) - }, - onClick = { - action(ReportAction.OnReportReasonSelected(ReportReason(index, reason))) - }, - ) + Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { + persistentListOf( + stringResource(id = R.string.report_category_leakage), + stringResource(id = R.string.report_category_obscenity), + stringResource(id = R.string.report_category_abuse), + stringResource(id = R.string.report_category_advertisement), + stringResource(id = R.string.report_category_custom), + ).forEachIndexed { index, reason -> + val isUserInputItem = stringResource(R.string.report_category_custom) == reason + val selected = index == state.reportReason.index + + WSSelectionItem( + title = if (isUserInputItem && selected) state.inputReportReason else reason, + selected = selected, + isEditable = isUserInputItem, + onTitleChanged = { + action(ReportAction.OnReportReasonChanged(it)) + }, + onClick = { + action(ReportAction.OnReportReasonSelected(ReportReason(index, reason))) + }, + ) + } } - - /** 버튼에 가려진 영역 만큼 하단 여백 추가 */ - Spacer(modifier = Modifier.height(72.dp)) } } - - Box( - modifier = Modifier - .fillMaxSize() - .zIndex(1f), - contentAlignment = Alignment.BottomCenter, - ) { - ListBottomGradient(height = 120) - - WSButton( - text = stringResource(R.string.choice_done), - content = { it() }, - onClick = { - if (state.reportReason.index != -1) { - action(ReportAction.OnMessageReportButtonClicked) - } - }, - ) - } } LaunchedEffect(Unit) { diff --git a/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/MessageEditScreen.kt b/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/MessageEditScreen.kt index 65567a6b..8fd58718 100644 --- a/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/MessageEditScreen.kt +++ b/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/MessageEditScreen.kt @@ -43,6 +43,7 @@ import com.bff.wespot.message.component.SendExitDialog import com.bff.wespot.message.state.send.SendAction import com.bff.wespot.message.state.send.SendSideEffect import com.bff.wespot.message.viewmodel.SendViewModel +import com.bff.wespot.ui.component.BottomButtonLayout import com.bff.wespot.ui.component.LetterCountIndicator import com.bff.wespot.ui.component.LoadingAnimation import com.bff.wespot.ui.component.NetworkDialog @@ -143,95 +144,89 @@ fun MessageEditScreen( ) }, ) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(it) - .verticalScroll(scrollState), - ) { - EditField( - title = stringResource(R.string.receiver), - value = state.selectedUser.toDescription(), - ) { - navigator.navigateReceiverSelectionScreen( - args = ReceiverSelectionScreenArgs(isEditing = true), - ) - } - - Spacer(modifier = Modifier.height(16.dp)) - - EditField( - title = stringResource(R.string.message_sent_content), - value = state.messageInput, - isMessageContent = true, - ) { - navigator.navigateMessageWriteScreen( - args = MessageWriteScreenArgs(isEditing = true), + BottomButtonLayout( + modifier = Modifier.padding(it), + button = { + WSButton( + onClick = { + if (state.isReservedMessage) { + action(SendAction.OnEditButtonClicked(navArgs.messageId)) + } else { + reserveDialog = true + } + }, + text = stringResource( + if (state.isReservedMessage) R.string.edit_done else R.string.message_send, + ), + content = { it() }, ) - } - - Box( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 20.dp), - contentAlignment = Alignment.CenterEnd, - ) { - LetterCountIndicator(currentCount = state.messageInput.length, maxCount = 200) - } + }, + ) { + Column(modifier = Modifier.verticalScroll(scrollState)) { + EditField( + title = stringResource(R.string.receiver), + value = state.selectedUser.toDescription(), + ) { + navigator.navigateReceiverSelectionScreen( + args = ReceiverSelectionScreenArgs(isEditing = true), + ) + } - EditField( - title = stringResource(R.string.sender), - value = if (state.isRandomName) state.randomName else state.sender, - onClicked = { toast = true }, - ) + Spacer(modifier = Modifier.height(16.dp)) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(top = 24.dp, start = 30.dp, end = 24.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Column( - verticalArrangement = Arrangement.spacedBy(6.dp), + EditField( + title = stringResource(R.string.message_sent_content), + value = state.messageInput, + isMessageContent = true, ) { - Text( - text = stringResource(R.string.random_nickname_title), - style = StaticTypeScale.Default.body1, - color = WeSpotThemeManager.colors.txtTitleColor, + navigator.navigateMessageWriteScreen( + args = MessageWriteScreenArgs(isEditing = true), ) + } - Text( - text = stringResource(R.string.random_nickname_subtitle), - style = StaticTypeScale.Default.body8, - color = Gray400, - ) + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + contentAlignment = Alignment.CenterEnd, + ) { + LetterCountIndicator(currentCount = state.messageInput.length, maxCount = 200) } - Spacer(modifier = Modifier.weight(1f)) + EditField( + title = stringResource(R.string.sender), + value = if (state.isRandomName) state.randomName else state.sender, + onClicked = { toast = true }, + ) - WSSwitch(checked = state.isRandomName) { - action(SendAction.OnRandomNameToggled(it)) - } - } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 24.dp, start = 30.dp, end = 24.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Column( + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + Text( + text = stringResource(R.string.random_nickname_title), + style = StaticTypeScale.Default.body1, + color = WeSpotThemeManager.colors.txtTitleColor, + ) + + Text( + text = stringResource(R.string.random_nickname_subtitle), + style = StaticTypeScale.Default.body8, + color = Gray400, + ) + } - // 버튼과 버튼 외부 패딩만큼 높이를 추가한다. - Spacer(modifier = Modifier.height(82.dp)) - } + Spacer(modifier = Modifier.weight(1f)) - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter) { - WSButton( - onClick = { - if (state.isReservedMessage) { - action(SendAction.OnEditButtonClicked(navArgs.messageId)) - } else { - reserveDialog = true + WSSwitch(checked = state.isRandomName) { + action(SendAction.OnRandomNameToggled(it)) } - }, - text = stringResource( - if (state.isReservedMessage) R.string.edit_done else R.string.message_send, - ), - ) { - it() + } } } diff --git a/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/MessageWriteScreen.kt b/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/MessageWriteScreen.kt index d8db949c..0f2280c4 100644 --- a/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/MessageWriteScreen.kt +++ b/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/MessageWriteScreen.kt @@ -2,11 +2,9 @@ package com.bff.wespot.message.screen.send import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api @@ -37,6 +35,7 @@ import com.bff.wespot.message.common.MESSAGE_MAX_LENGTH import com.bff.wespot.message.component.SendExitDialog import com.bff.wespot.message.state.send.SendAction import com.bff.wespot.message.viewmodel.SendViewModel +import com.bff.wespot.ui.component.BottomButtonLayout import com.bff.wespot.ui.component.LetterCountIndicator import com.bff.wespot.ui.component.NetworkDialog import com.bff.wespot.ui.util.handleSideEffect @@ -96,76 +95,74 @@ fun MessageWriteScreen( ) }, ) { - Column( - modifier = Modifier - .padding(it) - .padding(horizontal = 20.dp), + BottomButtonLayout( + modifier = Modifier.padding(it), + button = { + WSButton( + onClick = { + if (state.isReservedMessage) { + navigator.navigateUp() + } else { + navigator.navigateMessageEditScreen(EditMessageScreenArgs()) + } + }, + enabled = state.messageInput.length in 1..MESSAGE_MAX_LENGTH && state.hasProfanity.not(), + text = stringResource( + if (navArgs.isEditing) R.string.edit_done else R.string.write_done, + ), + content = { it() }, + ) + }, ) { - Text( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 3.dp), - text = stringResource(R.string.message_write_title), - style = StaticTypeScale.Default.header1, - color = WeSpotThemeManager.colors.txtTitleColor, - ) - - Spacer(modifier = Modifier.padding(top = 16.dp)) - - WsTextField( - value = state.messageInput, - onValueChange = { text -> - action(SendAction.OnMessageChanged(text)) - }, - placeholder = stringResource(R.string.message_write_text_holder), - isError = false, - focusRequester = focusRequester, - textFieldType = WsTextFieldType.Message, - ) + Column(modifier = Modifier.padding(horizontal = 20.dp)) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 3.dp), + text = stringResource(R.string.message_write_title), + style = StaticTypeScale.Default.header1, + color = WeSpotThemeManager.colors.txtTitleColor, + ) - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - ) { - val warningMessage = when { - state.messageInput.length > MESSAGE_MAX_LENGTH -> { - stringResource(R.string.message_length_limit) - } + Spacer(modifier = Modifier.padding(top = 16.dp)) + + WsTextField( + value = state.messageInput, + onValueChange = { text -> + action(SendAction.OnMessageChanged(text)) + }, + placeholder = stringResource(R.string.message_write_text_holder), + isError = false, + focusRequester = focusRequester, + textFieldType = WsTextFieldType.Message, + ) - state.hasProfanity -> { - stringResource(com.bff.wespot.designsystem.R.string.has_profanity) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + val warningMessage = when { + state.messageInput.length > MESSAGE_MAX_LENGTH -> { + stringResource(R.string.message_length_limit) + } + + state.hasProfanity -> { + stringResource(com.bff.wespot.designsystem.R.string.has_profanity) + } + else -> "" } - else -> "" - } - Text( - modifier = Modifier.padding(top = 5.dp, start = 10.dp, end = 10.dp), - text = warningMessage, - style = StaticTypeScale.Default.body7, - color = WeSpotThemeManager.colors.dangerColor, - ) - - LetterCountIndicator(currentCount = state.messageInput.length, maxCount = 200) - } - } - } + Text( + modifier = Modifier.padding(top = 5.dp, start = 10.dp, end = 10.dp), + text = warningMessage, + style = StaticTypeScale.Default.body7, + color = WeSpotThemeManager.colors.dangerColor, + ) - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter) { - WSButton( - onClick = { - if (state.isReservedMessage) { - navigator.navigateUp() - } else { - navigator.navigateMessageEditScreen(EditMessageScreenArgs()) + LetterCountIndicator(currentCount = state.messageInput.length, maxCount = 200) } - }, - enabled = state.messageInput.length in 1..MESSAGE_MAX_LENGTH && state.hasProfanity.not(), - text = stringResource( - if (navArgs.isEditing) R.string.edit_done else R.string.write_done, - ), - ) { - it() + } } } diff --git a/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/ReceiverSelectionScreen.kt b/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/ReceiverSelectionScreen.kt index 854a6b25..684e1057 100644 --- a/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/ReceiverSelectionScreen.kt +++ b/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/ReceiverSelectionScreen.kt @@ -5,10 +5,8 @@ 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.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.ExperimentalMaterial3Api @@ -29,7 +27,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.compose.ui.zIndex import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.itemKey @@ -48,7 +45,7 @@ import com.bff.wespot.message.state.send.SendAction import com.bff.wespot.message.viewmodel.SendViewModel import com.bff.wespot.model.common.KakaoContent import com.bff.wespot.navigation.Navigator -import com.bff.wespot.ui.component.ListBottomGradient +import com.bff.wespot.ui.component.BottomButtonLayout import com.bff.wespot.ui.component.NetworkDialog import com.bff.wespot.ui.component.WSListItem import com.bff.wespot.ui.util.handleSideEffect @@ -108,135 +105,132 @@ fun ReceiverSelectionScreen( ) }, ) { - Column( + BottomButtonLayout( modifier = Modifier .clickable( indication = null, interactionSource = interactionSource, onClick = { keyboard?.hide() }, ) - .padding(it) - .padding(horizontal = 20.dp), + .padding(it), + showGradient = true, + button = { + WSButton( + onClick = { + if (navArgs.isEditing) { + navigator.navigateUp() + return@WSButton + } + navigator.navigateMessageWriteScreen( + args = MessageWriteScreenArgs(isEditing = false), + ) + }, + enabled = state.selectedUser.name.isNotBlank(), + text = if (navArgs.isEditing) { + stringResource(R.string.edit_done) + } else { + stringResource(R.string.next) + }, + content = { it() }, + ) + }, ) { - Text( - modifier = Modifier - .padding(horizontal = 4.dp), - text = stringResource(R.string.receiver_screen_title, state.profile.name), - style = StaticTypeScale.Default.header1, - color = WeSpotThemeManager.colors.txtTitleColor, - ) + Column(modifier = Modifier.padding(horizontal = 20.dp)) { + Text( + modifier = Modifier + .padding(horizontal = 4.dp), + text = stringResource(R.string.receiver_screen_title, state.profile.name), + style = StaticTypeScale.Default.header1, + color = WeSpotThemeManager.colors.txtTitleColor, + ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(16.dp)) - WsTextField( - value = state.nameInput, - onValueChange = { - action(SendAction.OnSearchContentChanged(it)) - }, - placeholder = stringResource(R.string.receiver_search_text_field_placeholder), - textFieldType = WsTextFieldType.Search, - focusRequester = focusRequester, - singleLine = true, - ) + WsTextField( + value = state.nameInput, + onValueChange = { + action(SendAction.OnSearchContentChanged(it)) + }, + placeholder = stringResource(R.string.receiver_search_text_field_placeholder), + textFieldType = WsTextFieldType.Search, + focusRequester = focusRequester, + singleLine = true, + ) - if (pagingData.itemCount == 0) { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(top = 24.dp), - contentAlignment = Alignment.Center, - ) { - Text( + if (pagingData.itemCount == 0) { + Box( modifier = Modifier - .drawBehind { - drawLine( - strokeWidth = 1f * density, - color = Gray300, - start = Offset(0f, size.height), - end = Offset(size.width, size.height), - ) - } - .clickable { - if (state.kakaoContent != KakaoContent.EMPTY) { - activityNavigator.navigateToKakao( - context = context, - title = state.kakaoContent.title, - description = state.kakaoContent.description, - imageUrl = state.kakaoContent.imageUrl, - buttonText = state.kakaoContent.buttonText, - url = state.kakaoContent.url, + .fillMaxWidth() + .padding(top = 24.dp), + contentAlignment = Alignment.Center, + ) { + Text( + modifier = Modifier + .drawBehind { + drawLine( + strokeWidth = 1f * density, + color = Gray300, + start = Offset(0f, size.height), + end = Offset(size.width, size.height), ) } - }, - text = stringResource(R.string.invite_friend_text), - style = StaticTypeScale.Default.body5, - color = WeSpotThemeManager.colors.txtSubColor, - ) + .clickable { + if (state.kakaoContent != KakaoContent.EMPTY) { + activityNavigator.navigateToKakao( + context = context, + title = state.kakaoContent.title, + description = state.kakaoContent.description, + imageUrl = state.kakaoContent.imageUrl, + buttonText = state.kakaoContent.buttonText, + url = state.kakaoContent.url, + ) + } + }, + text = stringResource(R.string.invite_friend_text), + style = StaticTypeScale.Default.body5, + color = WeSpotThemeManager.colors.txtSubColor, + ) + } } - } - LazyColumn( - modifier = Modifier.padding(top = 16.dp, bottom = 74.dp), - ) { - items( - pagingData.itemCount, - key = pagingData.itemKey { it.id }, - ) { index -> - val item = pagingData[index] + LazyColumn( + modifier = Modifier.padding(top = 16.dp), + ) { + items( + pagingData.itemCount, + key = pagingData.itemKey { key -> key.id }, + ) { index -> + val item = pagingData[index] - item?.let { - WSListItem( - title = item.name, - subTitle = item.toSchoolInfo(), - selected = state.selectedUser.id == item.id, - backgroundColor = item.profileCharacter.backgroundColor, - onClick = { - keyboard?.hide() - action(SendAction.OnUserSelected(item)) - }, - imageContent = { - AsyncImage( - model = ImageRequest.Builder(LocalContext.current) - .data(item.profileCharacter.iconUrl) - .crossfade(true) - .build(), - contentDescription = stringResource( - com.bff.wespot.ui.R.string.user_character_image, - ), - ) - }, - ) + item?.let { + WSListItem( + title = item.name, + subTitle = item.toSchoolInfo(), + selected = state.selectedUser.id == item.id, + backgroundColor = item.profileCharacter.backgroundColor, + onClick = { + keyboard?.hide() + action(SendAction.OnUserSelected(item)) + }, + imageContent = { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(item.profileCharacter.iconUrl) + .crossfade(true) + .build(), + contentDescription = stringResource( + com.bff.wespot.ui.R.string.user_character_image, + ), + ) + }, + ) + } } } } } } - Box( - modifier = Modifier - .fillMaxSize() - .imePadding() - .zIndex(1f), - contentAlignment = Alignment.BottomCenter, - ) { - ListBottomGradient(height = 124) - - WSButton( - onClick = { - if (navArgs.isEditing) { - navigator.navigateUp() - return@WSButton - } - navigator.navigateMessageWriteScreen( - args = MessageWriteScreenArgs(isEditing = false), - ) - }, - enabled = state.selectedUser.name.isNotBlank(), - text = if (navArgs.isEditing) stringResource(R.string.edit_done) else stringResource(R.string.next), - content = { it() }, - ) - } - if (dialogState) { SendExitDialog( isReservedMessage = state.isReservedMessage, diff --git a/feature/vote/src/main/java/com/bff/wespot/vote/screen/IndividualVoteScreen.kt b/feature/vote/src/main/java/com/bff/wespot/vote/screen/IndividualVoteScreen.kt index 072a0e0a..dc5b4565 100644 --- a/feature/vote/src/main/java/com/bff/wespot/vote/screen/IndividualVoteScreen.kt +++ b/feature/vote/src/main/java/com/bff/wespot/vote/screen/IndividualVoteScreen.kt @@ -3,7 +3,6 @@ package com.bff.wespot.vote.screen import android.app.Activity import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues @@ -63,7 +62,7 @@ interface IndividualVoteNavigator { fun navigateUp() } -@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) +@OptIn(ExperimentalMaterial3Api::class) @Destination( navArgsDelegate = IndividualVoteArgs::class, ) diff --git a/feature/vote/src/main/java/com/bff/wespot/vote/screen/VoteResultScreen.kt b/feature/vote/src/main/java/com/bff/wespot/vote/screen/VoteResultScreen.kt index d4cedbf7..b17525ef 100644 --- a/feature/vote/src/main/java/com/bff/wespot/vote/screen/VoteResultScreen.kt +++ b/feature/vote/src/main/java/com/bff/wespot/vote/screen/VoteResultScreen.kt @@ -5,7 +5,6 @@ import android.os.Build import androidx.activity.compose.BackHandler import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectDragGestures @@ -106,7 +105,7 @@ data class VoteResultScreenArgs( val isTodayVoteResult: Boolean = false, ) -@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class) @Destination( navArgsDelegate = VoteResultScreenArgs::class, ) diff --git a/feature/vote/src/main/java/com/bff/wespot/vote/ui/VoteCard.kt b/feature/vote/src/main/java/com/bff/wespot/vote/ui/VoteCard.kt index 10a43ea9..906e8672 100644 --- a/feature/vote/src/main/java/com/bff/wespot/vote/ui/VoteCard.kt +++ b/feature/vote/src/main/java/com/bff/wespot/vote/ui/VoteCard.kt @@ -1,6 +1,5 @@ package com.bff.wespot.vote.ui -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement @@ -51,7 +50,6 @@ import com.bff.wespot.ui.component.MultiLineText import com.bff.wespot.ui.util.carouselTransition import com.bff.wespot.vote.R -@OptIn(ExperimentalFoundationApi::class) @Composable internal fun VoteCard( result: Result, @@ -219,7 +217,6 @@ internal fun VoteCard( } } -@OptIn(ExperimentalFoundationApi::class) @OrientationPreviews @Composable private fun PreviewVoteCard() {