From 0a4e7331ec2859cf262b1b3764893eee055d09f1 Mon Sep 17 00:00:00 2001 From: Heon Date: Mon, 6 Oct 2025 22:04:31 +0900 Subject: [PATCH 01/14] chore: network module domain dependency --- core/network/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index 355cba4d..037f5fcc 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -37,6 +37,7 @@ android { dependencies { implementation(projects.core.common) implementation(projects.core.model) + implementation(projects.domain) implementation(libs.androidx.core.ktx) implementation(libs.appcompat) implementation(libs.material) From 4765958988b5dd91254f71022fc7506d4a40c36c Mon Sep 17 00:00:00 2001 From: Heon Date: Mon, 6 Oct 2025 22:07:26 +0900 Subject: [PATCH 02/14] feat: outline tag component to design system --- .../design_system/component/tag/Tag.kt | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/core/design-system/src/main/java/com/moneymong/moneymong/design_system/component/tag/Tag.kt b/core/design-system/src/main/java/com/moneymong/moneymong/design_system/component/tag/Tag.kt index 2f60719a..dfdd20fc 100644 --- a/core/design-system/src/main/java/com/moneymong/moneymong/design_system/component/tag/Tag.kt +++ b/core/design-system/src/main/java/com/moneymong/moneymong/design_system/component/tag/Tag.kt @@ -2,6 +2,7 @@ package com.moneymong.moneymong.design_system.component.tag import androidx.annotation.DrawableRes import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding @@ -18,7 +19,12 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.moneymong.moneymong.design_system.theme.Blue04 import com.moneymong.moneymong.design_system.theme.Body2 +import com.moneymong.moneymong.design_system.theme.Body4 +import com.moneymong.moneymong.design_system.theme.Gray03 +import com.moneymong.moneymong.design_system.theme.Gray05 +import com.moneymong.moneymong.design_system.theme.Gray06 import com.moneymong.moneymong.design_system.theme.White +import com.moneymong.moneymong.ui.noRippleClickable @Composable fun MDSTag( @@ -54,6 +60,46 @@ fun MDSTag( } } +@Composable +fun MDSOutlineTag( + modifier: Modifier = Modifier, + text: String, + @DrawableRes iconResource: Int? = null, + onClick: () -> Unit, +) { + Row( + modifier = modifier + .border( + width = 1.4.dp, + color = Gray03, + shape = RoundedCornerShape(size = Int.MAX_VALUE.dp) + ) + .background( + color = White, + shape = RoundedCornerShape(size = Int.MAX_VALUE.dp) + ) + .padding(horizontal = 12.dp, vertical = 6.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = text, + color = Gray06, + style = Body4, + ) + if (iconResource != null) { + Icon( + modifier = Modifier + .size(18.dp) + .noRippleClickable(onClick), + painter = painterResource(id = iconResource), + contentDescription = "Tag icon", + tint = Gray05 + ) + } + } +} + @Preview(showBackground = true) @Composable fun MDSTagPreview() { @@ -73,4 +119,23 @@ fun MDSTagPreview() { iconResource = com.moneymong.moneymong.design_system.R.drawable.ic_pencil ) } -} \ No newline at end of file +} + +@Preview(showBackground = true) +@Composable +fun MDSOutlineTagPreview() { + Row( + modifier = Modifier.padding(8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + MDSOutlineTag( + text = "tag", + onClick = {}, + ) + MDSOutlineTag( + text = "tag", + iconResource = com.moneymong.moneymong.design_system.R.drawable.ic_close_default, + onClick = {}, + ) + } +} From 7f3c4196154568971e1b3550337bb7cd61e9ab2f Mon Sep 17 00:00:00 2001 From: Heon Date: Mon, 6 Oct 2025 22:13:43 +0900 Subject: [PATCH 03/14] feat: category bottomsheet to ledger manual --- .../ledgermanual/LedgerManualScreen.kt | 51 +++++++- .../ledgermanual/LedgerManualState.kt | 3 +- .../ledgermanual/LedgerManualViewModel.kt | 4 + .../view/LedgerManualCategoryBottomSheet.kt | 121 ++++++++++++++++++ 4 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt diff --git a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualScreen.kt b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualScreen.kt index 067b91a7..6f0975c0 100644 --- a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualScreen.kt +++ b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualScreen.kt @@ -26,13 +26,17 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState 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 @@ -68,18 +72,21 @@ import com.moneymong.moneymong.design_system.component.textfield.visualtransform import com.moneymong.moneymong.design_system.component.textfield.visualtransformation.TimeVisualTransformation import com.moneymong.moneymong.design_system.error.ErrorDialog import com.moneymong.moneymong.design_system.theme.Blue03 +import com.moneymong.moneymong.design_system.theme.Blue04 import com.moneymong.moneymong.design_system.theme.Body2 import com.moneymong.moneymong.design_system.theme.Body3 import com.moneymong.moneymong.design_system.theme.Gray06 import com.moneymong.moneymong.design_system.theme.Gray10 import com.moneymong.moneymong.design_system.theme.MMHorizontalSpacing import com.moneymong.moneymong.design_system.theme.White +import com.moneymong.moneymong.ledgermanual.view.LedgerManualCategoryBottomSheet import com.moneymong.moneymong.ledgermanual.view.LedgerManualTopbarView import com.moneymong.moneymong.model.ledger.FundType +import kotlinx.coroutines.launch import org.orbitmvi.orbit.compose.collectAsState import org.orbitmvi.orbit.compose.collectSideEffect -@OptIn(ExperimentalGlideComposeApi::class) +@OptIn(ExperimentalGlideComposeApi::class, ExperimentalMaterial3Api::class) @Composable fun LedgerManualScreen( modifier: Modifier = Modifier, @@ -99,6 +106,8 @@ fun LedgerManualScreen( } } ) + val sheetState = rememberModalBottomSheetState() + val scope = rememberCoroutineScope() viewModel.collectSideEffect { when (it) { @@ -137,6 +146,14 @@ fun LedgerManualScreen( BackHandler(onBack = { viewModel.eventEmit(LedgerManualSideEffect.LedgerManualShowPopBackStackModal) }) + LaunchedEffect(state.showBottomSheet) { + if (state.showBottomSheet) { + sheetState.show() + } else { + sheetState.hide() + } + } + if (state.showPopBackStackModal) { MDSModal( icon = drawable.ic_warning_filled, @@ -168,6 +185,19 @@ fun LedgerManualScreen( ) } + if (state.showBottomSheet) { + LedgerManualCategoryBottomSheet( + sheetState = sheetState, + categories = emptyList(), + onDismissRequest = { + scope.launch { + sheetState.hide() + viewModel.onDismissBottomSheet() + } + } + ) + } + Scaffold( topBar = { LedgerManualTopbarView( @@ -311,6 +341,25 @@ fun LedgerManualScreen( keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }) ) Spacer(modifier = Modifier.height(24.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = "카테고리", + style = Body2, + color = Gray06, + ) + Spacer(modifier = Modifier.weight(1f)) + Text( + modifier = Modifier.noRippleClickable(viewModel::onClickCategoryEdit), + text = "수정", + style = Body2, + color = Blue04, + ) + Spacer(modifier = Modifier.height(8.dp)) + } + Spacer(modifier = Modifier.height(24.dp)) Text( text = "사진 첨부 (최대 12장)", style = Body2, diff --git a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualState.kt b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualState.kt index 294d825a..bd29af4d 100644 --- a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualState.kt +++ b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualState.kt @@ -25,7 +25,8 @@ data class LedgerManualState( val isMemoError: Boolean = false, val showPopBackStackModal: Boolean = false, val showErrorDialog: Boolean = false, - val errorMessage: String = "" + val errorMessage: String = "", + val showBottomSheet: Boolean = false, ) : State { val enabled: Boolean diff --git a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualViewModel.kt b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualViewModel.kt index ac10bc8a..4f0f9b10 100644 --- a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualViewModel.kt +++ b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualViewModel.kt @@ -177,6 +177,10 @@ class LedgerManualViewModel @Inject constructor( fun onClickErrorDialogConfirm() = eventEmit(LedgerManualSideEffect.LedgerManualHideErrorDialog) + fun onClickCategoryEdit() = intent { reduce { state.copy(showBottomSheet = true) } } + + fun onDismissBottomSheet() = intent { reduce { state.copy(showBottomSheet = false) } } + private fun trimStartWithZero(value: TextFieldValue) = if (value.text.isNotEmpty() && value.text.all { it == '0' }) { value.copy(text = "0") diff --git a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt new file mode 100644 index 00000000..89bd4dae --- /dev/null +++ b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt @@ -0,0 +1,121 @@ +package com.moneymong.moneymong.ledgermanual.view + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.SheetState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.moneymong.moneymong.design_system.theme.MMHorizontalSpacing +import com.moneymong.moneymong.design_system.R +import com.moneymong.moneymong.design_system.component.bottomSheet.MDSBottomSheet +import com.moneymong.moneymong.design_system.component.tag.MDSOutlineTag +import com.moneymong.moneymong.design_system.theme.Blue04 +import com.moneymong.moneymong.design_system.theme.Body2 +import com.moneymong.moneymong.design_system.theme.Body3 +import com.moneymong.moneymong.design_system.theme.Gray05 +import com.moneymong.moneymong.design_system.theme.Gray10 +import com.moneymong.moneymong.design_system.theme.Heading4 +import com.moneymong.moneymong.design_system.theme.White +import com.moneymong.moneymong.ui.noRippleClickable + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun LedgerManualCategoryBottomSheet( + modifier: Modifier = Modifier, + sheetState: SheetState, + categories: List, // TODO API response + onDismissRequest: () -> Unit +) { + MDSBottomSheet( + modifier = modifier, + sheetState = sheetState, + onDismissRequest = onDismissRequest, + ) { + LedgerManualCategoryBottomSheetContent( + categories = categories, + onDismissRequest = onDismissRequest, + ) + } +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun LedgerManualCategoryBottomSheetContent( + modifier: Modifier = Modifier, + categories: List, + onDismissRequest: () -> Unit, +) { + Column( + modifier = modifier + .fillMaxWidth() + .height(448.dp) + .background(White) + .padding(horizontal = MMHorizontalSpacing, vertical = 20.dp), + ) { + Icon( + modifier = Modifier + .align(alignment = Alignment.End) + .noRippleClickable(onDismissRequest), + painter = painterResource(R.drawable.ic_close_default), + contentDescription = null + ) + Spacer(modifier = Modifier.height(4.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + style = Heading4, + color = Gray10, + text = "카테고리", + ) + Text( + style = Body3, + color = Blue04, + text = "추가", + ) + } + Spacer(modifier = Modifier.height(4.dp)) + Text( + style = Body2, + color = Gray05, + text = "원하는 카테고리를 마음대로 만들 수 있어요", + ) + Spacer(modifier = Modifier.height(16.dp)) + FlowRow( + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + categories.forEach { + MDSOutlineTag( + text = it, + iconResource = R.drawable.ic_close_default, + onClick = {}, + ) + } + } + } +} + +@Preview(showBackground = true) +@Composable +fun LedgerManualCategoryBottomSheetContentPreview() { + val categories = listOf("testTooLongTextOverFlow", "test") + + LedgerManualCategoryBottomSheetContent(categories = categories) {} +} \ No newline at end of file From a1e30760c82c7cf01c8a7ec5d3b71bd044062c6b Mon Sep 17 00:00:00 2001 From: Heon Date: Tue, 7 Oct 2025 20:20:44 +0900 Subject: [PATCH 04/14] feat: create category bottom sheet & interaction --- .../view/LedgerManualBottomSheetType.kt | 7 + .../view/LedgerManualCategoryBottomSheet.kt | 171 +++++++++++++++++- 2 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualBottomSheetType.kt diff --git a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualBottomSheetType.kt b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualBottomSheetType.kt new file mode 100644 index 00000000..4147217b --- /dev/null +++ b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualBottomSheetType.kt @@ -0,0 +1,7 @@ +package com.moneymong.moneymong.ledgermanual.view + +enum class LedgerManualBottomSheetType { + LIST, + CREATE, + ; +} \ No newline at end of file diff --git a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt index 89bd4dae..6f2cb117 100644 --- a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt +++ b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt @@ -1,5 +1,10 @@ package com.moneymong.moneymong.ledgermanual.view +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -10,25 +15,44 @@ import androidx.compose.foundation.layout.Spacer 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.material.Icon import androidx.compose.material.Text import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SheetState 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.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.moneymong.moneymong.design_system.theme.MMHorizontalSpacing import com.moneymong.moneymong.design_system.R import com.moneymong.moneymong.design_system.component.bottomSheet.MDSBottomSheet +import com.moneymong.moneymong.design_system.component.button.MDSButton +import com.moneymong.moneymong.design_system.component.button.MDSButtonSize +import com.moneymong.moneymong.design_system.component.button.MDSButtonType import com.moneymong.moneymong.design_system.component.tag.MDSOutlineTag +import com.moneymong.moneymong.design_system.component.textfield.MDSTextField +import com.moneymong.moneymong.design_system.component.textfield.util.MDSTextFieldIcons +import com.moneymong.moneymong.design_system.theme.Black import com.moneymong.moneymong.design_system.theme.Blue04 import com.moneymong.moneymong.design_system.theme.Body2 import com.moneymong.moneymong.design_system.theme.Body3 import com.moneymong.moneymong.design_system.theme.Gray05 +import com.moneymong.moneymong.design_system.theme.Gray07 import com.moneymong.moneymong.design_system.theme.Gray10 +import com.moneymong.moneymong.design_system.theme.Heading1 import com.moneymong.moneymong.design_system.theme.Heading4 import com.moneymong.moneymong.design_system.theme.White import com.moneymong.moneymong.ui.noRippleClickable @@ -41,15 +65,49 @@ fun LedgerManualCategoryBottomSheet( categories: List, // TODO API response onDismissRequest: () -> Unit ) { + var sheetType by remember { mutableStateOf(LedgerManualBottomSheetType.LIST) } + MDSBottomSheet( modifier = modifier, sheetState = sheetState, onDismissRequest = onDismissRequest, ) { - LedgerManualCategoryBottomSheetContent( - categories = categories, - onDismissRequest = onDismissRequest, - ) + AnimatedContent( + targetState = sheetType, + transitionSpec = { + when (targetState) { + LedgerManualBottomSheetType.CREATE -> { + slideInHorizontally { fullWidth -> fullWidth } + .togetherWith(slideOutHorizontally { fullWidth -> fullWidth / -3 }) + } + + LedgerManualBottomSheetType.LIST -> { + slideInHorizontally { fullWidth -> -fullWidth } + .togetherWith(slideOutHorizontally { fullWidth -> fullWidth / 3 }) + } + } + } + ) { targetState -> + when (targetState) { + LedgerManualBottomSheetType.LIST -> { + LedgerManualCategoryBottomSheetContent( + categories = categories, + onDismissRequest = onDismissRequest, + onClickCreate = { sheetType = LedgerManualBottomSheetType.CREATE } + ) + } + + LedgerManualBottomSheetType.CREATE -> { + LedgerManualCategoryCreateBottomSheetContent( + textFieldValue = TextFieldValue(), + isError = false, + onValueChange = {}, + onClickRegister = {}, + onPrev = { sheetType = LedgerManualBottomSheetType.LIST } + ) + } + } + } } } @@ -59,6 +117,7 @@ fun LedgerManualCategoryBottomSheetContent( modifier: Modifier = Modifier, categories: List, onDismissRequest: () -> Unit, + onClickCreate: () -> Unit, ) { Column( modifier = modifier @@ -86,6 +145,7 @@ fun LedgerManualCategoryBottomSheetContent( text = "카테고리", ) Text( + modifier = Modifier.noRippleClickable(onClickCreate), style = Body3, color = Blue04, text = "추가", @@ -112,10 +172,111 @@ fun LedgerManualCategoryBottomSheetContent( } } +@Composable +fun LedgerManualCategoryCreateBottomSheetContent( + modifier: Modifier = Modifier, + textFieldValue: TextFieldValue, + isError: Boolean, + onValueChange: (TextFieldValue) -> Unit, + onClickRegister: () -> Unit, + onPrev: () -> Unit, +) { + val maxCount = 10 + var isFilled by remember { mutableStateOf(false) } + val focusRequester = remember { FocusRequester() } + val keyboard = LocalSoftwareKeyboardController.current + + BackHandler { + keyboard?.hide() + onPrev() + } + + LaunchedEffect(Unit) { + focusRequester.requestFocus() + } + + Column( + modifier = modifier + .fillMaxWidth() + .background(White) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .background(White) + .padding(horizontal = MMHorizontalSpacing, vertical = 20.dp), + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + modifier = Modifier + .size(24.dp) + .noRippleClickable { + keyboard?.hide() + onPrev() + }, + painter = painterResource(R.drawable.ic_chevron_left), + contentDescription = null, + tint = Gray07, + ) + Text( + text = "카테고리 생성", + style = Heading1, + color = Black, + ) + } + Spacer(modifier = Modifier.height(36.dp)) + MDSTextField( + modifier = Modifier + .fillMaxWidth() + .onFocusChanged { isFilled = !it.isFocused } + .focusRequester(focusRequester), + placeholder = "카테고리를 입력해주세요", + value = textFieldValue, + onValueChange = onValueChange, + title = "", + isFilled = isFilled, + isError = isError, + singleLine = true, + icon = MDSTextFieldIcons.Clear, + onIconClick = { onValueChange(TextFieldValue("")) }, + maxCount = maxCount + ) + Spacer(modifier = Modifier.height(20.dp)) + } + MDSButton( + modifier = Modifier.fillMaxWidth(), + text = "등록", + type = MDSButtonType.PRIMARY, + size = MDSButtonSize.LARGE, + cornerShape = 0.dp, + enabled = textFieldValue.text.isNotBlank(), + onClick = onClickRegister, + ) + } +} + @Preview(showBackground = true) @Composable fun LedgerManualCategoryBottomSheetContentPreview() { val categories = listOf("testTooLongTextOverFlow", "test") - LedgerManualCategoryBottomSheetContent(categories = categories) {} + LedgerManualCategoryBottomSheetContent( + categories = categories, + onDismissRequest = {}, + ) {} +} + +@Preview(showBackground = true) +@Composable +fun LedgerManualCategoryCreateBottomSheetContentPreview() { + LedgerManualCategoryCreateBottomSheetContent( + textFieldValue = TextFieldValue(), + isError = false, + onValueChange = {}, + onClickRegister = {} + ) {} } \ No newline at end of file From 51ca70f1a7779dac18d5a661224e2d71d0d156b7 Mon Sep 17 00:00:00 2001 From: Heon Date: Wed, 8 Oct 2025 00:51:21 +0900 Subject: [PATCH 05/14] feat: category input handling --- .../ledgermanual/LedgerManualScreen.kt | 5 ++- .../ledgermanual/LedgerManualState.kt | 8 +++++ .../ledgermanual/LedgerManualViewModel.kt | 8 +++++ .../view/LedgerManualCategoryBottomSheet.kt | 35 ++++++++++++++----- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualScreen.kt b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualScreen.kt index 6f0975c0..6e77c3a3 100644 --- a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualScreen.kt +++ b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualScreen.kt @@ -189,12 +189,15 @@ fun LedgerManualScreen( LedgerManualCategoryBottomSheet( sheetState = sheetState, categories = emptyList(), + categoryValue = state.categoryValue, + isSystemCategoryError = state.isSystemCategoryError, onDismissRequest = { scope.launch { sheetState.hide() viewModel.onDismissBottomSheet() } - } + }, + onChangeCategoryValue = viewModel::onChangeCategoryValue ) } diff --git a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualState.kt b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualState.kt index bd29af4d..1a3a2eea 100644 --- a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualState.kt +++ b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualState.kt @@ -27,6 +27,7 @@ data class LedgerManualState( val showErrorDialog: Boolean = false, val errorMessage: String = "", val showBottomSheet: Boolean = false, + val categoryValue: TextFieldValue = TextFieldValue(), ) : State { val enabled: Boolean @@ -54,4 +55,11 @@ data class LedgerManualState( val formattedTime = timeFormat.format(timeFormat.parse(paymentTimeValue.text)) return "$formattedDate $formattedTime".toZonedDateTime("yyyyMMdd HHmmss") } + + val isSystemCategoryError: Boolean + get() = categoryValue.text == SYSTEM_CATEGORY + + companion object { + private const val SYSTEM_CATEGORY = "카테고리 없음" + } } diff --git a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualViewModel.kt b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualViewModel.kt index 4f0f9b10..89579c21 100644 --- a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualViewModel.kt +++ b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualViewModel.kt @@ -181,6 +181,14 @@ class LedgerManualViewModel @Inject constructor( fun onDismissBottomSheet() = intent { reduce { state.copy(showBottomSheet = false) } } + fun onChangeCategoryValue(value: TextFieldValue) = blockingIntent { + val validate = value.text.validateValue(length = 10) + + if (validate) { + reduce { state.copy(categoryValue = value) } + } + } + private fun trimStartWithZero(value: TextFieldValue) = if (value.text.isNotEmpty() && value.text.all { it == '0' }) { value.copy(text = "0") diff --git a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt index 6f2cb117..09b9683b 100644 --- a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt +++ b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt @@ -22,6 +22,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -63,7 +64,10 @@ fun LedgerManualCategoryBottomSheet( modifier: Modifier = Modifier, sheetState: SheetState, categories: List, // TODO API response - onDismissRequest: () -> Unit + categoryValue: TextFieldValue, + isSystemCategoryError: Boolean, + onDismissRequest: () -> Unit, + onChangeCategoryValue: (TextFieldValue) -> Unit, ) { var sheetType by remember { mutableStateOf(LedgerManualBottomSheetType.LIST) } @@ -99,9 +103,10 @@ fun LedgerManualCategoryBottomSheet( LedgerManualBottomSheetType.CREATE -> { LedgerManualCategoryCreateBottomSheetContent( - textFieldValue = TextFieldValue(), - isError = false, - onValueChange = {}, + textFieldValue = categoryValue, + isSystemCategoryError = isSystemCategoryError, + categories = categories, + onValueChange = onChangeCategoryValue, onClickRegister = {}, onPrev = { sheetType = LedgerManualBottomSheetType.LIST } ) @@ -176,7 +181,8 @@ fun LedgerManualCategoryBottomSheetContent( fun LedgerManualCategoryCreateBottomSheetContent( modifier: Modifier = Modifier, textFieldValue: TextFieldValue, - isError: Boolean, + isSystemCategoryError: Boolean, + categories: List, // TODO API response onValueChange: (TextFieldValue) -> Unit, onClickRegister: () -> Unit, onPrev: () -> Unit, @@ -185,6 +191,16 @@ fun LedgerManualCategoryCreateBottomSheetContent( var isFilled by remember { mutableStateOf(false) } val focusRequester = remember { FocusRequester() } val keyboard = LocalSoftwareKeyboardController.current + val isExists = categories.contains(textFieldValue.text) + val helperText by remember(isSystemCategoryError, isExists) { + derivedStateOf { + when { + isSystemCategoryError -> "사용할 수 없는 카테고리 이름이에요" + isExists -> "이미 있는 카테고리에요" + else -> "" + } + } + } BackHandler { keyboard?.hide() @@ -239,21 +255,23 @@ fun LedgerManualCategoryCreateBottomSheetContent( onValueChange = onValueChange, title = "", isFilled = isFilled, - isError = isError, + isError = isSystemCategoryError || isExists, singleLine = true, + helperText = helperText, icon = MDSTextFieldIcons.Clear, onIconClick = { onValueChange(TextFieldValue("")) }, maxCount = maxCount ) Spacer(modifier = Modifier.height(20.dp)) } + val enabled = textFieldValue.text.isNotBlank() && (!isSystemCategoryError && !isExists) MDSButton( modifier = Modifier.fillMaxWidth(), text = "등록", type = MDSButtonType.PRIMARY, size = MDSButtonSize.LARGE, cornerShape = 0.dp, - enabled = textFieldValue.text.isNotBlank(), + enabled = enabled, onClick = onClickRegister, ) } @@ -275,7 +293,8 @@ fun LedgerManualCategoryBottomSheetContentPreview() { fun LedgerManualCategoryCreateBottomSheetContentPreview() { LedgerManualCategoryCreateBottomSheetContent( textFieldValue = TextFieldValue(), - isError = false, + isSystemCategoryError = false, + categories = emptyList(), onValueChange = {}, onClickRegister = {} ) {} From 6c26f6b0eb7067754edf6f5a4a14fba516f00f2f Mon Sep 17 00:00:00 2001 From: Heon Date: Wed, 8 Oct 2025 01:28:11 +0900 Subject: [PATCH 06/14] feat: category row list to ledger manual screen --- .../ledgermanual/LedgerManualScreen.kt | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualScreen.kt b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualScreen.kt index 6e77c3a3..2ce9e209 100644 --- a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualScreen.kt +++ b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualScreen.kt @@ -11,6 +11,8 @@ import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -57,6 +59,7 @@ import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi import com.bumptech.glide.integration.compose.GlideImage import com.moneymong.moneymong.android.util.base64ToFile import com.moneymong.moneymong.android.util.encodingBase64 +import com.moneymong.moneymong.design_system.R import com.moneymong.moneymong.ui.noRippleClickable import com.moneymong.moneymong.design_system.R.drawable import com.moneymong.moneymong.design_system.component.button.MDSButton @@ -64,6 +67,7 @@ import com.moneymong.moneymong.design_system.component.button.MDSButtonSize import com.moneymong.moneymong.design_system.component.button.MDSButtonType import com.moneymong.moneymong.design_system.component.modal.MDSModal import com.moneymong.moneymong.design_system.component.selection.MDSSelection +import com.moneymong.moneymong.design_system.component.tag.MDSOutlineTag import com.moneymong.moneymong.design_system.component.textfield.MDSTextField import com.moneymong.moneymong.design_system.component.textfield.util.MDSTextFieldIcons import com.moneymong.moneymong.design_system.component.textfield.util.withRequiredMark @@ -86,7 +90,9 @@ import kotlinx.coroutines.launch import org.orbitmvi.orbit.compose.collectAsState import org.orbitmvi.orbit.compose.collectSideEffect -@OptIn(ExperimentalGlideComposeApi::class, ExperimentalMaterial3Api::class) +@OptIn(ExperimentalGlideComposeApi::class, ExperimentalMaterial3Api::class, + ExperimentalLayoutApi::class +) @Composable fun LedgerManualScreen( modifier: Modifier = Modifier, @@ -360,7 +366,16 @@ fun LedgerManualScreen( style = Body2, color = Blue04, ) - Spacer(modifier = Modifier.height(8.dp)) + } + Spacer(modifier = Modifier.height(8.dp)) + FlowRow( + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + MDSOutlineTag( + text = "Test", // TODO + iconResource = drawable.ic_close_default, + onClick = {}, + ) } Spacer(modifier = Modifier.height(24.dp)) Text( From 48f6cad694314f925522384820126e72ecebf552 Mon Sep 17 00:00:00 2001 From: Heon Date: Tue, 21 Oct 2025 22:45:55 +0900 Subject: [PATCH 07/14] refactor: tag horizontal spacedBy padding --- .../com/moneymong/moneymong/design_system/component/tag/Tag.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/design-system/src/main/java/com/moneymong/moneymong/design_system/component/tag/Tag.kt b/core/design-system/src/main/java/com/moneymong/moneymong/design_system/component/tag/Tag.kt index dfdd20fc..64b95225 100644 --- a/core/design-system/src/main/java/com/moneymong/moneymong/design_system/component/tag/Tag.kt +++ b/core/design-system/src/main/java/com/moneymong/moneymong/design_system/component/tag/Tag.kt @@ -79,7 +79,7 @@ fun MDSOutlineTag( shape = RoundedCornerShape(size = Int.MAX_VALUE.dp) ) .padding(horizontal = 12.dp, vertical = 6.dp), - horizontalArrangement = Arrangement.spacedBy(4.dp), + horizontalArrangement = Arrangement.spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically ) { Text( From cae67bca4264ca45b6e811d621cc23d5622d71a2 Mon Sep 17 00:00:00 2001 From: Heon Date: Tue, 21 Oct 2025 22:52:24 +0900 Subject: [PATCH 08/14] refactor: category bottomsheet header text color --- .../ledgermanual/view/LedgerManualCategoryBottomSheet.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt index 09b9683b..a7edaf1f 100644 --- a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt +++ b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt @@ -52,7 +52,6 @@ import com.moneymong.moneymong.design_system.theme.Body2 import com.moneymong.moneymong.design_system.theme.Body3 import com.moneymong.moneymong.design_system.theme.Gray05 import com.moneymong.moneymong.design_system.theme.Gray07 -import com.moneymong.moneymong.design_system.theme.Gray10 import com.moneymong.moneymong.design_system.theme.Heading1 import com.moneymong.moneymong.design_system.theme.Heading4 import com.moneymong.moneymong.design_system.theme.White @@ -146,7 +145,7 @@ fun LedgerManualCategoryBottomSheetContent( ) { Text( style = Heading4, - color = Gray10, + color = Black, text = "카테고리", ) Text( From ea23ef2c59b0b9e86fdecc8ab717e4f6838184f9 Mon Sep 17 00:00:00 2001 From: Heon Date: Tue, 21 Oct 2025 23:01:05 +0900 Subject: [PATCH 09/14] refactor: bottomsheet dismiss request apply invokeOnCompletion --- .../moneymong/ledgermanual/LedgerManualScreen.kt | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualScreen.kt b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualScreen.kt index 2ce9e209..288df93f 100644 --- a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualScreen.kt +++ b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/LedgerManualScreen.kt @@ -152,14 +152,6 @@ fun LedgerManualScreen( BackHandler(onBack = { viewModel.eventEmit(LedgerManualSideEffect.LedgerManualShowPopBackStackModal) }) - LaunchedEffect(state.showBottomSheet) { - if (state.showBottomSheet) { - sheetState.show() - } else { - sheetState.hide() - } - } - if (state.showPopBackStackModal) { MDSModal( icon = drawable.ic_warning_filled, @@ -200,8 +192,7 @@ fun LedgerManualScreen( onDismissRequest = { scope.launch { sheetState.hide() - viewModel.onDismissBottomSheet() - } + }.invokeOnCompletion { viewModel.onDismissBottomSheet() } }, onChangeCategoryValue = viewModel::onChangeCategoryValue ) From 52080ac373f67dc1b99cafd96c18b2281a3e74a5 Mon Sep 17 00:00:00 2001 From: Heon Date: Tue, 21 Oct 2025 23:02:55 +0900 Subject: [PATCH 10/14] refactor: remove semicolon with bottomsheet type enum class --- .../moneymong/ledgermanual/view/LedgerManualBottomSheetType.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualBottomSheetType.kt b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualBottomSheetType.kt index 4147217b..cc25f684 100644 --- a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualBottomSheetType.kt +++ b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualBottomSheetType.kt @@ -3,5 +3,4 @@ package com.moneymong.moneymong.ledgermanual.view enum class LedgerManualBottomSheetType { LIST, CREATE, - ; -} \ No newline at end of file +} From 251d638edb4919ecb00d8fedbba0f64e7f4d6191 Mon Sep 17 00:00:00 2001 From: Heon Date: Tue, 21 Oct 2025 23:06:54 +0900 Subject: [PATCH 11/14] refactor: spacing between title and textfield in create bottomsheet --- .../ledgermanual/view/LedgerManualCategoryBottomSheet.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt index a7edaf1f..d631645e 100644 --- a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt +++ b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt @@ -243,7 +243,7 @@ fun LedgerManualCategoryCreateBottomSheetContent( color = Black, ) } - Spacer(modifier = Modifier.height(36.dp)) + Spacer(modifier = Modifier.height(16.dp)) MDSTextField( modifier = Modifier .fillMaxWidth() From 7a35918d93d84008524a5bf87bb3a839b003ea5c Mon Sep 17 00:00:00 2001 From: Heon Date: Tue, 21 Oct 2025 23:09:29 +0900 Subject: [PATCH 12/14] refactor: remove spacing below the textfield in create bottomsheet --- .../ledgermanual/view/LedgerManualCategoryBottomSheet.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt index d631645e..1003ef74 100644 --- a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt +++ b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt @@ -261,7 +261,6 @@ fun LedgerManualCategoryCreateBottomSheetContent( onIconClick = { onValueChange(TextFieldValue("")) }, maxCount = maxCount ) - Spacer(modifier = Modifier.height(20.dp)) } val enabled = textFieldValue.text.isNotBlank() && (!isSystemCategoryError && !isExists) MDSButton( From 8db0e6ac94585d5782140f96c60a0bf75d9343f7 Mon Sep 17 00:00:00 2001 From: Heon Date: Tue, 21 Oct 2025 23:12:27 +0900 Subject: [PATCH 13/14] refactor: change tag value text style from Body4 to Body3 --- .../moneymong/moneymong/design_system/component/tag/Tag.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/design-system/src/main/java/com/moneymong/moneymong/design_system/component/tag/Tag.kt b/core/design-system/src/main/java/com/moneymong/moneymong/design_system/component/tag/Tag.kt index 64b95225..935b6e12 100644 --- a/core/design-system/src/main/java/com/moneymong/moneymong/design_system/component/tag/Tag.kt +++ b/core/design-system/src/main/java/com/moneymong/moneymong/design_system/component/tag/Tag.kt @@ -19,7 +19,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.moneymong.moneymong.design_system.theme.Blue04 import com.moneymong.moneymong.design_system.theme.Body2 -import com.moneymong.moneymong.design_system.theme.Body4 +import com.moneymong.moneymong.design_system.theme.Body3 import com.moneymong.moneymong.design_system.theme.Gray03 import com.moneymong.moneymong.design_system.theme.Gray05 import com.moneymong.moneymong.design_system.theme.Gray06 @@ -85,7 +85,7 @@ fun MDSOutlineTag( Text( text = text, color = Gray06, - style = Body4, + style = Body3, ) if (iconResource != null) { Icon( From 5e12038e335813e66a9e9642c7d6089aca58c4e4 Mon Sep 17 00:00:00 2001 From: Heon Date: Tue, 21 Oct 2025 23:14:46 +0900 Subject: [PATCH 14/14] refactor: remove hide event from bottomsheet back handler lambda --- .../ledgermanual/view/LedgerManualCategoryBottomSheet.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt index 1003ef74..ffd36754 100644 --- a/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt +++ b/feature/ledgermanual/src/main/java/com/moneymong/moneymong/ledgermanual/view/LedgerManualCategoryBottomSheet.kt @@ -202,7 +202,6 @@ fun LedgerManualCategoryCreateBottomSheetContent( } BackHandler { - keyboard?.hide() onPrev() }