Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#15] 검색화면 (SearchScreen) 구현 #16

Merged
merged 7 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions app/src/main/java/kr/ksw/visitkorea/data/di/DataModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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<Int, LocationBasedDTO>(){

override fun getRefreshKey(state: PagingState<Int, LocationBasedDTO>): 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<Int>): LoadResult<Int, LocationBasedDTO> {
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
)
}

}
Original file line number Diff line number Diff line change
@@ -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<LocationBasedDTO>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

방금 든 생각인데, @QueryMap 을 사용하면 여러 개의 쿼리를 맵 하나에 넣어서 좀더 간편하게 할 수 있을 거 같아요.
https://futurestud.io/tutorials/retrofit-2-add-multiple-query-parameter-with-querymap

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@QueryMap을 쓰기에는 좀 애매하다고 생각했었는데, 한 번 적용해 보도록 하겠습니다.

}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Flow<PagingData<LocationBasedDTO>>>
}
Original file line number Diff line number Diff line change
@@ -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<Int, LocationBasedDTO>? = null

override suspend fun getListByKeyword(
keyword: String
): Result<Flow<PagingData<LocationBasedDTO>>> = runCatching {
pagingSource?.run {
if(!invalid) {
invalidate()
}
}
Pager(
config = PagingConfig(
pageSize = 30,
initialLoadSize = 30
),
pagingSourceFactory = {
SearchKeyWordPagingSource(
searchKeywordApi,
keyword,
).also {
pagingSource = it
}
}
).flow
}
}
17 changes: 17 additions & 0 deletions app/src/main/java/kr/ksw/visitkorea/domain/di/SearchModule.kt
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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<Flow<PagingData<LocationBasedDTO>>>
}
Original file line number Diff line number Diff line change
@@ -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<Flow<PagingData<LocationBasedDTO>>> {
return searchKeywordRepository.getListByKeyword(keyword)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<PagingData<MoreCardModel>> = emptyFlow(),
)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -34,7 +33,6 @@ class MoreViewModel @Inject constructor(
_moreState.update {
it.copy(isRefreshing = true)
}
delay(500)
}

val moreListFlow = getMoreListUseCase(
Expand All @@ -43,6 +41,8 @@ class MoreViewModel @Inject constructor(
"37.5678958128",
contentTypeId
).getOrNull()
delay(300)

if(moreListFlow == null) {
// Toast Effect
_moreState.update {
Expand Down
Loading
Loading