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 935b6e12..5929fafb 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 @@ -3,6 +3,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.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding @@ -13,16 +14,19 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color 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.R 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.Gray03 import com.moneymong.moneymong.design_system.theme.Gray05 import com.moneymong.moneymong.design_system.theme.Gray06 +import com.moneymong.moneymong.design_system.theme.Gray08 import com.moneymong.moneymong.design_system.theme.White import com.moneymong.moneymong.ui.noRippleClickable @@ -64,38 +68,56 @@ fun MDSTag( fun MDSOutlineTag( modifier: Modifier = Modifier, text: String, + selected: Boolean = false, @DrawableRes iconResource: Int? = null, - onClick: () -> Unit, + onClick: () -> Unit = {}, ) { Row( modifier = modifier .border( width = 1.4.dp, - color = Gray03, + color = if (selected) Blue04 else Gray03, shape = RoundedCornerShape(size = Int.MAX_VALUE.dp) ) + .clip(RoundedCornerShape(size = Int.MAX_VALUE.dp)) .background( color = White, shape = RoundedCornerShape(size = Int.MAX_VALUE.dp) ) + .clickable( + enabled = iconResource == null, + onClick = onClick + ) .padding(horizontal = 12.dp, vertical = 6.dp), horizontalArrangement = Arrangement.spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically ) { Text( text = text, - color = Gray06, + color = if (selected) Gray08 else Gray06, style = Body3, ) - if (iconResource != null) { - Icon( - modifier = Modifier - .size(18.dp) - .noRippleClickable(onClick), - painter = painterResource(id = iconResource), - contentDescription = "Tag icon", - tint = Gray05 - ) + when { + !selected && iconResource != null -> { + Icon( + modifier = Modifier + .size(18.dp) + .noRippleClickable(onClick), + painter = painterResource(id = iconResource), + contentDescription = "Tag icon", + tint = Gray05 + ) + } + + selected -> { + Icon( + modifier = Modifier + .size(18.dp), + painter = painterResource(id = R.drawable.ic_check), + contentDescription = "Tag icon", + tint = Blue04 + ) + } } } } @@ -116,7 +138,7 @@ fun MDSTagPreview() { text = "tag", backgroundColor = Blue04, contentColor = White, - iconResource = com.moneymong.moneymong.design_system.R.drawable.ic_pencil + iconResource = R.drawable.ic_pencil ) } } @@ -130,11 +152,13 @@ fun MDSOutlineTagPreview() { ) { MDSOutlineTag( text = "tag", + selected = false, onClick = {}, ) MDSOutlineTag( text = "tag", - iconResource = com.moneymong.moneymong.design_system.R.drawable.ic_close_default, + selected = true, + iconResource = R.drawable.ic_close_default, onClick = {}, ) } diff --git a/core/model/src/main/java/com/moneymong/moneymong/model/agency/CategoryReadResponse.kt b/core/model/src/main/java/com/moneymong/moneymong/model/agency/CategoryReadResponse.kt index a54fa491..1625d09b 100644 --- a/core/model/src/main/java/com/moneymong/moneymong/model/agency/CategoryReadResponse.kt +++ b/core/model/src/main/java/com/moneymong/moneymong/model/agency/CategoryReadResponse.kt @@ -1,6 +1,7 @@ package com.moneymong.moneymong.model.agency data class CategoryReadResponse( + val agencyId: Long, val categories: List, ) diff --git a/core/network/src/main/java/com/moneymong/moneymong/network/api/AgencyApi.kt b/core/network/src/main/java/com/moneymong/moneymong/network/api/AgencyApi.kt index 7c666c20..f46e9446 100644 --- a/core/network/src/main/java/com/moneymong/moneymong/network/api/AgencyApi.kt +++ b/core/network/src/main/java/com/moneymong/moneymong/network/api/AgencyApi.kt @@ -7,6 +7,7 @@ import com.moneymong.moneymong.model.agency.AgencyJoinResponse import com.moneymong.moneymong.model.agency.AgencyRegisterRequest import com.moneymong.moneymong.model.agency.CategoryCreateRequest import com.moneymong.moneymong.model.agency.CategoryCreateResponse +import com.moneymong.moneymong.model.agency.CategoryReadResponse import com.moneymong.moneymong.model.agency.MyAgencyResponse import com.moneymong.moneymong.model.agency.RegisterAgencyResponse import com.moneymong.moneymong.model.member.InvitationCodeResponse @@ -40,6 +41,11 @@ interface AgencyApi { @Query("keyword") name: String ): Result> + @GET("api/v1/agencies/categories") + suspend fun fetchCategories( + @Query("agencyId") agencyId: Long + ): Result + // POST @POST("/api/v2/agencies/invitation-code") suspend fun agencyCodeNumbers( diff --git a/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSource.kt b/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSource.kt index 23637a70..b437c7d6 100644 --- a/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSource.kt +++ b/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSource.kt @@ -7,6 +7,7 @@ import com.moneymong.moneymong.model.agency.AgencyJoinResponse import com.moneymong.moneymong.model.agency.AgencyRegisterRequest import com.moneymong.moneymong.model.agency.CategoryCreateRequest import com.moneymong.moneymong.model.agency.CategoryCreateResponse +import com.moneymong.moneymong.model.agency.CategoryReadResponse import com.moneymong.moneymong.model.agency.MyAgencyResponse import com.moneymong.moneymong.model.agency.RegisterAgencyResponse @@ -17,4 +18,5 @@ interface AgencyRemoteDataSource { suspend fun fetchAgencyByName(agencyName: String): Result> suspend fun agencyCodeNumbers(data: AgencyJoinRequest): Result suspend fun createCategory(request: CategoryCreateRequest): Result + suspend fun fetchCategories(agencyId: Long): Result } \ No newline at end of file diff --git a/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSourceImpl.kt b/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSourceImpl.kt index 8af2e0e7..fc2e9e8c 100644 --- a/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSourceImpl.kt +++ b/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSourceImpl.kt @@ -7,6 +7,7 @@ import com.moneymong.moneymong.model.agency.AgencyJoinResponse import com.moneymong.moneymong.model.agency.AgencyRegisterRequest import com.moneymong.moneymong.model.agency.CategoryCreateRequest import com.moneymong.moneymong.model.agency.CategoryCreateResponse +import com.moneymong.moneymong.model.agency.CategoryReadResponse import com.moneymong.moneymong.model.agency.MyAgencyResponse import com.moneymong.moneymong.model.agency.RegisterAgencyResponse import com.moneymong.moneymong.network.api.AgencyApi @@ -41,4 +42,8 @@ class AgencyRemoteDataSourceImpl @Inject constructor( override suspend fun createCategory(request: CategoryCreateRequest): Result { return agencyApi.createCategory(request = request) } + + override suspend fun fetchCategories(agencyId: Long): Result { + return agencyApi.fetchCategories(agencyId = agencyId) + } } \ No newline at end of file diff --git a/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSourceMock.kt b/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSourceMock.kt index 9ef13a1d..b1ebc95b 100644 --- a/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSourceMock.kt +++ b/data/src/main/java/com/moneymong/moneymong/data/datasource/agency/AgencyRemoteDataSourceMock.kt @@ -7,6 +7,7 @@ import com.moneymong.moneymong.model.agency.AgencyJoinResponse import com.moneymong.moneymong.model.agency.AgencyRegisterRequest import com.moneymong.moneymong.model.agency.CategoryCreateRequest import com.moneymong.moneymong.model.agency.CategoryCreateResponse +import com.moneymong.moneymong.model.agency.CategoryReadResponse import com.moneymong.moneymong.model.agency.MyAgencyResponse import com.moneymong.moneymong.model.agency.RegisterAgencyResponse import kotlinx.coroutines.delay @@ -42,6 +43,10 @@ class AgencyRemoteDataSourceMock : AgencyRemoteDataSource { return Result.success(CategoryCreateResponse(agencyId = 1L, name = "category")) } + override suspend fun fetchCategories(agencyId: Long): Result { + return Result.success(CategoryReadResponse(agencyId = agencyId, categories = emptyList())) + } + private companion object { val agenciesMockOfSuccess = listOf( Result.success( diff --git a/data/src/main/java/com/moneymong/moneymong/data/repository/agency/AgencyRepositoryImpl.kt b/data/src/main/java/com/moneymong/moneymong/data/repository/agency/AgencyRepositoryImpl.kt index 4c15c851..ffb7591d 100644 --- a/data/src/main/java/com/moneymong/moneymong/data/repository/agency/AgencyRepositoryImpl.kt +++ b/data/src/main/java/com/moneymong/moneymong/data/repository/agency/AgencyRepositoryImpl.kt @@ -13,6 +13,7 @@ import com.moneymong.moneymong.model.agency.AgencyJoinResponse import com.moneymong.moneymong.model.agency.AgencyRegisterRequest import com.moneymong.moneymong.model.agency.CategoryCreateRequest import com.moneymong.moneymong.model.agency.CategoryCreateResponse +import com.moneymong.moneymong.model.agency.CategoryReadResponse import com.moneymong.moneymong.model.agency.MyAgencyResponse import com.moneymong.moneymong.model.agency.RegisterAgencyResponse import kotlinx.coroutines.flow.Flow @@ -54,4 +55,7 @@ class AgencyRepositoryImpl @Inject constructor( override suspend fun createCategory(request: CategoryCreateRequest): Result = agencyRemoteDataSource.createCategory(request = request) + + override suspend fun fetchCategories(agencyId: Long): Result = + agencyRemoteDataSource.fetchCategories(agencyId = agencyId) } \ No newline at end of file diff --git a/domain/src/main/java/com/moneymong/moneymong/domain/repository/agency/AgencyRepository.kt b/domain/src/main/java/com/moneymong/moneymong/domain/repository/agency/AgencyRepository.kt index f7d686c5..79a75d63 100644 --- a/domain/src/main/java/com/moneymong/moneymong/domain/repository/agency/AgencyRepository.kt +++ b/domain/src/main/java/com/moneymong/moneymong/domain/repository/agency/AgencyRepository.kt @@ -7,6 +7,7 @@ import com.moneymong.moneymong.model.agency.AgencyJoinResponse import com.moneymong.moneymong.model.agency.AgencyRegisterRequest import com.moneymong.moneymong.model.agency.CategoryCreateRequest import com.moneymong.moneymong.model.agency.CategoryCreateResponse +import com.moneymong.moneymong.model.agency.CategoryReadResponse import com.moneymong.moneymong.model.agency.MyAgencyResponse import com.moneymong.moneymong.model.agency.RegisterAgencyResponse import kotlinx.coroutines.flow.Flow @@ -21,4 +22,5 @@ interface AgencyRepository { suspend fun saveAgencyId(agencyId: Int) suspend fun fetchAgencyId(): Int suspend fun createCategory(request: CategoryCreateRequest): Result + suspend fun fetchCategories(agencyId: Long): Result } \ No newline at end of file diff --git a/domain/src/main/java/com/moneymong/moneymong/domain/usecase/agency/FetchCategoriesUseCase.kt b/domain/src/main/java/com/moneymong/moneymong/domain/usecase/agency/FetchCategoriesUseCase.kt new file mode 100644 index 00000000..41501a56 --- /dev/null +++ b/domain/src/main/java/com/moneymong/moneymong/domain/usecase/agency/FetchCategoriesUseCase.kt @@ -0,0 +1,12 @@ +package com.moneymong.moneymong.domain.usecase.agency + +import com.moneymong.moneymong.domain.repository.agency.AgencyRepository +import com.moneymong.moneymong.model.agency.CategoryReadResponse +import javax.inject.Inject + +class FetchCategoriesUseCase @Inject constructor( + private val agencyRepository: AgencyRepository +) { + suspend operator fun invoke(agencyId: Long): Result = + agencyRepository.fetchCategories(agencyId = agencyId) +} \ No newline at end of file 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 7a8d7a31..f18169da 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 @@ -34,7 +34,6 @@ 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 @@ -89,7 +88,8 @@ 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 @@ -191,7 +191,10 @@ fun LedgerManualScreen( onDismissRequest = { scope.launch { sheetState.hide() - }.invokeOnCompletion { viewModel.onDismissBottomSheet() } + }.invokeOnCompletion { + viewModel.onDismissBottomSheet() + viewModel.onChangeCategoryValue(TextFieldValue()) + } }, onChangeCategoryValue = viewModel::onChangeCategoryValue, onCategoryCreate = viewModel::createCategory, @@ -360,13 +363,17 @@ fun LedgerManualScreen( } Spacer(modifier = Modifier.height(8.dp)) FlowRow( - horizontalArrangement = Arrangement.spacedBy(10.dp) + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), ) { - MDSOutlineTag( - text = "Test", // TODO - iconResource = drawable.ic_close_default, - onClick = {}, - ) + state.categories.forEach { category -> + val isSelected = category == state.selectedCategory + MDSOutlineTag( + text = category.name, + selected = isSelected, + onClick = { viewModel.onClickCategory(category) }, + ) + } } Spacer(modifier = Modifier.height(24.dp)) Text( 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 24617e6f..c8001f71 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 @@ -29,7 +29,8 @@ data class LedgerManualState( val errorMessage: String = "", val showBottomSheet: Boolean = false, val categoryValue: TextFieldValue = TextFieldValue(), - val categories: List? = null, + val categories: List = emptyList(), + val selectedCategory: CategoryResponse? = null ) : 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 d028d303..a8b7ce5e 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 @@ -9,10 +9,12 @@ import com.moneymong.moneymong.ui.isValidPaymentDate import com.moneymong.moneymong.ui.isValidPaymentTime import com.moneymong.moneymong.ui.validateValue import com.moneymong.moneymong.domain.usecase.agency.FetchAgencyIdUseCase +import com.moneymong.moneymong.domain.usecase.agency.FetchCategoriesUseCase import com.moneymong.moneymong.domain.usecase.ledger.PostLedgerTransactionUseCase import com.moneymong.moneymong.domain.usecase.ocr.PostFileUploadUseCase import com.moneymong.moneymong.domain.usecase.user.FetchUserNicknameUseCase import com.moneymong.moneymong.model.agency.CategoryCreateRequest +import com.moneymong.moneymong.model.agency.CategoryResponse import com.moneymong.moneymong.model.ledger.FundType import com.moneymong.moneymong.model.ledger.LedgerTransactionRequest import com.moneymong.moneymong.model.ocr.FileUploadRequest @@ -32,10 +34,12 @@ class LedgerManualViewModel @Inject constructor( private val fetchAgencyIdUseCase: FetchAgencyIdUseCase, private val fetchUserNicknameUseCase: FetchUserNicknameUseCase, private val createCategoryUseCase: CreateCategoryUseCase, + private val fetchCategoriesUseCase: FetchCategoriesUseCase, ) : BaseViewModel(LedgerManualState()) { init { fetchUserInfo() + fetchCategories() } @OptIn(OrbitExperimental::class) @@ -98,16 +102,15 @@ class LedgerManualViewModel @Inject constructor( fun createCategory() = intent { val request = - CategoryCreateRequest(agencyId = state.agencyId.toLong(), name = state.categoryValue.text) + CategoryCreateRequest( + agencyId = state.agencyId.toLong(), + name = state.categoryValue.text + ) createCategoryUseCase(request) .onSuccess { - reduce { - state.copy( - showBottomSheet = false, - categoryValue = TextFieldValue(), - ) - } + fetchCategories() + reduce { state.copy(showBottomSheet = false) } }.onFailure { reduce { state.copy( @@ -115,6 +118,15 @@ class LedgerManualViewModel @Inject constructor( errorMessage = it.message ?: MoneyMongError.UnExpectedError.message ) } + }.also { onChangeCategoryValue(TextFieldValue()) } + } + + fun fetchCategories() = intent { + fetchCategoriesUseCase(agencyId = state.agencyId.toLong()) + .onSuccess { + reduce { + state.copy(categories = it.categories) + } } } @@ -215,6 +227,10 @@ class LedgerManualViewModel @Inject constructor( } } + fun onClickCategory(category: CategoryResponse) = intent { + reduce { state.copy(selectedCategory = category) } + } + 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 d473d583..9364348c 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 @@ -16,6 +16,8 @@ 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.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.Icon import androidx.compose.material.Text import androidx.compose.material3.ExperimentalMaterial3Api @@ -109,7 +111,10 @@ fun LedgerManualCategoryBottomSheet( categories = categories, onValueChange = onChangeCategoryValue, onClickRegister = onCategoryCreate, - onPrev = { sheetType = LedgerManualBottomSheetType.LIST } + onPrev = { + sheetType = LedgerManualBottomSheetType.LIST + onChangeCategoryValue(TextFieldValue()) + } ) } } @@ -125,6 +130,7 @@ fun LedgerManualCategoryBottomSheetContent( onDismissRequest: () -> Unit, onClickCreate: () -> Unit, ) { + val scrollState = rememberScrollState() Column( modifier = modifier .fillMaxWidth() @@ -165,13 +171,14 @@ fun LedgerManualCategoryBottomSheetContent( ) Spacer(modifier = Modifier.height(16.dp)) FlowRow( + modifier = Modifier.verticalScroll(scrollState), horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), ) { categories?.forEach { MDSOutlineTag( text = it.name, iconResource = R.drawable.ic_close_default, - onClick = {}, ) } }