diff --git a/app/src/main/java/kr/ksw/visitkorea/data/di/DataModule.kt b/app/src/main/java/kr/ksw/visitkorea/data/di/DataModule.kt index 3160761..da67bf7 100644 --- a/app/src/main/java/kr/ksw/visitkorea/data/di/DataModule.kt +++ b/app/src/main/java/kr/ksw/visitkorea/data/di/DataModule.kt @@ -11,12 +11,15 @@ import kr.ksw.visitkorea.data.remote.api.AreaCodeApi import kr.ksw.visitkorea.data.remote.api.LocationBasedListApi import kr.ksw.visitkorea.data.remote.api.RetrofitInterceptor import kr.ksw.visitkorea.data.remote.api.SearchFestivalApi +import kr.ksw.visitkorea.data.remote.api.SearchKeywordApi import kr.ksw.visitkorea.data.repository.AreaCodeRepository import kr.ksw.visitkorea.data.repository.AreaCodeRepositoryImpl import kr.ksw.visitkorea.data.repository.LocationBasedListRepository import kr.ksw.visitkorea.data.repository.LocationBasedListRepositoryImpl import kr.ksw.visitkorea.data.repository.SearchFestivalRepository import kr.ksw.visitkorea.data.repository.SearchFestivalRepositoryImpl +import kr.ksw.visitkorea.data.repository.SearchKeywordRepository +import kr.ksw.visitkorea.data.repository.SearchKeywordRepositoryImpl import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory @@ -85,4 +88,14 @@ object DataModule { fun provideSearchFestivalRepository(searchFestivalApi: SearchFestivalApi): SearchFestivalRepository { return SearchFestivalRepositoryImpl(searchFestivalApi) } + + @Provides + @Singleton + fun provideSearchKeywordApi(retrofit: Retrofit): SearchKeywordApi = retrofit.create(SearchKeywordApi::class.java) + + @Provides + @Singleton + fun provideSearchKeywordRepository(searchKeywordApi: SearchKeywordApi): SearchKeywordRepository { + return SearchKeywordRepositoryImpl(searchKeywordApi) + } } \ No newline at end of file diff --git a/app/src/main/java/kr/ksw/visitkorea/data/paging/source/SearchKeyWordPagingSource.kt b/app/src/main/java/kr/ksw/visitkorea/data/paging/source/SearchKeyWordPagingSource.kt new file mode 100644 index 0000000..5954b15 --- /dev/null +++ b/app/src/main/java/kr/ksw/visitkorea/data/paging/source/SearchKeyWordPagingSource.kt @@ -0,0 +1,40 @@ +package kr.ksw.visitkorea.data.paging.source + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import kr.ksw.visitkorea.data.mapper.toItems +import kr.ksw.visitkorea.data.remote.api.SearchKeywordApi +import kr.ksw.visitkorea.data.remote.dto.LocationBasedDTO + +class SearchKeyWordPagingSource( + private val searchKeywordApi: SearchKeywordApi, + private val keyword: String +) : PagingSource(){ + + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition?.let { anchor -> + val anchorPage = state.closestPageToPosition(anchor) + anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1) + } + } + + override suspend fun load(params: LoadParams): LoadResult { + val page = params.key ?: 1 + val loadSize = params.loadSize + val data = try { + searchKeywordApi.searchListByKeyword( + numOfRows = loadSize, + pageNo = page, + keyword = keyword + ).toItems() + } catch (e: Exception) { + emptyList() + } + return LoadResult.Page( + data = data, + prevKey = if(page == 1 || data.isEmpty()) null else page - 1, + nextKey = if(data.size == loadSize && data.isNotEmpty()) page + 1 else null + ) + } + +} \ No newline at end of file diff --git a/app/src/main/java/kr/ksw/visitkorea/data/remote/api/SearchKeywordApi.kt b/app/src/main/java/kr/ksw/visitkorea/data/remote/api/SearchKeywordApi.kt new file mode 100644 index 0000000..b3931c8 --- /dev/null +++ b/app/src/main/java/kr/ksw/visitkorea/data/remote/api/SearchKeywordApi.kt @@ -0,0 +1,17 @@ +package kr.ksw.visitkorea.data.remote.api + +import kr.ksw.visitkorea.data.remote.dto.LocationBasedDTO +import kr.ksw.visitkorea.data.remote.model.ApiResponse +import retrofit2.http.GET +import retrofit2.http.Query + +interface SearchKeywordApi { + @GET("searchKeyword1") + suspend fun searchListByKeyword( + @Query("arrange") arrange: String = "Q", + @Query("numOfRows") numOfRows: Int, + @Query("pageNo") pageNo: Int, + @Query("keyword") keyword: String, + @Query("contentTypeId") contentTypeId: String? = null + ): ApiResponse +} \ No newline at end of file diff --git a/app/src/main/java/kr/ksw/visitkorea/data/repository/SearchFestivalRepository.kt b/app/src/main/java/kr/ksw/visitkorea/data/repository/SearchFestivalRepository.kt index 0c8a5f1..2bf41a8 100644 --- a/app/src/main/java/kr/ksw/visitkorea/data/repository/SearchFestivalRepository.kt +++ b/app/src/main/java/kr/ksw/visitkorea/data/repository/SearchFestivalRepository.kt @@ -3,7 +3,7 @@ package kr.ksw.visitkorea.data.repository import kr.ksw.visitkorea.data.remote.dto.SearchFestivalDTO interface SearchFestivalRepository { - suspend operator fun invoke( + suspend fun searchFestival( numOfRows: Int, pageNo: Int, eventStartDate: String, diff --git a/app/src/main/java/kr/ksw/visitkorea/data/repository/SearchFestivalRepositoryImpl.kt b/app/src/main/java/kr/ksw/visitkorea/data/repository/SearchFestivalRepositoryImpl.kt index 0d24ca3..5072a01 100644 --- a/app/src/main/java/kr/ksw/visitkorea/data/repository/SearchFestivalRepositoryImpl.kt +++ b/app/src/main/java/kr/ksw/visitkorea/data/repository/SearchFestivalRepositoryImpl.kt @@ -8,7 +8,7 @@ import javax.inject.Inject class SearchFestivalRepositoryImpl @Inject constructor( private val searchFestivalApi: SearchFestivalApi ): SearchFestivalRepository { - override suspend fun invoke( + override suspend fun searchFestival( numOfRows: Int, pageNo: Int, eventStartDate: String, diff --git a/app/src/main/java/kr/ksw/visitkorea/data/repository/SearchKeywordRepository.kt b/app/src/main/java/kr/ksw/visitkorea/data/repository/SearchKeywordRepository.kt new file mode 100644 index 0000000..a2896d8 --- /dev/null +++ b/app/src/main/java/kr/ksw/visitkorea/data/repository/SearchKeywordRepository.kt @@ -0,0 +1,11 @@ +package kr.ksw.visitkorea.data.repository + +import androidx.paging.PagingData +import kotlinx.coroutines.flow.Flow +import kr.ksw.visitkorea.data.remote.dto.LocationBasedDTO + +interface SearchKeywordRepository { + suspend fun getListByKeyword( + keyword: String + ): Result>> +} \ No newline at end of file diff --git a/app/src/main/java/kr/ksw/visitkorea/data/repository/SearchKeywordRepositoryImpl.kt b/app/src/main/java/kr/ksw/visitkorea/data/repository/SearchKeywordRepositoryImpl.kt new file mode 100644 index 0000000..37e8e01 --- /dev/null +++ b/app/src/main/java/kr/ksw/visitkorea/data/repository/SearchKeywordRepositoryImpl.kt @@ -0,0 +1,41 @@ +package kr.ksw.visitkorea.data.repository + +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.PagingSource +import kotlinx.coroutines.flow.Flow +import kr.ksw.visitkorea.data.paging.source.SearchKeyWordPagingSource +import kr.ksw.visitkorea.data.remote.api.SearchKeywordApi +import kr.ksw.visitkorea.data.remote.dto.LocationBasedDTO +import javax.inject.Inject + +class SearchKeywordRepositoryImpl @Inject constructor( + private val searchKeywordApi: SearchKeywordApi +) : SearchKeywordRepository { + private var pagingSource: PagingSource? = null + + override suspend fun getListByKeyword( + keyword: String + ): Result>> = runCatching { + pagingSource?.run { + if(!invalid) { + invalidate() + } + } + Pager( + config = PagingConfig( + pageSize = 30, + initialLoadSize = 30 + ), + pagingSourceFactory = { + SearchKeyWordPagingSource( + searchKeywordApi, + keyword, + ).also { + pagingSource = it + } + } + ).flow + } +} \ No newline at end of file diff --git a/app/src/main/java/kr/ksw/visitkorea/domain/di/SearchModule.kt b/app/src/main/java/kr/ksw/visitkorea/domain/di/SearchModule.kt new file mode 100644 index 0000000..805d23a --- /dev/null +++ b/app/src/main/java/kr/ksw/visitkorea/domain/di/SearchModule.kt @@ -0,0 +1,17 @@ +package kr.ksw.visitkorea.domain.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityRetainedComponent +import kr.ksw.visitkorea.domain.usecase.search.GetListByKeywordUseCase +import kr.ksw.visitkorea.domain.usecase.search.GetListByKeywordUseCaseImpl + +@Module +@InstallIn(ActivityRetainedComponent::class) +abstract class SearchModule { + @Binds + abstract fun bindGetListByKeywordUseCase( + getListByKeywordUseCase: GetListByKeywordUseCaseImpl + ): GetListByKeywordUseCase +} \ No newline at end of file diff --git a/app/src/main/java/kr/ksw/visitkorea/domain/usecase/search/GetListByKeywordUseCase.kt b/app/src/main/java/kr/ksw/visitkorea/domain/usecase/search/GetListByKeywordUseCase.kt new file mode 100644 index 0000000..023258b --- /dev/null +++ b/app/src/main/java/kr/ksw/visitkorea/domain/usecase/search/GetListByKeywordUseCase.kt @@ -0,0 +1,11 @@ +package kr.ksw.visitkorea.domain.usecase.search + +import androidx.paging.PagingData +import kotlinx.coroutines.flow.Flow +import kr.ksw.visitkorea.data.remote.dto.LocationBasedDTO + +interface GetListByKeywordUseCase { + suspend operator fun invoke( + keyword: String + ): Result>> +} \ No newline at end of file diff --git a/app/src/main/java/kr/ksw/visitkorea/domain/usecase/search/GetListByKeywordUseCaseImpl.kt b/app/src/main/java/kr/ksw/visitkorea/domain/usecase/search/GetListByKeywordUseCaseImpl.kt new file mode 100644 index 0000000..0af8b89 --- /dev/null +++ b/app/src/main/java/kr/ksw/visitkorea/domain/usecase/search/GetListByKeywordUseCaseImpl.kt @@ -0,0 +1,15 @@ +package kr.ksw.visitkorea.domain.usecase.search + +import androidx.paging.PagingData +import kotlinx.coroutines.flow.Flow +import kr.ksw.visitkorea.data.remote.dto.LocationBasedDTO +import kr.ksw.visitkorea.data.repository.SearchKeywordRepository +import javax.inject.Inject + +class GetListByKeywordUseCaseImpl @Inject constructor( + private val searchKeywordRepository: SearchKeywordRepository +): GetListByKeywordUseCase { + override suspend fun invoke(keyword: String): Result>> { + return searchKeywordRepository.getListByKeyword(keyword) + } +} \ No newline at end of file diff --git a/app/src/main/java/kr/ksw/visitkorea/presentation/festival/component/FestivalCard.kt b/app/src/main/java/kr/ksw/visitkorea/presentation/festival/component/FestivalCard.kt index c9c1af6..d423055 100644 --- a/app/src/main/java/kr/ksw/visitkorea/presentation/festival/component/FestivalCard.kt +++ b/app/src/main/java/kr/ksw/visitkorea/presentation/festival/component/FestivalCard.kt @@ -51,17 +51,15 @@ fun FestivalCard( Box( modifier = Modifier .background(Color.White) - .padding( - top = 8.dp, - start = 8.dp, - end = 8.dp - ) ) { AsyncImage( modifier = Modifier .fillMaxWidth() .aspectRatio(2f) - .clip(RoundedCornerShape(24.dp)) + .clip(RoundedCornerShape( + topStart = 24.dp, + topEnd = 24.dp + )) .background(color = Color.LightGray), model = ImageRequest .Builder(LocalContext.current) @@ -115,38 +113,45 @@ fun FestivalCard( letterSpacing = (-0.6).sp ) } - SingleLineText( - modifier = Modifier - .padding(start = 16.dp, end = 16.dp, bottom = 10.dp) - .align(Alignment.BottomEnd), - text = festival.title, - fontSize = 18.sp, - fontWeight = FontWeight.Medium, - color = Color.White - ) } - Row( + Column( modifier = Modifier .fillMaxSize() .background(Color.White) - .padding( - top = 8.dp, - start = 10.dp, - end = 10.dp, - bottom = 10.dp - ), - verticalAlignment = Alignment.CenterVertically ) { - Icon( - modifier = Modifier - .size(20.dp), - imageVector = Icons.Outlined.LocationOn, - contentDescription = null, - ) SingleLineText( - text = festival.address, - fontSize = 16.sp, + modifier = Modifier + .padding( + top = 8.dp, + start = 10.dp, + end = 10.dp, + bottom = 4.dp + ), + text = festival.title, + fontSize = 18.sp, + fontWeight = FontWeight.Medium, ) + Row( + modifier = Modifier + .padding( + top = 8.dp, + start = 10.dp, + end = 10.dp, + bottom = 10.dp + ), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + modifier = Modifier + .size(20.dp), + imageVector = Icons.Outlined.LocationOn, + contentDescription = null, + ) + SingleLineText( + text = festival.address, + fontSize = 16.sp, + ) + } } } } diff --git a/app/src/main/java/kr/ksw/visitkorea/presentation/main/MainNavHost.kt b/app/src/main/java/kr/ksw/visitkorea/presentation/main/MainNavHost.kt index f795f40..7a1638b 100644 --- a/app/src/main/java/kr/ksw/visitkorea/presentation/main/MainNavHost.kt +++ b/app/src/main/java/kr/ksw/visitkorea/presentation/main/MainNavHost.kt @@ -14,6 +14,7 @@ import androidx.navigation.compose.composable import kr.ksw.visitkorea.presentation.festival.screen.FestivalScreen import kr.ksw.visitkorea.presentation.home.screen.HomeScreen import kr.ksw.visitkorea.presentation.hotel.screen.HotelScreen +import kr.ksw.visitkorea.presentation.search.screen.SearchScreen import kr.ksw.visitkorea.presentation.ui.theme.VisitKoreaTheme @Composable @@ -36,12 +37,7 @@ fun MainNavHost( FestivalScreen() } composable(route = MainRoute.SEARCH.route) { - SampleScreen { - Text( - text = "SEARCH", - fontSize = 32.sp - ) - } + SearchScreen() } composable(route = MainRoute.FAVORITE.route) { SampleScreen { diff --git a/app/src/main/java/kr/ksw/visitkorea/presentation/more/viewmodel/MoreState.kt b/app/src/main/java/kr/ksw/visitkorea/presentation/more/viewmodel/MoreState.kt index 6ac98d3..4eb69c7 100644 --- a/app/src/main/java/kr/ksw/visitkorea/presentation/more/viewmodel/MoreState.kt +++ b/app/src/main/java/kr/ksw/visitkorea/presentation/more/viewmodel/MoreState.kt @@ -8,6 +8,6 @@ import kr.ksw.visitkorea.domain.usecase.model.MoreCardModel @Immutable data class MoreState( - val isRefreshing: Boolean = true, + val isRefreshing: Boolean = false, val moreCardModelFlow: Flow> = emptyFlow(), ) diff --git a/app/src/main/java/kr/ksw/visitkorea/presentation/more/viewmodel/MoreViewModel.kt b/app/src/main/java/kr/ksw/visitkorea/presentation/more/viewmodel/MoreViewModel.kt index 31960f0..1efd41c 100644 --- a/app/src/main/java/kr/ksw/visitkorea/presentation/more/viewmodel/MoreViewModel.kt +++ b/app/src/main/java/kr/ksw/visitkorea/presentation/more/viewmodel/MoreViewModel.kt @@ -12,7 +12,6 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import kr.ksw.visitkorea.R import kr.ksw.visitkorea.domain.usecase.mapper.toMoreCardModel import kr.ksw.visitkorea.domain.usecase.more.GetMoreListUseCase import javax.inject.Inject @@ -34,7 +33,6 @@ class MoreViewModel @Inject constructor( _moreState.update { it.copy(isRefreshing = true) } - delay(500) } val moreListFlow = getMoreListUseCase( @@ -43,6 +41,8 @@ class MoreViewModel @Inject constructor( "37.5678958128", contentTypeId ).getOrNull() + delay(300) + if(moreListFlow == null) { // Toast Effect _moreState.update { diff --git a/app/src/main/java/kr/ksw/visitkorea/presentation/search/screen/SearchScreen.kt b/app/src/main/java/kr/ksw/visitkorea/presentation/search/screen/SearchScreen.kt new file mode 100644 index 0000000..cb6bc84 --- /dev/null +++ b/app/src/main/java/kr/ksw/visitkorea/presentation/search/screen/SearchScreen.kt @@ -0,0 +1,162 @@ +package kr.ksw.visitkorea.presentation.search.screen + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.SearchBar +import androidx.compose.material3.SearchBarDefaults +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.paging.compose.collectAsLazyPagingItems +import kr.ksw.visitkorea.presentation.home.component.CultureCard +import kr.ksw.visitkorea.presentation.search.viewmodel.SearchActions +import kr.ksw.visitkorea.presentation.search.viewmodel.SearchState +import kr.ksw.visitkorea.presentation.search.viewmodel.SearchViewModel +import kr.ksw.visitkorea.presentation.ui.theme.VisitKoreaTheme + +@Composable +fun SearchScreen( + viewModel: SearchViewModel = hiltViewModel(), +) { + val searchState by viewModel.searchState.collectAsState() + SearchScreen( + searchState, + viewModel::onAction + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SearchScreen( + searchState: SearchState, + onAction: (SearchActions) -> Unit, +) { + val searchCardModels = searchState.searchCardModelFlow.collectAsLazyPagingItems() + val focusManager = LocalFocusManager.current + + Surface { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp), + ) { + Spacer(modifier = Modifier.height(20.dp)) + Text( + text = "검색", + fontSize = 32.sp, + fontWeight = FontWeight.SemiBold + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "키워드로 관광지를 찾아보세요!", + fontSize = 22.sp, + ) + Spacer(modifier = Modifier.height(16.dp)) + SearchBar( + modifier = Modifier + .fillMaxWidth(), + inputField = { + SearchBarDefaults.InputField( + query = searchState.searchKeyword, + onQueryChange = { + onAction(SearchActions.UpdateSearchKeyword(it)) + }, + onSearch = { + onAction(SearchActions.SubmitSearchKeyword) + focusManager.clearFocus() + }, + expanded = false, + onExpandedChange = {}, + placeholder = { + Text(text = "검색어를 입력해주세요") + }, + trailingIcon = { + Icon( + Icons.Default.Search, + contentDescription = null + ) + } + ) + }, + expanded = false, + onExpandedChange = {} + ) { + + } + Spacer(modifier = Modifier.height(10.dp)) + + if(searchState.isLoadingImages) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } else { + LazyVerticalGrid( + columns = GridCells.Fixed(2), + contentPadding = PaddingValues(vertical = 16.dp), + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + items( + count = searchCardModels.itemCount, + key = { index -> + searchCardModels[index]?.contentId?.toInt() ?: index + } + ) { index -> + val model = searchCardModels[index] + model?.run { + CultureCard( + modifier = Modifier.clickable { + // Go to DetailActivity + }, + title = title, + address = address, + image = firstImage + ) + } + } + } + } + } + } +} + +@Composable +@Preview(showBackground = true) +fun SearchScreenPreview() { + VisitKoreaTheme { + SearchScreen( + searchState = SearchState(), + ) { + + } + } +} + diff --git a/app/src/main/java/kr/ksw/visitkorea/presentation/search/viewmodel/SearchActions.kt b/app/src/main/java/kr/ksw/visitkorea/presentation/search/viewmodel/SearchActions.kt new file mode 100644 index 0000000..5b54945 --- /dev/null +++ b/app/src/main/java/kr/ksw/visitkorea/presentation/search/viewmodel/SearchActions.kt @@ -0,0 +1,6 @@ +package kr.ksw.visitkorea.presentation.search.viewmodel + +sealed interface SearchActions { + data object SubmitSearchKeyword : SearchActions + data class UpdateSearchKeyword(val newKeyword: String) : SearchActions +} \ No newline at end of file diff --git a/app/src/main/java/kr/ksw/visitkorea/presentation/search/viewmodel/SearchState.kt b/app/src/main/java/kr/ksw/visitkorea/presentation/search/viewmodel/SearchState.kt new file mode 100644 index 0000000..ca909f8 --- /dev/null +++ b/app/src/main/java/kr/ksw/visitkorea/presentation/search/viewmodel/SearchState.kt @@ -0,0 +1,14 @@ +package kr.ksw.visitkorea.presentation.search.viewmodel + +import androidx.compose.runtime.Immutable +import androidx.paging.PagingData +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kr.ksw.visitkorea.domain.usecase.model.CommonCardModel + +@Immutable +data class SearchState( + val searchKeyword: String = "", + val isLoadingImages: Boolean = false, + val searchCardModelFlow: Flow> = emptyFlow(), +) \ No newline at end of file diff --git a/app/src/main/java/kr/ksw/visitkorea/presentation/search/viewmodel/SearchViewModel.kt b/app/src/main/java/kr/ksw/visitkorea/presentation/search/viewmodel/SearchViewModel.kt new file mode 100644 index 0000000..23a1e16 --- /dev/null +++ b/app/src/main/java/kr/ksw/visitkorea/presentation/search/viewmodel/SearchViewModel.kt @@ -0,0 +1,87 @@ +package kr.ksw.visitkorea.presentation.search.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.paging.cachedIn +import androidx.paging.filter +import androidx.paging.map +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kr.ksw.visitkorea.domain.usecase.mapper.toCommonCardModel +import kr.ksw.visitkorea.domain.usecase.search.GetListByKeywordUseCase +import kr.ksw.visitkorea.presentation.common.ContentType +import javax.inject.Inject + +@HiltViewModel +class SearchViewModel @Inject constructor( + private val getListByKeywordUseCase: GetListByKeywordUseCase +): ViewModel() { + private val _searchState: MutableStateFlow = MutableStateFlow(SearchState()) + val searchState: StateFlow + get() = _searchState.asStateFlow() + + fun onAction(action: SearchActions) { + when(action) { + is SearchActions.UpdateSearchKeyword -> { + _searchState.update { + it.copy( + searchKeyword = action.newKeyword + ) + } + } + is SearchActions.SubmitSearchKeyword -> { + getListByKeyword() + } + } + } + + private fun getListByKeyword() { + viewModelScope.launch { + _searchState.update { + it.copy( + isLoadingImages = true + ) + } + val searchListFlow = getListByKeywordUseCase( + searchState.value.searchKeyword + ).getOrNull() + delay(300) + + if(searchListFlow == null) { + _searchState.update { + it.copy( + isLoadingImages = false + ) + } + return@launch + } + val searchCardModelFlow = searchListFlow.map { pagingData -> + pagingData.filter { + contentTypeFilter(it.contentTypeId) + }.map { + it.toCommonCardModel() + } + }.cachedIn(viewModelScope) + _searchState.update { + it.copy( + searchCardModelFlow = searchCardModelFlow, + isLoadingImages = false + ) + } + } + } + + private fun contentTypeFilter(contentType: String) = when(contentType) { + ContentType.CULTURE.contentTypeId, + ContentType.LEiSURE.contentTypeId, + ContentType.RESTAURANT.contentTypeId, + ContentType.TOURIST.contentTypeId -> true + else -> false + } +} \ No newline at end of file