Skip to content

Commit

Permalink
Merge pull request #203 from TeamPINGLE/feat-list-search-map
Browse files Browse the repository at this point in the history
[feat] 리스트 뷰 <-> 검색 뷰 <-> 지도 뷰 플로우 구현
  • Loading branch information
jihyunniiii authored Mar 2, 2024
2 parents 06ccaa4 + b865dc1 commit f968fad
Show file tree
Hide file tree
Showing 15 changed files with 335 additions and 63 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.sopt.pingle.presentation.model

import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import org.sopt.pingle.presentation.type.HomeViewType

@Parcelize
data class SearchModel(
val homeViewType: HomeViewType,
val searchWord: String
) : Parcelable
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.sopt.pingle.presentation.type

import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import org.sopt.pingle.R

enum class HomeViewType(
val index: Int,
@DrawableRes val fabDrawableRes: Int,
@StringRes val searchHintRes: Int,
@StringRes val searchDescriptionRes: Int
) {
MAP(
index = 0,
fabDrawableRes = R.drawable.ic_map_list_24,
searchHintRes = R.string.home_view_map_search_hint,
searchDescriptionRes = R.string.home_view_map_search_description
),
MAIN_LIST(
index = 1,
fabDrawableRes = R.drawable.ic_map_map_24,
searchHintRes = R.string.home_view_main_list_search_hint,
searchDescriptionRes = R.string.home_view_main_list_search_description
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class JoinGroupSearchActivity :
finish()
}

(pingleSearchJoinGroupSearch.editText).let { searchEditText ->
(pingleSearchJoinGroupSearch.binding.etSearchPingleEditText).let { searchEditText ->
root.setOnClickListener {
hideKeyboard(searchEditText)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
package org.sopt.pingle.presentation.ui.main.home

import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.viewpager2.widget.ViewPager2
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.sopt.pingle.R
import org.sopt.pingle.databinding.FragmentHomeBinding
import org.sopt.pingle.presentation.model.SearchModel
import org.sopt.pingle.presentation.type.CategoryType
import org.sopt.pingle.presentation.type.HomeViewType
import org.sopt.pingle.presentation.ui.main.home.mainlist.MainListFragment
import org.sopt.pingle.presentation.ui.main.home.map.MapFragment
import org.sopt.pingle.presentation.ui.search.SearchActivity
import org.sopt.pingle.presentation.ui.search.SearchActivity.Companion.SEARCH_WORD
import org.sopt.pingle.util.base.BindingFragment
import org.sopt.pingle.util.component.PingleChip
import org.sopt.pingle.util.view.PingleFragmentStateAdapter
Expand All @@ -25,6 +33,8 @@ class HomeFragment : BindingFragment<FragmentHomeBinding>(R.layout.fragment_home
private val homeViewModel: HomeViewModel by activityViewModels()
private lateinit var fragmentList: ArrayList<Fragment>
private lateinit var fragmentStateAdapter: PingleFragmentStateAdapter
private lateinit var resultLauncher: ActivityResultLauncher<Intent>
private lateinit var stopSearchCallback: OnBackPressedCallback

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Expand All @@ -33,6 +43,8 @@ class HomeFragment : BindingFragment<FragmentHomeBinding>(R.layout.fragment_home
addListeners()
collectData()
setFragmentStateAdapter()
setResultLauncher()
setStopSearchCallback()
}

private fun initLayout() {
Expand All @@ -51,11 +63,27 @@ class HomeFragment : BindingFragment<FragmentHomeBinding>(R.layout.fragment_home
}

tvHomeGroup.text = homeViewModel.getGroupName()

with(pingleSearchHomeSearch.binding.etSearchPingleEditText) {
isFocusable = false
setOnClickListener {
navigateToSearch()
}
}

pingleSearchHomeSearch.binding.ivSearchPingleClear.setOnClickListener {
homeViewModel.clearSearchWord()
navigateToSearch()
}
}
}

private fun addListeners() {
with(binding) {
ivHomeSearch.setOnClickListener {
navigateToSearch()
}

cgHomeCategory.setOnCheckedStateChangeListener { group, checkedIds ->
homeViewModel.setCategory(
category = checkedIds.getOrNull(SINGLE_SELECTION)
Expand All @@ -64,14 +92,12 @@ class HomeFragment : BindingFragment<FragmentHomeBinding>(R.layout.fragment_home
}

fabHomeChange.setOnClickListener {
vpHome.setCurrentItem(
when (vpHome.currentItem) {
MAP_INDEX -> MAIN_LIST_INDEX
MAIN_LIST_INDEX -> MAP_INDEX
else -> vpHome.currentItem
},
false
)
with(homeViewModel) {
when (homeViewType.value) {
HomeViewType.MAP -> setHomeViewType(HomeViewType.MAIN_LIST)
HomeViewType.MAIN_LIST -> setHomeViewType(HomeViewType.MAP)
}
}
}
}
}
Expand All @@ -83,6 +109,28 @@ class HomeFragment : BindingFragment<FragmentHomeBinding>(R.layout.fragment_home
homeViewModel.getPinListWithoutFilter()
}.launchIn(viewLifecycleOwner.lifecycleScope)

homeViewModel.homeViewType.flowWithLifecycle(viewLifecycleOwner.lifecycle).onEach {
with(binding) {
vpHome.setCurrentItem(homeViewModel.homeViewType.value.index, false)
fabHomeChange.setImageResource(homeViewModel.homeViewType.value.fabDrawableRes)
}
}.launchIn(viewLifecycleOwner.lifecycleScope)

homeViewModel.searchWord.flowWithLifecycle(viewLifecycleOwner.lifecycle)
.onEach { searchWord ->
with(binding) {
pingleSearchHomeSearch.binding.etSearchPingleEditText.setText(homeViewModel.searchWord.value)

(searchWord.isNotBlank()).let { isSearching ->
pingleSearchHomeSearch.visibility =
if (isSearching) View.VISIBLE else View.INVISIBLE
tvHomeGroup.visibility = if (isSearching) View.INVISIBLE else View.VISIBLE
ivHomeSearch.visibility = if (isSearching) View.INVISIBLE else View.VISIBLE
if (isSearching) setStopSearchCallback() else stopSearchCallback.remove()
}
}
}.launchIn(viewLifecycleOwner.lifecycleScope)

homeViewModel.markerModelData.flowWithLifecycle(viewLifecycleOwner.lifecycle)
.onEach { markerModelData ->
(markerModelData.first == HomeViewModel.DEFAULT_SELECTED_MARKER_POSITION).let { isMarkerUnselected ->
Expand All @@ -104,22 +152,50 @@ class HomeFragment : BindingFragment<FragmentHomeBinding>(R.layout.fragment_home
with(binding.vpHome) {
adapter = fragmentStateAdapter
isUserInputEnabled = false
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
with(binding) {
(vpHome.currentItem == MAP_INDEX).let { isMap ->
fabHomeChange.setImageResource(if (isMap) R.drawable.ic_map_list_24 else R.drawable.ic_map_map_24)
}
}
}
}

private fun setResultLauncher() {
resultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
if (activityResult.resultCode == RESULT_OK) {
homeViewModel.setSearchWord(
activityResult.data?.getStringExtra(SEARCH_WORD) ?: ""
)
}
})
}
}

private fun setStopSearchCallback() {
stopSearchCallback =
object : OnBackPressedCallback(homeViewModel.searchWord.value.isNotBlank()) {
override fun handleOnBackPressed() {
homeViewModel.clearSearchWord()
navigateToSearch()
}
}

requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,
stopSearchCallback
)
}

private fun navigateToSearch() {
Intent(requireContext(), SearchActivity::class.java).apply {
putExtra(
SEARCH_MODEL,
SearchModel(
homeViewType = homeViewModel.homeViewType.value,
searchWord = homeViewModel.searchWord.value
)
)
resultLauncher.launch(this)
}
}

companion object {
private const val SINGLE_SELECTION = 0
const val MAP_INDEX = 0
const val MAIN_LIST_INDEX = 1
const val SEARCH_MODEL = "searchModel"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import org.sopt.pingle.domain.usecase.GetPingleListUseCase
import org.sopt.pingle.domain.usecase.PostPingleJoinUseCase
import org.sopt.pingle.presentation.model.MarkerModel
import org.sopt.pingle.presentation.type.CategoryType
import org.sopt.pingle.presentation.type.HomeViewType
import org.sopt.pingle.presentation.type.MainListOrderType
import org.sopt.pingle.util.view.UiState

Expand All @@ -34,6 +35,12 @@ class HomeViewModel @Inject constructor(
private val _category = MutableStateFlow<CategoryType?>(null)
val category get() = _category.asStateFlow()

private val _homeViewType = MutableStateFlow<HomeViewType>(HomeViewType.MAP)
val homeViewType get() = _homeViewType.asStateFlow()

private var _searchWord = MutableStateFlow("")
val searchWord get() = _searchWord.asStateFlow()

private val _pinEntityListState = MutableStateFlow<UiState<List<PinEntity>>>(UiState.Empty)
val pinEntityListState get() = _pinEntityListState.asStateFlow()

Expand All @@ -56,13 +63,25 @@ class HomeViewModel @Inject constructor(
private val _pingleDeleteState = MutableSharedFlow<UiState<Unit?>>()
val pingleDeleteState get() = _pingleDeleteState.asSharedFlow()

private val _mainListOrderType = MutableStateFlow<MainListOrderType>(MainListOrderType.NEW)
private val _mainListOrderType = MutableStateFlow(MainListOrderType.NEW)
val mainListOrderType get() = _mainListOrderType.asStateFlow()

fun setCategory(category: CategoryType?) {
_category.value = category
}

fun setHomeViewType(homeViewType: HomeViewType) {
_homeViewType.value = homeViewType
}

fun setSearchWord(searchWord: String) {
_searchWord.value = searchWord
}

fun clearSearchWord() {
_searchWord.value = ""
}

private fun setMarkerModelListIsSelected(position: Int) {
_markerModelData.value.second[position].isSelected.set(!_markerModelData.value.second[position].isSelected.get())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ class MainListFragment : BindingFragment<FragmentMainListBinding>(R.layout.fragm
// TODO 서버통신 구현 후 collectData 함수로 해당 로직 이동
with(homeViewModel.dummyPingleList) {
binding.tvMainListEmpty.visibility = if (isEmpty()) View.VISIBLE else View.INVISIBLE
binding.tvMainListEmpty.text = stringOf(R.string.main_list_empty_pingle)
}
}

Expand Down Expand Up @@ -102,6 +101,28 @@ class MainListFragment : BindingFragment<FragmentMainListBinding>(R.layout.fragm
}

private fun collectData() {
homeViewModel.searchWord.flowWithLifecycle(viewLifecycleOwner.lifecycle)
.onEach { searchWord ->
(searchWord.isNotBlank()).let { isSearching ->
with(binding.tvMainListSearchCount) {
visibility = if (isSearching) View.VISIBLE else View.INVISIBLE
text = getString(
R.string.main_list_search_count,
homeViewModel.dummyPingleList.size
)
}

binding.tvMainListEmpty.text =
if (isSearching) {
stringOf(R.string.main_list_empty_search)
} else {
stringOf(
R.string.main_list_empty_pingle
)
}
}
}.launchIn(viewLifecycleOwner.lifecycleScope)

homeViewModel.mainListOrderType.flowWithLifecycle(viewLifecycleOwner.lifecycle)
.onEach { mainListOrderType ->
binding.tvMainListOrderType.text =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ class PlanLocationFragment :
}

private fun addListeners() {
(binding.pingleSearchPlanLocation.editText).let { searchEditText ->
(binding.pingleSearchPlanLocation.binding.etSearchPingleEditText).let { searchEditText ->
binding.root.setOnClickListener {
requireContext().hideKeyboard(searchEditText)
}

searchEditText.setOnKeyListener(
View.OnKeyListener { _, keyCode, event ->
if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_UP) {
if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN) {
if (searchEditText.text.isNotBlank()) {
planLocationViewModel.getPlanLocationList(searchEditText.text.toString())
}
Expand Down
Loading

0 comments on commit f968fad

Please sign in to comment.