Skip to content

Commit

Permalink
[feat/all_search]: 그룹 페이지 제외 검색 페이지 대략적으로 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
soopeach committed Mar 11, 2024
1 parent 449380a commit 1a9d962
Show file tree
Hide file tree
Showing 19 changed files with 935 additions and 249 deletions.
13 changes: 13 additions & 0 deletions domain/src/main/java/org/gdsc/domain/model/GroupInfo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.gdsc.domain.model

data class GroupInfo(
val groupBackgroundImageUrl: String,
val groupId: Int,
val groupIntroduce: String,
val groupName: String,
val groupProfileImageUrl: String,
val isSelected: Boolean,
val memberCnt: Int,
val privateGroup: Boolean,
val restaurantCnt: Int
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.gdsc.domain.model.response

data class GroupResponse(
val groupBackgroundImageUrl: String,
val groupId: Int,
val groupIntroduce: String,
val groupName: String,
val groupProfileImageUrl: String,
val isSelected: Boolean,
val memberCnt: Int,
val privateGroup: Boolean,
val restaurantCnt: Int
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,27 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collect
import org.gdsc.presentation.R
import org.gdsc.presentation.base.CancelViewListener
import org.gdsc.presentation.base.SearchViewListener
import org.gdsc.presentation.databinding.FragmentAllSearchContainerBinding
import org.gdsc.presentation.utils.repeatWhenUiStarted
import org.gdsc.presentation.view.MainActivity
import org.gdsc.presentation.view.allsearch.adapter.SearchCategoryPagerAdapter

@AndroidEntryPoint
class AllSearchContainerFragment: Fragment() {

private var _binding: FragmentAllSearchContainerBinding? = null
private val binding get() = _binding!!

val viewModel: AllSearchViewModel by viewModels()
private val parent by lazy { requireActivity() as MainActivity }

val viewModel: AllSearchViewModel by activityViewModels()

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
Expand All @@ -35,6 +38,8 @@ class AllSearchContainerFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

parent.changeToolbarVisible(false)

binding.searchBar.setSearchViewListener(searchListener)
binding.searchBar.setCancelViewListener(cancelViewListener)

Expand Down Expand Up @@ -84,4 +89,10 @@ class AllSearchContainerFragment: Fragment() {
findNavController().navigateUp()
}
}

override fun onDestroyView() {
_binding = null
parent.changeToolbarVisible(true)
super.onDestroyView()
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
package org.gdsc.presentation.view.allsearch

import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import com.google.android.material.chip.Chip
import dagger.hilt.android.AndroidEntryPoint
import org.gdsc.presentation.R
import org.gdsc.presentation.base.CancelViewListener
import org.gdsc.presentation.base.SearchViewListener
import org.gdsc.presentation.databinding.FragmentAllSearchBinding
import org.gdsc.presentation.utils.repeatWhenUiStarted
import org.gdsc.presentation.view.MainActivity

@AndroidEntryPoint
class AllSearchFragment: Fragment() {
class AllSearchFragment : Fragment() {

private var _binding: FragmentAllSearchBinding? = null
private val binding get() = _binding!!

private val parent by lazy { requireActivity() as MainActivity }

private val viewModel: AllSearchViewModel by activityViewModels()

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
Expand All @@ -29,8 +41,33 @@ class AllSearchFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

parent.changeToolbarTitle("검색")

binding.searchBar.setSearchViewListener(searchListener)
binding.searchBar.setCancelViewListener(cancelViewListener)

binding.tvDelete.setOnClickListener {
viewModel.deleteAllSearchedKeyword()
}

repeatWhenUiStarted {
viewModel.searchedKeywordsState.collect { keywordList ->
binding.cgRecentSearch.removeAllViews()
keywordList.forEach {
if (it.isNotBlank()) {
binding.cgRecentSearch.addView(
newChip(it,
{ keyword ->
viewModel.deleteSearchedKeyword(keyword)
}) { keyword ->
binding.searchBar.editText.setText(keyword)
navigateToResultPage()
}
)
}
}
}
}
}

private val searchListener = object : SearchViewListener {
Expand All @@ -39,7 +76,9 @@ class AllSearchFragment: Fragment() {
override fun onChangeText(text: CharSequence) {}
override fun onSubmitText(text: CharSequence) {
if (text.isEmpty()) return
val action = AllSearchFragmentDirections.actionAllSearchFragmentToAllSearchContainerFragment(text.toString())
viewModel.updateSearchedKeyword(text.toString())
val action =
AllSearchFragmentDirections.actionAllSearchFragmentToAllSearchContainerFragment(text.toString())
findNavController().navigate(action)
}
}
Expand All @@ -49,4 +88,58 @@ class AllSearchFragment: Fragment() {
findNavController().navigateUp()
}
}

private fun newChip(
text: String,
onCloseIconClicked: (String) -> Unit,
onClicked: (String) -> Unit
): Chip {
return Chip(requireContext()).apply {
this.text = text
isCloseIconVisible = true
closeIcon = ContextCompat.getDrawable(
requireContext(),
R.drawable.cancel_icon
)

closeIconTint = ContextCompat.getColorStateList(
requireContext(),
R.color.grey200
)

chipBackgroundColor = ContextCompat.getColorStateList(
requireContext(),
R.color.white
)
chipStrokeColor = ContextCompat.getColorStateList(
requireContext(),
R.color.grey200
)
chipStrokeWidth = 1f

rippleColor = ColorStateList.valueOf(Color.TRANSPARENT)

setOnCloseIconClickListener {
onCloseIconClicked(text)
}

setOnClickListener {
onClicked(text)
}

}

}

private fun navigateToResultPage() {
val action =
AllSearchFragmentDirections.actionAllSearchFragmentToAllSearchContainerFragment(binding.searchBar.text)
findNavController().navigate(action)
}

override fun onDestroyView() {
_binding = null
parent.changeToolbarTitle("")
super.onDestroyView()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,75 @@ import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import androidx.paging.cachedIn
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.gdsc.domain.DrinkPossibility
import org.gdsc.domain.FoodCategory
import org.gdsc.domain.SortType
import org.gdsc.domain.model.Location
import org.gdsc.domain.model.RegisteredRestaurant
import org.gdsc.domain.usecase.GetRestaurantBySearchUseCase
import org.gdsc.domain.usecase.GetRestaurantBySearchWithLimitCountUseCase
import org.gdsc.domain.usecase.user.DeleteSearchedKeywordUseCase
import org.gdsc.domain.usecase.user.GetSearchedKeywordsUseCase
import org.gdsc.domain.usecase.user.InitSearchedKeywordUseCase
import org.gdsc.domain.usecase.user.UpdateSearchedKeywordUseCase
import org.gdsc.presentation.JmtLocationManager
import javax.inject.Inject

@HiltViewModel
class AllSearchViewModel @Inject constructor(
private val locationManager: JmtLocationManager,
private val getRestaurantBySearchUseCase: GetRestaurantBySearchUseCase,
): ViewModel(){
private val getSearchedKeywordsUseCase: GetSearchedKeywordsUseCase,
private val updateSearchedKeywordUseCase: UpdateSearchedKeywordUseCase,
private val deleteSearchedKeywordUseCase: DeleteSearchedKeywordUseCase,
private val initSearchedKeywordUseCase: InitSearchedKeywordUseCase,
private val getRestaurantBySearchWithLimitCountUseCase: GetRestaurantBySearchWithLimitCountUseCase
) : ViewModel() {

init {

viewModelScope.launch {
val location = locationManager.getCurrentLocation()

if (location == null) {
_searchedRestaurantPreviewState.value = emptyList()
} else {

val userLoc = Location(location.longitude.toString(), location.latitude.toString())

_searchedRestaurantPreviewState.value =
getRestaurantBySearchWithLimitCountUseCase(searchKeyword.value, userLoc, 3)
}

}

viewModelScope.launch {
val location = locationManager.getCurrentLocation()

if (location == null) {
_searchedRestaurantState.value = PagingData.empty()
} else {
val userLoc = Location(location.longitude.toString(), location.latitude.toString())

getRestaurantBySearchUseCase(searchKeyword.value, userLoc).distinctUntilChanged()
.collect {
_searchedRestaurantState.value = it
}
}
}

viewModelScope.launch {
val keywords = getSearchedKeywordsUseCase()
if (keywords.isNotEmpty()) {
_searchedKeywordsState.value = keywords
}
}
}

private var _searchKeyword = MutableStateFlow("")
val searchKeyword: StateFlow<String>
Expand All @@ -44,6 +91,20 @@ class AllSearchViewModel @Inject constructor(
val drinkPossibilityState: StateFlow<DrinkPossibility>
get() = _drinkPossibilityState

private var _searchedRestaurantState =
MutableStateFlow<PagingData<RegisteredRestaurant>>(PagingData.empty())
val searchedRestaurantState: StateFlow<PagingData<RegisteredRestaurant>>
get() = _searchedRestaurantState

private var _searchedRestaurantPreviewState =
MutableStateFlow<List<RegisteredRestaurant>>(emptyList())
val searchedRestaurantPreviewState: StateFlow<List<RegisteredRestaurant>>
get() = _searchedRestaurantPreviewState


private var _searchedKeywordsState = MutableStateFlow<List<String>>(emptyList())
val searchedKeywordsState: StateFlow<List<String>>
get() = _searchedKeywordsState

fun setSearchKeyword(keyword: String) {
_searchKeyword.value = keyword
Expand All @@ -60,13 +121,24 @@ class AllSearchViewModel @Inject constructor(
fun setDrinkPossibility(drinkPossibility: DrinkPossibility) {
_drinkPossibilityState.value = drinkPossibility
}
@OptIn(ExperimentalCoroutinesApi::class)
suspend fun registeredPagingData(): Flow<PagingData<RegisteredRestaurant>> {
val location = locationManager.getCurrentLocation() ?: return flowOf(PagingData.empty())
val userLoc = Location(location.longitude.toString(), location.latitude.toString())

return run {
getRestaurantBySearchUseCase(searchKeyword.value, userLoc).distinctUntilChanged()
}.cachedIn(viewModelScope)

fun deleteSearchedKeyword(keyword: String) {
viewModelScope.launch {
_searchedKeywordsState.value = deleteSearchedKeywordUseCase(keyword)
}
}

fun updateSearchedKeyword(keyword: String) {
viewModelScope.launch {
_searchedKeywordsState.value = updateSearchedKeywordUseCase(keyword)
}
}

fun deleteAllSearchedKeyword() {
viewModelScope.launch {
_searchedKeywordsState.value = initSearchedKeywordUseCase()
}
}


}
Loading

0 comments on commit 1a9d962

Please sign in to comment.