Skip to content

Commit

Permalink
[feat/login]: 식당 검색 기능 Paging
Browse files Browse the repository at this point in the history
  • Loading branch information
kez-lab committed Feb 15, 2024
1 parent 972fec2 commit 51851f9
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import com.everymeal.data.datasource.restaurant.RestaurantDataSource
import com.everymeal.data.datasource.restaurant.RestaurantDataSourceImpl
import com.everymeal.data.datasource.review.ReviewDataSource
import com.everymeal.data.datasource.review.ReviewDataSourceImpl
import com.everymeal.data.datasource.search.SearchDataSource
import com.everymeal.data.datasource.search.SearchDataSourceImpl
import com.everymeal.data.repository.auth.DefaultUsersRepository
import com.everymeal.data.repository.local.LocalRepositoryImpl
import com.everymeal.data.repository.onboarding.OnboardingRepositoryImpl
Expand Down Expand Up @@ -92,6 +94,12 @@ abstract class RepositoryModule {
defaultReviewRepository: DefaultReviewRepository,
): ReviewRepository

@Singleton
@Binds
abstract fun bindSearchDataSource(
searchDataSourceImpl: SearchDataSourceImpl,
): SearchDataSource

@Singleton
@Binds
abstract fun bindSearchRepository(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.everymeal.data.datasource.search

import androidx.paging.PagingData
import com.everymeal.data.model.restaruant.RestaurantResponse
import kotlinx.coroutines.flow.Flow

interface SearchDataSource {

suspend fun searchRestraurant(
campusIdx: Int,
keyword: String,
): Flow<PagingData<RestaurantResponse>>


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.everymeal.data.datasource.search

import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.everymeal.data.model.restaruant.RestaurantResponse
import com.everymeal.data.service.search.SearchService
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

class SearchDataSourceImpl @Inject constructor(
private val searchService: SearchService,
) : SearchDataSource {
override suspend fun searchRestraurant(
campusIdx: Int,
keyword: String,
): Flow<PagingData<RestaurantResponse>> {
val pagingSourceFactory = {
SearchPagingSource(
searchService = searchService,
campusIdx = campusIdx,
keyword = keyword
)
}
return Pager(
config = PagingConfig(
initialLoadSize = 20,
pageSize = 20,
enablePlaceholders = false,
),
pagingSourceFactory = pagingSourceFactory
).flow
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.everymeal.data.datasource.search

import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.everymeal.data.datasource.restaurant.PAGING_SIZE
import com.everymeal.data.datasource.restaurant.STARTING_PAGE_INDEX
import com.everymeal.data.model.restaruant.RestaurantResponse
import com.everymeal.data.service.search.SearchService
import retrofit2.HttpException
import java.io.IOException

class SearchPagingSource(
private val searchService: SearchService,
private val campusIdx: Int,
private val keyword: String,
) : PagingSource<Int, RestaurantResponse>() {
override fun getRefreshKey(state: PagingState<Int, RestaurantResponse>): Int? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, RestaurantResponse> {
val position = params.key ?: STARTING_PAGE_INDEX

return try {
val response = searchService.search(
campusIdx = campusIdx,
keyword = keyword,
offset = position,
limit = PAGING_SIZE
)
val restaurants = response.data?.content ?: emptyList()
LoadResult.Page(
data = restaurants,
prevKey = if (position == STARTING_PAGE_INDEX) null else position - 1,
nextKey = if (restaurants.isEmpty()) null else position + 1
)
} catch (exception: IOException) {
LoadResult.Error(exception)
} catch (exception: HttpException) {
LoadResult.Error(exception)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ data class SearchRestaurantResponse(
@Serializable
data class Data(
@SerialName("content")
val content: List<Content?>? = null,
val content: List<RestaurantResponse>? = null,
@SerialName("empty")
val empty: Boolean? = null,
@SerialName("first")
Expand All @@ -32,37 +32,12 @@ data class SearchRestaurantResponse(
@SerialName("size")
val size: Int? = null,
@SerialName("sort")
val sort: Sort? = null,
val sort: Pageable.Sort? = null,
@SerialName("totalElements")
val totalElements: Int? = null,
@SerialName("totalPages")
val totalPages: Int? = null,
) {
@Serializable
data class Content(
@SerialName("address")
val address: String? = null,
@SerialName("categoryDetail")
val categoryDetail: String? = null,
@SerialName("distance")
val distance: Int? = null,
@SerialName("grade")
val grade: Int? = null,
@SerialName("idx")
val idx: Int? = null,
@SerialName("images")
val images: List<String?>? = null,
@SerialName("isLiked")
val isLiked: Boolean? = null,
@SerialName("name")
val name: String? = null,
@SerialName("phoneNumber")
val phoneNumber: String? = null,
@SerialName("recommendedCount")
val recommendedCount: Int? = null,
@SerialName("reviewCount")
val reviewCount: Int? = null,
)

@Serializable
data class Pageable(
Expand All @@ -89,36 +64,25 @@ data class SearchRestaurantResponse(
val unsorted: Boolean? = null,
)
}

@Serializable
data class Sort(
@SerialName("empty")
val empty: Boolean? = null,
@SerialName("sorted")
val sorted: Boolean? = null,
@SerialName("unsorted")
val unsorted: Boolean? = null,
)
}
}


fun SearchRestaurantResponse.toRestaurants(): List<RestaurantDataEntity> {
return this.data?.content?.mapNotNull { content ->
content?.let {
fun SearchRestaurantResponse.toRestaurants(): List<RestaurantDataEntity?> {
return this.data?.content?.map { content ->
content.let {
RestaurantDataEntity(
idx = it.idx ?: 0,
name = it.name.orEmpty(),
address = it.address.orEmpty(),
phoneNumber = it.phoneNumber.orEmpty(),
categoryDetail = it.categoryDetail.orEmpty(),
distance = it.distance ?: 0,
grade = it.grade?.toFloat()
?: 0f,
reviewCount = it.reviewCount ?: 0,
recommendedCount = it.recommendedCount ?: 0,
images = it.images?.filterNotNull(),
isLiked = it.isLiked ?: false,
idx = it.idx,
name = it.name,
address = it.address,
phoneNumber = it.phoneNumber,
categoryDetail = it.categoryDetail,
distance = it.distance,
grade = it.grade,
reviewCount = it.reviewCount,
recommendedCount = it.recommendedCount,
images = it.images,
isLiked = it.isLiked,
)
}
} ?: emptyList()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
package com.everymeal.data.repository.search

import com.everymeal.data.model.restaruant.toRestaurants
import com.everymeal.data.service.search.SearchService
import androidx.paging.PagingData
import androidx.paging.map
import com.everymeal.data.datasource.search.SearchDataSourceImpl
import com.everymeal.data.model.restaruant.toRestaurant
import com.everymeal.domain.model.restaurant.RestaurantDataEntity
import com.everymeal.domain.repository.search.SearchRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject

class DefaultSearchRepository @Inject constructor(
private val searchService: SearchService,
private val searchDataSourceImpl: SearchDataSourceImpl,
) : SearchRepository {
override suspend fun search(keyword: String): Result<List<RestaurantDataEntity>> {
return runCatching {
searchService.search(keyword).toRestaurants()
override suspend fun search(
campusIdx: Int,
keyword: String
): Flow<PagingData<RestaurantDataEntity>> {
return searchDataSourceImpl.searchRestraurant(
campusIdx = campusIdx,
keyword = keyword,
).map { pagingData ->
pagingData.map {
it.toRestaurant()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package com.everymeal.data.service.search

import com.everymeal.data.model.BaseResponse
import com.everymeal.data.model.restaruant.SearchRestaurantResponse
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query

interface SearchService {
// TODO 임시 캠퍼스 ID
@GET("/api/v1/stores/{0}/{keyword}")
@GET("/api/v1/stores/{campusIdx}/{keyword}")
suspend fun search(
@Path("campusIdx") campusIdx: Int,
@Path("keyword") keyword: String,
@Query("offset") offset: Int?,
@Query("limit") limit: Int?,
): SearchRestaurantResponse
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.everymeal.domain.repository.search

import androidx.paging.PagingData
import com.everymeal.domain.model.restaurant.RestaurantDataEntity
import kotlinx.coroutines.flow.Flow

interface SearchRepository {
suspend fun search(keyword: String): Result<List<RestaurantDataEntity>>
suspend fun search(campusIdx: Int, keyword: String): Flow<PagingData<RestaurantDataEntity>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import android.util.Log
import androidx.lifecycle.viewModelScope
import com.everymeal.domain.usecase.local.GetUniversityIndexUseCase
import com.everymeal.domain.usecase.restaurant.GetHomeRestaurantUseCase
import com.everymeal.domain.usecase.restaurant.GetUnivRestaurantUseCase
import com.everymeal.domain.usecase.review.GetHomeReviewUseCase
import com.everymeal.presentation.base.BaseViewModel
import com.everymeal.presentation.base.LoadState
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.everymeal.presentation.ui.search

import androidx.paging.PagingData
import com.everymeal.domain.model.restaurant.RestaurantDataEntity
import com.everymeal.presentation.base.ViewEvent
import com.everymeal.presentation.base.ViewSideEffect
import com.everymeal.presentation.base.ViewState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

/*
대학교 불러오기 LoadState
Expand All @@ -13,7 +16,7 @@ data class SearchState(
val searchQuery: String = "",
val searchIsShowHistory: Boolean = true,
val searchHistoryItems: List<String> = listOf(),
val searchResultList: List<RestaurantDataEntity> = listOf(),
val searchResultList: Flow<PagingData<RestaurantDataEntity>> = flow { },
) : ViewState

sealed class SearchEvent : ViewEvent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
Expand All @@ -29,6 +28,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import com.everymeal.domain.model.restaurant.RestaurantDataEntity
import com.everymeal.presentation.R
import com.everymeal.presentation.components.EveryMealRestaurantItem
Expand Down Expand Up @@ -75,7 +76,7 @@ fun SearchScreen(
} else {
SearchDetail(
modifier = Modifier.padding(innerPadding),
searchResultList = viewState.value.searchResultList,
searchResultList = viewState.value.searchResultList.collectAsLazyPagingItems(),
)
}
}
Expand All @@ -84,14 +85,19 @@ fun SearchScreen(
@Composable
fun SearchDetail(
modifier: Modifier = Modifier,
searchResultList: List<RestaurantDataEntity>,
searchResultList: LazyPagingItems<RestaurantDataEntity>,
) {
LazyColumn(modifier = modifier) {
itemsIndexed(searchResultList) { index, restaurant ->
items(searchResultList.itemCount) { indexd ->
val restaurant = searchResultList[indexd] ?: return@items
EveryMealRestaurantItem(
restaurant = restaurant,
onLoveClick = { },
onDetailClick = { },
onLoveClick = {

},
onDetailClick = {

},
)
}
}
Expand Down
Loading

0 comments on commit 51851f9

Please sign in to comment.