From 72438fe024b11974dc6343cc110771daee9c1681 Mon Sep 17 00:00:00 2001 From: jinukeu Date: Sun, 14 Jan 2024 16:19:43 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20GetLedgerListUseCase=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/susu/core/model/Category.kt | 8 +++ .../main/java/com/susu/core/model/Ledger.kt | 13 ++++ .../com/susu/data/data/di/RepositoryModule.kt | 7 ++ .../data/repository/LedgerRepositoryImpl.kt | 31 ++++++++ .../com/susu/data/remote/api/LedgerService.kt | 19 +++++ .../susu/data/remote/di/ApiServiceModule.kt | 7 ++ .../model/response/LedgerListResponse.kt | 72 +++++++++++++++++++ .../domain/repository/LedgerRepository.kt | 15 ++++ .../usecase/ledger/GetLedgerListUseCase.kt | 32 +++++++++ .../received/search/LedgerSearchViewModel.kt | 13 ++++ 10 files changed, 217 insertions(+) create mode 100644 core/model/src/main/java/com/susu/core/model/Category.kt create mode 100644 core/model/src/main/java/com/susu/core/model/Ledger.kt create mode 100644 data/src/main/java/com/susu/data/data/repository/LedgerRepositoryImpl.kt create mode 100644 data/src/main/java/com/susu/data/remote/api/LedgerService.kt create mode 100644 data/src/main/java/com/susu/data/remote/model/response/LedgerListResponse.kt create mode 100644 domain/src/main/java/com/susu/domain/repository/LedgerRepository.kt create mode 100644 domain/src/main/java/com/susu/domain/usecase/ledger/GetLedgerListUseCase.kt diff --git a/core/model/src/main/java/com/susu/core/model/Category.kt b/core/model/src/main/java/com/susu/core/model/Category.kt new file mode 100644 index 00000000..e35d01ed --- /dev/null +++ b/core/model/src/main/java/com/susu/core/model/Category.kt @@ -0,0 +1,8 @@ +package com.susu.core.model + +data class Category( + val id: Int, + val seq: Int, + val category: String, + val customCategory: String? = null, +) diff --git a/core/model/src/main/java/com/susu/core/model/Ledger.kt b/core/model/src/main/java/com/susu/core/model/Ledger.kt new file mode 100644 index 00000000..9edc8208 --- /dev/null +++ b/core/model/src/main/java/com/susu/core/model/Ledger.kt @@ -0,0 +1,13 @@ +package com.susu.core.model + +import java.time.LocalDateTime + +data class Ledger( + val id: Int, + val title: String, + val description: String, + val startAt: LocalDateTime, + val endAt: LocalDateTime, + val category: Category, + val totalAmounts: Int, +) diff --git a/data/src/main/java/com/susu/data/data/di/RepositoryModule.kt b/data/src/main/java/com/susu/data/data/di/RepositoryModule.kt index a9b67d77..34902eb2 100644 --- a/data/src/main/java/com/susu/data/data/di/RepositoryModule.kt +++ b/data/src/main/java/com/susu/data/data/di/RepositoryModule.kt @@ -1,11 +1,13 @@ package com.susu.data.data.di import com.susu.data.data.repository.LedgerRecentSearchRepositoryImpl +import com.susu.data.data.repository.LedgerRepositoryImpl import com.susu.data.data.repository.LoginRepositoryImpl import com.susu.data.data.repository.SignUpRepositoryImpl import com.susu.data.data.repository.TermRepositoryImpl import com.susu.data.data.repository.TokenRepositoryImpl import com.susu.domain.repository.LedgerRecentSearchRepository +import com.susu.domain.repository.LedgerRepository import com.susu.domain.repository.LoginRepository import com.susu.domain.repository.SignUpRepository import com.susu.domain.repository.TermRepository @@ -43,4 +45,9 @@ abstract class RepositoryModule { abstract fun bindTermRepository( termRepositoryImpl: TermRepositoryImpl, ): TermRepository + + @Binds + abstract fun bindLedgerRepository( + ledgerRepositoryImpl: LedgerRepositoryImpl, + ): LedgerRepository } diff --git a/data/src/main/java/com/susu/data/data/repository/LedgerRepositoryImpl.kt b/data/src/main/java/com/susu/data/data/repository/LedgerRepositoryImpl.kt new file mode 100644 index 00000000..4f0e75df --- /dev/null +++ b/data/src/main/java/com/susu/data/data/repository/LedgerRepositoryImpl.kt @@ -0,0 +1,31 @@ +package com.susu.data.data.repository + +import com.susu.core.model.Ledger +import com.susu.data.remote.api.LedgerService +import com.susu.data.remote.model.response.toModel +import com.susu.domain.repository.LedgerRepository +import kotlinx.datetime.toKotlinLocalDateTime +import java.time.LocalDateTime +import javax.inject.Inject + +class LedgerRepositoryImpl @Inject constructor( + private val ledgerService: LedgerService, +) : LedgerRepository { + override suspend fun getLedgerList( + title: String?, + categoryId: Long?, + fromStartAt: LocalDateTime, + toEndAt: LocalDateTime, + page: Int?, + sort: String?, + ): List { + return ledgerService.getLedgerList( + title = title, + categoryId = categoryId, + fromStartAt = fromStartAt.toKotlinLocalDateTime(), + toEndAt = toEndAt.toKotlinLocalDateTime(), + page = page, + sort = sort, + ).getOrThrow().toModel() + } +} diff --git a/data/src/main/java/com/susu/data/remote/api/LedgerService.kt b/data/src/main/java/com/susu/data/remote/api/LedgerService.kt new file mode 100644 index 00000000..e24872b9 --- /dev/null +++ b/data/src/main/java/com/susu/data/remote/api/LedgerService.kt @@ -0,0 +1,19 @@ +package com.susu.data.remote.api + +import com.susu.data.remote.model.response.LedgerListResponse +import com.susu.data.remote.retrofit.ApiResult +import kotlinx.datetime.LocalDateTime +import retrofit2.http.GET +import retrofit2.http.Query + +interface LedgerService { + @GET("ledgers") + suspend fun getLedgerList( + @Query("title") title: String?, + @Query("categoryId") categoryId: Long?, + @Query("fromStartAt") fromStartAt: LocalDateTime, + @Query("toEndAt") toEndAt: LocalDateTime, + @Query("page") page: Int?, + @Query("sort") sort: String?, + ): ApiResult +} diff --git a/data/src/main/java/com/susu/data/remote/di/ApiServiceModule.kt b/data/src/main/java/com/susu/data/remote/di/ApiServiceModule.kt index 7ddec6cc..aa41241f 100644 --- a/data/src/main/java/com/susu/data/remote/di/ApiServiceModule.kt +++ b/data/src/main/java/com/susu/data/remote/di/ApiServiceModule.kt @@ -1,5 +1,6 @@ package com.susu.data.remote.di +import com.susu.data.remote.api.LedgerService import com.susu.data.remote.api.SignUpService import com.susu.data.remote.api.TermService import com.susu.data.remote.api.TokenService @@ -39,4 +40,10 @@ object ApiServiceModule { fun providesTermService(retrofit: Retrofit): TermService { return retrofit.create(TermService::class.java) } + + @Singleton + @Provides + fun providesLedgerService(retrofit: Retrofit): LedgerService { + return retrofit.create(LedgerService::class.java) + } } diff --git a/data/src/main/java/com/susu/data/remote/model/response/LedgerListResponse.kt b/data/src/main/java/com/susu/data/remote/model/response/LedgerListResponse.kt new file mode 100644 index 00000000..4465ab6b --- /dev/null +++ b/data/src/main/java/com/susu/data/remote/model/response/LedgerListResponse.kt @@ -0,0 +1,72 @@ +package com.susu.data.remote.model.response + +import com.susu.core.model.Category +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.toJavaLocalDateTime +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LedgerListResponse( + @SerialName("data") + val ledgerList: List, + val page: Int, + val size: Int, + val totalPage: Int, + val totalCount: Int, + val sort: SortInfo +) + +@Serializable +data class Ledger( + @SerialName("ledger") + val info: LedgerInfo, + val category: CategoryInfo, + val totalAmounts: Int, + val totalCounts: Int +) + +@Serializable +data class LedgerInfo( + val id: Int, + val title: String, + val description: String, + val startAt: LocalDateTime, + val endAt: LocalDateTime +) + +@Serializable +data class CategoryInfo( + val id: Int, + val seq: Int, + val category: String, + val customCategory: String? = null, +) + +@Serializable +data class SortInfo( + val empty: Boolean, + val unsorted: Boolean, + val sorted: Boolean +) + +internal fun LedgerListResponse.toModel() = this.ledgerList.map { ledger -> + with(ledger) { + com.susu.core.model.Ledger( + id = info.id, + title = info.title, + description = info.description, + startAt = info.startAt.toJavaLocalDateTime(), + endAt = info.endAt.toJavaLocalDateTime(), + category = category.toModel(), + totalAmounts = totalAmounts, + ) + } +} + +internal fun CategoryInfo.toModel() = Category( + id = id, + seq = seq, + category = category, + customCategory = customCategory, +) diff --git a/domain/src/main/java/com/susu/domain/repository/LedgerRepository.kt b/domain/src/main/java/com/susu/domain/repository/LedgerRepository.kt new file mode 100644 index 00000000..97602be5 --- /dev/null +++ b/domain/src/main/java/com/susu/domain/repository/LedgerRepository.kt @@ -0,0 +1,15 @@ +package com.susu.domain.repository + +import com.susu.core.model.Ledger +import java.time.LocalDateTime + +interface LedgerRepository { + suspend fun getLedgerList( + title: String?, + categoryId: Long?, + fromStartAt: LocalDateTime, + toEndAt: LocalDateTime, + page: Int?, + sort: String?, + ): List +} diff --git a/domain/src/main/java/com/susu/domain/usecase/ledger/GetLedgerListUseCase.kt b/domain/src/main/java/com/susu/domain/usecase/ledger/GetLedgerListUseCase.kt new file mode 100644 index 00000000..edb20111 --- /dev/null +++ b/domain/src/main/java/com/susu/domain/usecase/ledger/GetLedgerListUseCase.kt @@ -0,0 +1,32 @@ +package com.susu.domain.usecase.ledger + +import com.susu.core.common.runCatchingIgnoreCancelled +import com.susu.domain.repository.LedgerRepository +import java.time.LocalDateTime +import javax.inject.Inject + +class GetLedgerListUseCase @Inject constructor( + private val ledgerRepository: LedgerRepository, +) { + suspend operator fun invoke(param: Param) = runCatchingIgnoreCancelled { + with(param) { + ledgerRepository.getLedgerList( + title = title, + categoryId = categoryId, + fromStartAt = fromStartAt, + toEndAt = toEndAt, + page = page, + sort = sort, + ) + } + } + + data class Param( + val title: String? = null, + val categoryId: Long? = null, + val fromStartAt: LocalDateTime = LocalDateTime.now().minusYears(10), + val toEndAt: LocalDateTime = LocalDateTime.now(), + val page: Int? = null, + val sort: String? = null, + ) +} diff --git a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt index 59ad5003..6e0402e2 100644 --- a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt +++ b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt @@ -2,12 +2,14 @@ package com.susu.feature.received.search import androidx.lifecycle.viewModelScope import com.susu.core.ui.base.BaseViewModel +import com.susu.domain.usecase.ledger.GetLedgerListUseCase import com.susu.domain.usecase.ledgerrecentsearch.DeleteLedgerRecentSearchUseCase import com.susu.domain.usecase.ledgerrecentsearch.GetLedgerRecentSearchListUseCase import com.susu.domain.usecase.ledgerrecentsearch.UpsertLedgerRecentSearchUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @HiltViewModel @@ -15,9 +17,20 @@ class LedgerSearchViewModel @Inject constructor( private val upsertLedgerRecentSearchUseCase: UpsertLedgerRecentSearchUseCase, private val getLedgerRecentSearchListUseCase: GetLedgerRecentSearchListUseCase, private val deleteLedgerRecentSearchUseCase: DeleteLedgerRecentSearchUseCase, + private val getLedgerListUseCase: GetLedgerListUseCase, ) : BaseViewModel( LedgerSearchState(), ) { + init { + viewModelScope.launch { + getLedgerListUseCase( + GetLedgerListUseCase.Param(), + ).onSuccess { + Timber.d("$it") + } + } + } + fun getLedgerRecentSearchList() = viewModelScope.launch { getLedgerRecentSearchListUseCase() .onSuccess(::updateRecentSearchList) From 6d4d13e478a9f9d3a0b9e3bcf0c5e64c1a771b5c Mon Sep 17 00:00:00 2001 From: jinukeu Date: Sun, 14 Jan 2024 22:25:42 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20GetLedgerListUseCase=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/searchbar/SusuSearchBar.kt | 11 +- .../main/java/com/susu/core/model/Ledger.kt | 2 + core/ui/src/main/res/values/strings.xml | 1 + .../received/search/LedgerSearchContract.kt | 4 +- .../received/search/LedgerSearchScreen.kt | 120 ++++++++++++------ .../received/search/LedgerSearchViewModel.kt | 17 +-- .../src/main/res/drawable/ic_ledger.xml | 9 ++ .../received/src/main/res/values/strings.xml | 3 +- 8 files changed, 116 insertions(+), 51 deletions(-) create mode 100644 feature/received/src/main/res/drawable/ic_ledger.xml diff --git a/core/designsystem/src/main/java/com/susu/core/designsystem/component/searchbar/SusuSearchBar.kt b/core/designsystem/src/main/java/com/susu/core/designsystem/component/searchbar/SusuSearchBar.kt index 9a74dd2e..15d2bad1 100644 --- a/core/designsystem/src/main/java/com/susu/core/designsystem/component/searchbar/SusuSearchBar.kt +++ b/core/designsystem/src/main/java/com/susu/core/designsystem/component/searchbar/SusuSearchBar.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.susu.core.designsystem.R +import com.susu.core.designsystem.component.button.ClearIconButton import com.susu.core.designsystem.theme.Gray100 import com.susu.core.designsystem.theme.Gray20 import com.susu.core.designsystem.theme.Gray60 @@ -40,6 +41,7 @@ fun SusuSearchBar( modifier: Modifier = Modifier, value: String, onValueChange: (String) -> Unit = {}, + onClickClearIcon: () -> Unit = {}, maxLines: Int = 1, minLines: Int = 1, textColor: Color = Gray100, @@ -94,6 +96,13 @@ fun SusuSearchBar( ) } } + + if (value.isNotEmpty()) { + ClearIconButton( + iconSize = 24.dp, + onClick = onClickClearIcon, + ) + } } }, ) @@ -104,7 +113,7 @@ fun SusuSearchBar( fun SusuSearchBarPreview() { SusuTheme { var text by remember { - mutableStateOf("") + mutableStateOf("zzzz") } SusuSearchBar( diff --git a/core/model/src/main/java/com/susu/core/model/Ledger.kt b/core/model/src/main/java/com/susu/core/model/Ledger.kt index 9edc8208..fced05c1 100644 --- a/core/model/src/main/java/com/susu/core/model/Ledger.kt +++ b/core/model/src/main/java/com/susu/core/model/Ledger.kt @@ -1,7 +1,9 @@ package com.susu.core.model +import androidx.compose.runtime.Stable import java.time.LocalDateTime +@Stable data class Ledger( val id: Int, val title: String, diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml index 19e91fa6..2d9826d0 100644 --- a/core/ui/src/main/res/values/strings.xml +++ b/core/ui/src/main/res/values/strings.xml @@ -5,6 +5,7 @@ 금액 높은 순 금액 낮은 순 최근 검색 + 검색 결과 편집 저장 삭제 diff --git a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchContract.kt b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchContract.kt index 8033b589..091628e7 100644 --- a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchContract.kt +++ b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchContract.kt @@ -1,5 +1,6 @@ package com.susu.feature.received.search +import com.susu.core.model.Ledger import com.susu.core.ui.base.SideEffect import com.susu.core.ui.base.UiState import kotlinx.collections.immutable.PersistentList @@ -7,7 +8,8 @@ import kotlinx.collections.immutable.persistentListOf data class LedgerSearchState( val searchKeyword: String = "", - val searchKeywordList: PersistentList = persistentListOf(), + val recentSearchKeywordList: PersistentList = persistentListOf(), + val ledgerList: PersistentList = persistentListOf(), val isLoading: Boolean = false, ) : UiState diff --git a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchScreen.kt b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchScreen.kt index c1a4e465..f425f1cc 100644 --- a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchScreen.kt +++ b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchScreen.kt @@ -1,5 +1,6 @@ package com.susu.feature.received.search +import androidx.compose.animation.Crossfade import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -7,10 +8,10 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.KeyboardActions import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -26,10 +27,14 @@ import com.susu.core.designsystem.component.searchbar.SusuSearchBar import com.susu.core.designsystem.theme.Gray60 import com.susu.core.designsystem.theme.Gray80 import com.susu.core.designsystem.theme.SusuTheme +import com.susu.core.model.Ledger import com.susu.core.ui.extension.collectWithLifecycle import com.susu.feature.received.R import kotlinx.collections.immutable.PersistentList +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.debounce +@OptIn(FlowPreview::class) @Composable fun LedgerSearchRoute( viewModel: LedgerSearchViewModel = hiltViewModel(), @@ -46,16 +51,19 @@ fun LedgerSearchRoute( viewModel.getLedgerRecentSearchList() } + LaunchedEffect(key1 = uiState.searchKeyword) { + snapshotFlow { uiState.searchKeyword } + .debounce(100L) + .collect(viewModel::getLedgerList) + } + LedgerSearchScreen( uiState = uiState, onClickBackIcon = viewModel::popBackStack, onValueChangeSearchBar = viewModel::updateSearch, + onClickSearchClearIcon = { viewModel.updateSearch("") }, onClickRecentSearchContainer = viewModel::upsertLedgerRecentSearch, onClickRecentSearchContainerCloseIcon = viewModel::deleteLedgerRecentSearch, - onSearchDone = { - viewModel.upsertLedgerRecentSearch(it) - viewModel.updateSearch("") - }, ) } @@ -63,10 +71,10 @@ fun LedgerSearchRoute( fun LedgerSearchScreen( uiState: LedgerSearchState = LedgerSearchState(), onClickBackIcon: () -> Unit = {}, + onClickSearchClearIcon: () -> Unit = {}, onValueChangeSearchBar: (String) -> Unit = {}, onClickRecentSearchContainer: (String) -> Unit = {}, onClickRecentSearchContainerCloseIcon: (String) -> Unit = {}, - onSearchDone: (String) -> Unit = {}, // TODO REMOVE -> 테스트 용 ) { Box( modifier = Modifier @@ -90,23 +98,23 @@ fun LedgerSearchScreen( SusuSearchBar( value = uiState.searchKeyword, onValueChange = onValueChangeSearchBar, + onClickClearIcon = onClickSearchClearIcon, placeholder = stringResource(R.string.ledger_search_screen_search_placeholder), - keyboardActions = KeyboardActions( - onDone = { - // TODO REMOVE -> 테스트 용 - onSearchDone(uiState.searchKeyword) - }, - ), ) - if (uiState.searchKeywordList.isEmpty()) { - RecentSearchEmptyScreen() - } else { - RecentSearchScreen( - recentSearchList = uiState.searchKeywordList, - onClickItem = onClickRecentSearchContainer, - onClickCloseIcon = onClickRecentSearchContainerCloseIcon, - ) + Crossfade(targetState = uiState.searchKeyword.isEmpty(), label = "SearchColumn") { showRecentSearch -> + if (showRecentSearch) { + RecentSearchColumn( + recentSearchList = uiState.recentSearchKeywordList, + onClickItem = onClickRecentSearchContainer, + onClickCloseIcon = onClickRecentSearchContainerCloseIcon, + ) + } else { + SearchResultColumn( + ledgerList = uiState.ledgerList, + onClickItem = {}, + ) + } } } } @@ -114,19 +122,21 @@ fun LedgerSearchScreen( } @Composable -private fun RecentSearchEmptyScreen() { +private fun ResultEmptyColumn( + title: String, +) { Column( modifier = Modifier.fillMaxWidth().padding(top = 136.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(SusuTheme.spacing.spacing_xxxxs), ) { Text( - text = stringResource(R.string.ledger_search_screen_empty_recent_search_title), + text = title, style = SusuTheme.typography.title_xs, color = Gray80, ) Text( - text = stringResource(R.string.ledger_search_screen_empty_recent_search_description), + text = stringResource(R.string.ledger_search_screen_empty_search_description), style = SusuTheme.typography.text_xxs, textAlign = TextAlign.Center, color = Gray80, @@ -135,26 +145,62 @@ private fun RecentSearchEmptyScreen() { } @Composable -private fun RecentSearchScreen( +private fun RecentSearchColumn( recentSearchList: PersistentList, onClickItem: (String) -> Unit, onClickCloseIcon: (String) -> Unit, ) { - Column( - modifier = Modifier.padding(top = SusuTheme.spacing.spacing_xxl), - verticalArrangement = Arrangement.spacedBy(SusuTheme.spacing.spacing_m), - ) { - Text( - text = stringResource(com.susu.core.ui.R.string.word_recent_search), - style = SusuTheme.typography.title_xxs, - color = Gray60, + if (recentSearchList.isEmpty()) { + ResultEmptyColumn( + title = stringResource(R.string.ledger_search_screen_empty_recent_search_title), + ) + } else { + Column( + modifier = Modifier.padding(top = SusuTheme.spacing.spacing_xxl), + verticalArrangement = Arrangement.spacedBy(SusuTheme.spacing.spacing_m), + ) { + Text( + text = stringResource(com.susu.core.ui.R.string.word_recent_search), + style = SusuTheme.typography.title_xxs, + color = Gray60, + ) + recentSearchList.forEach { name -> + SusuRecentSearchContainer( + text = name, + onClick = { onClickItem(name) }, + onClickCloseIcon = { onClickCloseIcon(name) }, + ) + } + } + } +} + +@Composable +private fun SearchResultColumn( + ledgerList: PersistentList, + onClickItem: (Int) -> Unit, +) { + if (ledgerList.isEmpty()) { + ResultEmptyColumn( + title = stringResource(R.string.ledger_search_screen_empty_search_result_title), ) - recentSearchList.forEach { name -> - SusuRecentSearchContainer( - text = name, - onClick = { onClickItem(name) }, - onClickCloseIcon = { onClickCloseIcon(name) }, + } else { + Column( + modifier = Modifier.padding(top = SusuTheme.spacing.spacing_xxl), + verticalArrangement = Arrangement.spacedBy(SusuTheme.spacing.spacing_m), + ) { + Text( + text = stringResource(com.susu.core.ui.R.string.word_search_result), + style = SusuTheme.typography.title_xxs, + color = Gray60, ) + ledgerList.forEach { ledger -> + SusuRecentSearchContainer( + typeIconId = R.drawable.ic_ledger, + text = ledger.title, + onClick = { onClickItem(ledger.id) }, + ) + } } } } diff --git a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt index 6e0402e2..3e6d492c 100644 --- a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt +++ b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt @@ -21,16 +21,6 @@ class LedgerSearchViewModel @Inject constructor( ) : BaseViewModel( LedgerSearchState(), ) { - init { - viewModelScope.launch { - getLedgerListUseCase( - GetLedgerListUseCase.Param(), - ).onSuccess { - Timber.d("$it") - } - } - } - fun getLedgerRecentSearchList() = viewModelScope.launch { getLedgerRecentSearchListUseCase() .onSuccess(::updateRecentSearchList) @@ -51,7 +41,12 @@ class LedgerSearchViewModel @Inject constructor( fun updateSearch(search: String) = intent { copy(searchKeyword = search) } + fun getLedgerList(search: String) = viewModelScope.launch { + getLedgerListUseCase(GetLedgerListUseCase.Param(title = search)) + .onSuccess { intent { copy(ledgerList = it.toPersistentList()) } } + } + fun popBackStack() = postSideEffect(LedgerSearchSideEffect.PopBackStack) - private fun updateRecentSearchList(searchList: List) = intent { copy(searchKeywordList = searchList.toPersistentList()) } + private fun updateRecentSearchList(searchList: List) = intent { copy(recentSearchKeywordList = searchList.toPersistentList()) } } diff --git a/feature/received/src/main/res/drawable/ic_ledger.xml b/feature/received/src/main/res/drawable/ic_ledger.xml new file mode 100644 index 00000000..c58956bd --- /dev/null +++ b/feature/received/src/main/res/drawable/ic_ledger.xml @@ -0,0 +1,9 @@ + + + diff --git a/feature/received/src/main/res/values/strings.xml b/feature/received/src/main/res/values/strings.xml index 953a889e..cfdcf105 100644 --- a/feature/received/src/main/res/values/strings.xml +++ b/feature/received/src/main/res/values/strings.xml @@ -4,8 +4,9 @@ 필터 아이콘 아직 받은 장부가 없어요 받아요 - 장부 이름, 경조사 카테고리 등을\n검색해볼 수 있어요 + 장부 이름, 경조사 카테고리 등을\n검색해볼 수 있어요 어떤 장부를 찾아드릴까요? + 원하는 검색 결과가 없나요? 찾고 싶은 장부를 검색해보세요 아직 보낸 봉투가 없어요 받은 봉투 추가하기 From a8becac17ffc9d907f4806297018c462c2f3f1c8 Mon Sep 17 00:00:00 2001 From: jinukeu Date: Mon, 15 Jan 2024 10:27:03 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=EC=9E=A5=EB=B6=80=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../received/navigation/ReceivedNavigation.kt | 1 + .../received/search/LedgerSearchContract.kt | 1 + .../received/search/LedgerSearchScreen.kt | 18 ++++++++++++++---- .../received/search/LedgerSearchViewModel.kt | 2 ++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/feature/received/src/main/java/com/susu/feature/received/navigation/ReceivedNavigation.kt b/feature/received/src/main/java/com/susu/feature/received/navigation/ReceivedNavigation.kt index b83df67b..867246b8 100644 --- a/feature/received/src/main/java/com/susu/feature/received/navigation/ReceivedNavigation.kt +++ b/feature/received/src/main/java/com/susu/feature/received/navigation/ReceivedNavigation.kt @@ -74,6 +74,7 @@ fun NavGraphBuilder.receivedNavGraph( composable(route = ReceivedRoute.ledgerSearchRoute) { LedgerSearchRoute( popBackStack = popBackStack, + navigateLedgerDetail = navigateLedgerDetail, ) } composable( diff --git a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchContract.kt b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchContract.kt index 091628e7..28b5b0ae 100644 --- a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchContract.kt +++ b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchContract.kt @@ -15,4 +15,5 @@ data class LedgerSearchState( sealed interface LedgerSearchSideEffect : SideEffect { data object PopBackStack : LedgerSearchSideEffect + data class NavigateLedgerDetail(val id: Int) : LedgerSearchSideEffect } diff --git a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchScreen.kt b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchScreen.kt index f425f1cc..9c7c8ee0 100644 --- a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchScreen.kt +++ b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchScreen.kt @@ -39,11 +39,13 @@ import kotlinx.coroutines.flow.debounce fun LedgerSearchRoute( viewModel: LedgerSearchViewModel = hiltViewModel(), popBackStack: () -> Unit, + navigateLedgerDetail: (Int) -> Unit, ) { val uiState = viewModel.uiState.collectAsStateWithLifecycle().value viewModel.sideEffect.collectWithLifecycle { sideEffect -> when (sideEffect) { LedgerSearchSideEffect.PopBackStack -> popBackStack() + is LedgerSearchSideEffect.NavigateLedgerDetail -> navigateLedgerDetail(sideEffect.id) } } @@ -62,8 +64,15 @@ fun LedgerSearchRoute( onClickBackIcon = viewModel::popBackStack, onValueChangeSearchBar = viewModel::updateSearch, onClickSearchClearIcon = { viewModel.updateSearch("") }, - onClickRecentSearchContainer = viewModel::upsertLedgerRecentSearch, + onClickRecentSearchContainer = { search -> + viewModel.updateSearch(search) + viewModel.upsertLedgerRecentSearch(search) + }, onClickRecentSearchContainerCloseIcon = viewModel::deleteLedgerRecentSearch, + onClickSearchResultContainer = { + viewModel.upsertLedgerRecentSearch(it.title) + viewModel.navigateLedgerDetail(it.id) + } ) } @@ -75,6 +84,7 @@ fun LedgerSearchScreen( onValueChangeSearchBar: (String) -> Unit = {}, onClickRecentSearchContainer: (String) -> Unit = {}, onClickRecentSearchContainerCloseIcon: (String) -> Unit = {}, + onClickSearchResultContainer: (Ledger) -> Unit = {}, ) { Box( modifier = Modifier @@ -112,7 +122,7 @@ fun LedgerSearchScreen( } else { SearchResultColumn( ledgerList = uiState.ledgerList, - onClickItem = {}, + onClickItem = onClickSearchResultContainer, ) } } @@ -178,7 +188,7 @@ private fun RecentSearchColumn( @Composable private fun SearchResultColumn( ledgerList: PersistentList, - onClickItem: (Int) -> Unit, + onClickItem: (Ledger) -> Unit, ) { if (ledgerList.isEmpty()) { ResultEmptyColumn( @@ -198,7 +208,7 @@ private fun SearchResultColumn( SusuRecentSearchContainer( typeIconId = R.drawable.ic_ledger, text = ledger.title, - onClick = { onClickItem(ledger.id) }, + onClick = { onClickItem(ledger) }, ) } } diff --git a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt index 3e6d492c..92c95db6 100644 --- a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt +++ b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt @@ -21,6 +21,8 @@ class LedgerSearchViewModel @Inject constructor( ) : BaseViewModel( LedgerSearchState(), ) { + fun navigateLedgerDetail(id: Int) = postSideEffect(LedgerSearchSideEffect.NavigateLedgerDetail(id)) + fun getLedgerRecentSearchList() = viewModelScope.launch { getLedgerRecentSearchListUseCase() .onSuccess(::updateRecentSearchList) From 7ea50e3ad7f028e501d4c05e1d4c97fa480c3f77 Mon Sep 17 00:00:00 2001 From: jinukeu Date: Mon, 15 Jan 2024 10:27:39 +0900 Subject: [PATCH 4/5] feat: ktlint format --- .../susu/data/remote/model/response/LedgerListResponse.kt | 8 ++++---- .../susu/feature/received/search/LedgerSearchScreen.kt | 2 +- .../susu/feature/received/search/LedgerSearchViewModel.kt | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/data/src/main/java/com/susu/data/remote/model/response/LedgerListResponse.kt b/data/src/main/java/com/susu/data/remote/model/response/LedgerListResponse.kt index 4465ab6b..71eeb782 100644 --- a/data/src/main/java/com/susu/data/remote/model/response/LedgerListResponse.kt +++ b/data/src/main/java/com/susu/data/remote/model/response/LedgerListResponse.kt @@ -14,7 +14,7 @@ data class LedgerListResponse( val size: Int, val totalPage: Int, val totalCount: Int, - val sort: SortInfo + val sort: SortInfo, ) @Serializable @@ -23,7 +23,7 @@ data class Ledger( val info: LedgerInfo, val category: CategoryInfo, val totalAmounts: Int, - val totalCounts: Int + val totalCounts: Int, ) @Serializable @@ -32,7 +32,7 @@ data class LedgerInfo( val title: String, val description: String, val startAt: LocalDateTime, - val endAt: LocalDateTime + val endAt: LocalDateTime, ) @Serializable @@ -47,7 +47,7 @@ data class CategoryInfo( data class SortInfo( val empty: Boolean, val unsorted: Boolean, - val sorted: Boolean + val sorted: Boolean, ) internal fun LedgerListResponse.toModel() = this.ledgerList.map { ledger -> diff --git a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchScreen.kt b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchScreen.kt index 9c7c8ee0..3fc675f1 100644 --- a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchScreen.kt +++ b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchScreen.kt @@ -72,7 +72,7 @@ fun LedgerSearchRoute( onClickSearchResultContainer = { viewModel.upsertLedgerRecentSearch(it.title) viewModel.navigateLedgerDetail(it.id) - } + }, ) } diff --git a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt index 92c95db6..8ca99642 100644 --- a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt +++ b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt @@ -9,7 +9,6 @@ import com.susu.domain.usecase.ledgerrecentsearch.UpsertLedgerRecentSearchUseCas import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.launch -import timber.log.Timber import javax.inject.Inject @HiltViewModel From c03570a1dd07ee223fe08b8385b21a5768f5929c Mon Sep 17 00:00:00 2001 From: jinukeu Date: Mon, 15 Jan 2024 10:50:49 +0900 Subject: [PATCH 5/5] =?UTF-8?q?refactor:=20=EA=B2=80=EC=83=89=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EC=97=86=EC=9D=8C=20=ED=99=94=EB=A9=B4=EC=9D=B4=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=20=ED=86=B5=EC=8B=A0=EC=9D=B4=20=EC=9D=B4?= =?UTF-8?q?=EB=A4=84=EC=A7=84=20=ED=9B=84=20=EB=B0=9C=EC=83=9D=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../susu/feature/received/search/LedgerSearchContract.kt | 1 + .../com/susu/feature/received/search/LedgerSearchScreen.kt | 4 +++- .../susu/feature/received/search/LedgerSearchViewModel.kt | 7 ++++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchContract.kt b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchContract.kt index 28b5b0ae..f7a2e55d 100644 --- a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchContract.kt +++ b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchContract.kt @@ -11,6 +11,7 @@ data class LedgerSearchState( val recentSearchKeywordList: PersistentList = persistentListOf(), val ledgerList: PersistentList = persistentListOf(), val isLoading: Boolean = false, + val showSearchResultEmpty: Boolean = false, ) : UiState sealed interface LedgerSearchSideEffect : SideEffect { diff --git a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchScreen.kt b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchScreen.kt index 3fc675f1..2d968a6e 100644 --- a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchScreen.kt +++ b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchScreen.kt @@ -121,6 +121,7 @@ fun LedgerSearchScreen( ) } else { SearchResultColumn( + showSearchResultEmpty = uiState.showSearchResultEmpty, ledgerList = uiState.ledgerList, onClickItem = onClickSearchResultContainer, ) @@ -187,10 +188,11 @@ private fun RecentSearchColumn( @Composable private fun SearchResultColumn( + showSearchResultEmpty: Boolean, ledgerList: PersistentList, onClickItem: (Ledger) -> Unit, ) { - if (ledgerList.isEmpty()) { + if (showSearchResultEmpty) { ResultEmptyColumn( title = stringResource(R.string.ledger_search_screen_empty_search_result_title), ) diff --git a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt index 8ca99642..d8210429 100644 --- a/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt +++ b/feature/received/src/main/java/com/susu/feature/received/search/LedgerSearchViewModel.kt @@ -49,5 +49,10 @@ class LedgerSearchViewModel @Inject constructor( fun popBackStack() = postSideEffect(LedgerSearchSideEffect.PopBackStack) - private fun updateRecentSearchList(searchList: List) = intent { copy(recentSearchKeywordList = searchList.toPersistentList()) } + private fun updateRecentSearchList(searchList: List) = intent { + copy( + recentSearchKeywordList = searchList.toPersistentList(), + showSearchResultEmpty = searchList.isEmpty(), + ) + } }