diff --git a/data/src/main/java/org/gdsc/data/database/GroupBySearchPagingSource.kt b/data/src/main/java/org/gdsc/data/database/GroupBySearchPagingSource.kt new file mode 100644 index 00000000..a24d7c3c --- /dev/null +++ b/data/src/main/java/org/gdsc/data/database/GroupBySearchPagingSource.kt @@ -0,0 +1,33 @@ +package org.gdsc.data.database + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import org.gdsc.data.network.GroupAPI +import org.gdsc.domain.model.GroupPreview +import org.gdsc.domain.model.request.GroupSearchRequest + +class GroupBySearchPagingSource( + private val api: GroupAPI, + private val groupSearchRequest: GroupSearchRequest, +): PagingSource() { + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition?.let { anchorPosition -> + val anchorPage = state.closestPageToPosition(anchorPosition) + anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1) + } + } + + override suspend fun load(params: LoadParams): LoadResult { + val page = params.key ?: 1 + return try { + val items = api.searchGroup(groupSearchRequest) + LoadResult.Page( + data = items.data.groupList, + prevKey = null, + nextKey = if (items.data.groupList.isEmpty()) null else page + 1 + ) + } catch (e: Exception) { + return LoadResult.Error(e) + } + } +} \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/datasource/GroupDataSource.kt b/data/src/main/java/org/gdsc/data/datasource/GroupDataSource.kt index 8653d6e9..f4559e39 100644 --- a/data/src/main/java/org/gdsc/data/datasource/GroupDataSource.kt +++ b/data/src/main/java/org/gdsc/data/datasource/GroupDataSource.kt @@ -1,5 +1,7 @@ package org.gdsc.data.datasource +import androidx.paging.PagingData +import kotlinx.coroutines.flow.Flow import org.gdsc.domain.model.GroupPreview import org.gdsc.domain.model.response.Group @@ -10,4 +12,6 @@ interface GroupDataSource { suspend fun selectGroup(groupId: Int): String suspend fun searchGroup(keyword: String, limitCount: Int): List + + suspend fun searchPagingGroup(keyword: String): Flow> } \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/datasource/GroupDataSourceImpl.kt b/data/src/main/java/org/gdsc/data/datasource/GroupDataSourceImpl.kt index 5ce7b982..c4cab792 100644 --- a/data/src/main/java/org/gdsc/data/datasource/GroupDataSourceImpl.kt +++ b/data/src/main/java/org/gdsc/data/datasource/GroupDataSourceImpl.kt @@ -1,7 +1,13 @@ package org.gdsc.data.datasource -import android.util.Log -import org.gdsc.data.database.GroupPaging +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.cachedIn +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import org.gdsc.data.database.GroupBySearchPagingSource import org.gdsc.data.network.GroupAPI import org.gdsc.domain.model.GroupPreview import org.gdsc.domain.model.request.GroupSearchRequest @@ -10,7 +16,7 @@ import javax.inject.Inject class GroupDataSourceImpl @Inject constructor( private val groupAPI: GroupAPI, -): GroupDataSource { +) : GroupDataSource { override suspend fun getMyGroups(): List { runCatching { groupAPI.getMyGroups() @@ -36,4 +42,20 @@ class GroupDataSourceImpl @Inject constructor( override suspend fun searchGroup(keyword: String, limitCount: Int): List { return groupAPI.searchGroup(GroupSearchRequest(keyword)).data.groupList.take(limitCount) } + + override suspend fun searchPagingGroup( + keyword: String, + ): Flow> { + return Pager( + config = PagingConfig( + pageSize = 20, + enablePlaceholders = true + ) + ) { + GroupBySearchPagingSource( + groupAPI, + GroupSearchRequest(keyword)) + }.flow.cachedIn(CoroutineScope(Dispatchers.IO)) + + } } \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/repository/GroupRepositoryImpl.kt b/data/src/main/java/org/gdsc/data/repository/GroupRepositoryImpl.kt index 6be11426..0ef2de30 100644 --- a/data/src/main/java/org/gdsc/data/repository/GroupRepositoryImpl.kt +++ b/data/src/main/java/org/gdsc/data/repository/GroupRepositoryImpl.kt @@ -1,5 +1,7 @@ package org.gdsc.data.repository +import androidx.paging.PagingData +import kotlinx.coroutines.flow.Flow import org.gdsc.data.datasource.GroupDataSource import org.gdsc.domain.model.GroupPreview import org.gdsc.domain.model.response.Group @@ -20,4 +22,8 @@ class GroupRepositoryImpl @Inject constructor( override suspend fun searchGroup(keyword: String, limitCount: Int): List { return groupDataSource.searchGroup(keyword, limitCount) } + + override suspend fun searchPagingGroup(keyword: String): Flow> { + return groupDataSource.searchPagingGroup(keyword) + } } \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/repository/GroupRepository.kt b/domain/src/main/java/org/gdsc/domain/repository/GroupRepository.kt index b81a58c0..6d8f050a 100644 --- a/domain/src/main/java/org/gdsc/domain/repository/GroupRepository.kt +++ b/domain/src/main/java/org/gdsc/domain/repository/GroupRepository.kt @@ -1,5 +1,7 @@ package org.gdsc.domain.repository +import androidx.paging.PagingData +import kotlinx.coroutines.flow.Flow import org.gdsc.domain.model.GroupPreview import org.gdsc.domain.model.response.Group @@ -9,4 +11,6 @@ interface GroupRepository { suspend fun selectGroup(groupId: Int): String suspend fun searchGroup(keyword: String, limitCount: Int): List + + suspend fun searchPagingGroup(keyword: String): Flow> } \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/usecase/GetGroupBySearchUseCase.kt b/domain/src/main/java/org/gdsc/domain/usecase/GetGroupBySearchUseCase.kt new file mode 100644 index 00000000..ad3a0c1e --- /dev/null +++ b/domain/src/main/java/org/gdsc/domain/usecase/GetGroupBySearchUseCase.kt @@ -0,0 +1,18 @@ +package org.gdsc.domain.usecase + +import androidx.paging.PagingData +import kotlinx.coroutines.flow.Flow +import org.gdsc.domain.model.GroupPreview +import org.gdsc.domain.repository.GroupRepository +import javax.inject.Inject + +class GetGroupBySearchUseCase @Inject constructor( + private val groupRepository: GroupRepository +) { + suspend operator fun invoke( + keyword: String, + ): Flow> { + + return groupRepository.searchPagingGroup(keyword) + } +} \ No newline at end of file diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchViewModel.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchViewModel.kt index 64146b9c..43982ffa 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchViewModel.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchViewModel.kt @@ -16,6 +16,7 @@ import org.gdsc.domain.SortType import org.gdsc.domain.model.GroupPreview import org.gdsc.domain.model.Location import org.gdsc.domain.model.RegisteredRestaurant +import org.gdsc.domain.usecase.GetGroupBySearchUseCase import org.gdsc.domain.usecase.GetGroupBySearchWithLimitCountUseCase import org.gdsc.domain.usecase.GetRestaurantBySearchUseCase import org.gdsc.domain.usecase.GetRestaurantBySearchWithLimitCountUseCase @@ -35,7 +36,8 @@ class AllSearchViewModel @Inject constructor( private val deleteSearchedKeywordUseCase: DeleteSearchedKeywordUseCase, private val initSearchedKeywordUseCase: InitSearchedKeywordUseCase, private val getRestaurantBySearchWithLimitCountUseCase: GetRestaurantBySearchWithLimitCountUseCase, - private val getGroupBySearchWithLimitCountUseCase: GetGroupBySearchWithLimitCountUseCase + private val getGroupBySearchWithLimitCountUseCase: GetGroupBySearchWithLimitCountUseCase, + private val getGroupBySearchUseCase: GetGroupBySearchUseCase ) : ViewModel() { init { @@ -81,6 +83,12 @@ class AllSearchViewModel @Inject constructor( private var _searchedGroupPreviewState = MutableStateFlow>(emptyList()) + val searchedGroupState: StateFlow> + get() = _searchedGroupState + + private var _searchedGroupState = + MutableStateFlow>(PagingData.empty()) + val searchedGroupPreviewState: StateFlow> get() = _searchedGroupPreviewState @@ -166,6 +174,12 @@ class AllSearchViewModel @Inject constructor( val result = getGroupBySearchWithLimitCountUseCase(searchKeyword.value, 3) _searchedGroupPreviewState.value = result } + viewModelScope.launch { + getGroupBySearchUseCase(searchKeyword.value).distinctUntilChanged() + .collect { + _searchedGroupState.value = it + } + } } diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryAllFragment.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryAllFragment.kt index 168dd3a0..090aa9ac 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryAllFragment.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryAllFragment.kt @@ -48,10 +48,10 @@ class SearchCategoryAllFragment( viewModel.isForGroup.collect { isForGroup -> if (isForGroup.not()) { binding.restaurantRecyclerView.visibility = View.GONE - binding.waringNoRestaurant.root.visibility = View.VISIBLE + binding.warningNoRestaurant.root.visibility = View.VISIBLE } else { binding.restaurantRecyclerView.visibility = View.VISIBLE - binding.waringNoRestaurant.root.visibility = View.GONE + binding.warningNoRestaurant.root.visibility = View.GONE } } } @@ -60,10 +60,10 @@ class SearchCategoryAllFragment( viewModel.searchedGroupPreviewState.collect { if (it.isEmpty()) { binding.groupRecyclerView.visibility = View.GONE - binding.waringNoGroup.root.visibility = View.VISIBLE + binding.warningNoGroup.root.visibility = View.VISIBLE } else { binding.groupRecyclerView.visibility = View.VISIBLE - binding.waringNoGroup.root.visibility = View.GONE + binding.warningNoGroup.root.visibility = View.GONE } } diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryGroupFragment.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryGroupFragment.kt index 487d0ff7..5cc44206 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryGroupFragment.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryGroupFragment.kt @@ -6,7 +6,11 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels +import androidx.recyclerview.widget.LinearLayoutManager import org.gdsc.presentation.databinding.FragmentSearchCategoryGroupBinding +import org.gdsc.presentation.utils.repeatWhenUiStarted +import org.gdsc.presentation.view.allsearch.adapter.SearchCategoryGroupAdapter +import org.gdsc.presentation.view.allsearch.adapter.SearchCategoryGroupPreviewAdapter class SearchCategoryGroupFragment( private val searchKeyword: String @@ -27,5 +31,28 @@ class SearchCategoryGroupFragment( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + + repeatWhenUiStarted { + viewModel.searchedGroupPreviewState.collect { + if (it.isEmpty()) { + binding.groupRecyclerView.visibility = View.GONE + binding.warningNoGroup.root.visibility = View.VISIBLE + } else { + binding.groupRecyclerView.visibility = View.VISIBLE + binding.warningNoGroup.root.visibility = View.GONE + + } + } + } + + val adapter = SearchCategoryGroupAdapter() + binding.groupRecyclerView.adapter = adapter + binding.groupRecyclerView.layoutManager = LinearLayoutManager(requireContext()) + + repeatWhenUiStarted { + viewModel.searchedGroupState.collect { + adapter.submitData(it) + } + } } } \ No newline at end of file diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryRestaurantFragment.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryRestaurantFragment.kt index da482f58..6b0c2233 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryRestaurantFragment.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryRestaurantFragment.kt @@ -43,10 +43,10 @@ class SearchCategoryRestaurantFragment( viewModel.isForGroup.collect{ isForGroup -> if (isForGroup.not()) { binding.restaurantRecyclerView.visibility = View.GONE - binding.waringNoRestaurant.root.visibility = View.VISIBLE + binding.warningNoRestaurant.root.visibility = View.VISIBLE } else { binding.restaurantRecyclerView.visibility = View.VISIBLE - binding.waringNoRestaurant.root.visibility = View.GONE + binding.warningNoRestaurant.root.visibility = View.GONE } } } diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryGroupAdapter.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryGroupAdapter.kt index e8d5e37a..19be5427 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryGroupAdapter.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryGroupAdapter.kt @@ -6,21 +6,21 @@ import androidx.paging.PagingDataAdapter import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide -import org.gdsc.domain.model.GroupInfo +import org.gdsc.domain.model.GroupPreview import org.gdsc.presentation.R import org.gdsc.presentation.databinding.ItemSearchGroupBinding class SearchCategoryGroupAdapter() : - PagingDataAdapter( + PagingDataAdapter( DiffCallback ) { - companion object DiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: GroupInfo, newItem: GroupInfo): Boolean { + companion object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: GroupPreview, newItem: GroupPreview): Boolean { return oldItem.groupId == newItem.groupId } - override fun areContentsTheSame(oldItem: GroupInfo, newItem: GroupInfo): Boolean { + override fun areContentsTheSame(oldItem: GroupPreview, newItem: GroupPreview): Boolean { return oldItem == newItem } } @@ -28,10 +28,10 @@ class SearchCategoryGroupAdapter() : class SearchCategoryGroupViewHolder( private val binding: ItemSearchGroupBinding, ) : RecyclerView.ViewHolder(binding.root) { - fun bind(item: GroupInfo) { + fun bind(item: GroupPreview) { binding.run { Glide.with(itemView.context) - .load(item.groupProfileImageUrl) + .load("https://picsum.photos/200") .placeholder(R.drawable.base_profile_image) .into(ivGroupImage) diff --git a/presentation/src/main/res/layout/fragment_search_category_all.xml b/presentation/src/main/res/layout/fragment_search_category_all.xml index be857990..b9776597 100644 --- a/presentation/src/main/res/layout/fragment_search_category_all.xml +++ b/presentation/src/main/res/layout/fragment_search_category_all.xml @@ -43,7 +43,7 @@ android:layout_marginHorizontal="@dimen/default_spacing"> @@ -102,7 +102,7 @@ app:layout_constraintEnd_toEndOf="parent"> diff --git a/presentation/src/main/res/layout/fragment_search_category_group.xml b/presentation/src/main/res/layout/fragment_search_category_group.xml index f5cb348d..da6c9225 100644 --- a/presentation/src/main/res/layout/fragment_search_category_group.xml +++ b/presentation/src/main/res/layout/fragment_search_category_group.xml @@ -1,15 +1,27 @@ + android:layout_height="wrap_content" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> - + tools:layout_editor_absoluteX="0dp" /> + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/fragment_search_category_restaurant.xml b/presentation/src/main/res/layout/fragment_search_category_restaurant.xml index d1193312..9650ccd7 100644 --- a/presentation/src/main/res/layout/fragment_search_category_restaurant.xml +++ b/presentation/src/main/res/layout/fragment_search_category_restaurant.xml @@ -65,7 +65,7 @@