From 8d1066b0ef6036f1ee5710293e64ffb117ff85d0 Mon Sep 17 00:00:00 2001 From: swit-jim Date: Mon, 4 Mar 2024 19:02:47 +0900 Subject: [PATCH 01/12] =?UTF-8?q?[feat/all=5Fsearch]:=20JmtSearchEditText?= =?UTF-8?q?=20=EB=A6=AC=EC=8A=A4=EB=84=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/base/SearchViewListener.kt | 12 ++ .../view/custom/JmtSearchEditText.kt | 150 +++++++++++++++++- .../main/res/layout/jmt_search_edit_text.xml | 5 +- 3 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 presentation/src/main/java/org/gdsc/presentation/base/SearchViewListener.kt diff --git a/presentation/src/main/java/org/gdsc/presentation/base/SearchViewListener.kt b/presentation/src/main/java/org/gdsc/presentation/base/SearchViewListener.kt new file mode 100644 index 00000000..bd2bacfd --- /dev/null +++ b/presentation/src/main/java/org/gdsc/presentation/base/SearchViewListener.kt @@ -0,0 +1,12 @@ +package org.gdsc.presentation.base + +interface SearchViewListener { + fun changeFocus(focus: Boolean) + fun onChangeText(text: CharSequence) + fun onSubmitText(text: CharSequence) + fun onSearchClear() +} + +interface CancelViewListener{ + fun onCancel() +} \ No newline at end of file diff --git a/presentation/src/main/java/org/gdsc/presentation/view/custom/JmtSearchEditText.kt b/presentation/src/main/java/org/gdsc/presentation/view/custom/JmtSearchEditText.kt index 7150e7fe..6871b805 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/custom/JmtSearchEditText.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/custom/JmtSearchEditText.kt @@ -4,12 +4,24 @@ import android.annotation.SuppressLint import androidx.constraintlayout.widget.ConstraintLayout import android.content.Context import android.util.AttributeSet +import android.util.Log +import android.view.KeyEvent import android.view.LayoutInflater +import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager +import androidx.core.view.isVisible +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import org.gdsc.presentation.R +import org.gdsc.presentation.base.CancelViewListener +import org.gdsc.presentation.base.SearchViewListener import org.gdsc.presentation.databinding.JmtSearchEditTextBinding import org.gdsc.presentation.utils.fadeIn import org.gdsc.presentation.utils.fadeOut +import kotlin.coroutines.CoroutineContext @SuppressLint("ClickableViewAccessibility") class JmtSearchEditText(context: Context, attrs: AttributeSet) : ConstraintLayout(context, attrs) { @@ -19,6 +31,11 @@ class JmtSearchEditText(context: Context, attrs: AttributeSet) : ConstraintLayou val text get() = binding.searchEditText.text.toString() val editText get() = binding.searchEditText + private var searchViewListener: SearchViewListener? = null + private var cancelViewListener: CancelViewListener? = null + private var afterTextChanged: ((String?) -> Unit)? = null + private var autoSubmit = false + init { context.theme.obtainStyledAttributes( @@ -40,6 +57,8 @@ class JmtSearchEditText(context: Context, attrs: AttributeSet) : ConstraintLayou setCancel() setAnimation() + setEditorKeyListener() + setTextChangedListener() } @@ -49,10 +68,10 @@ class JmtSearchEditText(context: Context, attrs: AttributeSet) : ConstraintLayou binding.searchEditText.clearFocus() hideKeyboardFromEditText() binding.searchIcon.fadeIn() + cancelViewListener?.onCancel() } } - private fun setAnimation() { binding.searchEditText.setOnFocusChangeListener { _, hasFocus -> @@ -67,10 +86,139 @@ class JmtSearchEditText(context: Context, attrs: AttributeSet) : ConstraintLayou } + private fun setEditorKeyListener() { + binding.searchEditText.setOnEditorActionListener { v, actionID, event -> + + if (actionID == EditorInfo.IME_ACTION_SEARCH) { + clearFocus() + hideKeyboardFromEditText() + searchViewListener?.onSubmitText(v.text.toString()) + true + } else { + false + } + } + + binding.searchEditText.onKey { _, keyCode, _ -> + + if (keyCode == KeyEvent.KEYCODE_ENTER) { + clearFocus() + hideKeyboardFromEditText() + searchViewListener?.onSubmitText(binding.searchEditText.text.toString()) + } + } + } + + + private fun setTextChangedListener() { + binding.searchEditText.textChangedListener { + onTextChanged { charSequence, _, _, _ -> + searchViewListener?.onChangeText(charSequence ?: "") + if (autoSubmit) { + searchViewListener?.onSubmitText(binding.searchEditText.text.toString()) + autoSubmit = false + } + } + + afterTextChanged { + afterTextChanged?.invoke(it.toString()) + } + + } + } private fun hideKeyboardFromEditText() { val inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager inputMethodManager.hideSoftInputFromWindow(binding.searchEditText.windowToken, 0) } + fun setSearchViewListener(searchViewListener: SearchViewListener) { + this.searchViewListener = searchViewListener + } + + fun setCancelViewListener(cancelViewListener: CancelViewListener) { + this.cancelViewListener = cancelViewListener + } + + fun setSearchText(text: String) { + binding.searchEditText.setText(text) + } + + fun setAfterTextChanged(action: (String?) -> Unit) { + afterTextChanged = action + } + + fun android.view.View.onKey( + context: CoroutineContext = Dispatchers.Main, + returnValue: Boolean = false, + handler: suspend CoroutineScope.(v: android.view.View, keyCode: Int, event: android.view.KeyEvent?) -> Unit + ) { + setOnKeyListener { v, keyCode, event -> + GlobalScope.launch(context, CoroutineStart.DEFAULT) { + handler(v, keyCode, event) + } + returnValue + } + } + + fun android.widget.TextView.textChangedListener( + context: CoroutineContext = Dispatchers.Main, + init: __TextWatcher.() -> Unit + ) { + val listener = __TextWatcher(context) + listener.init() + addTextChangedListener(listener) + } } + +class __TextWatcher(private val context: CoroutineContext) : android.text.TextWatcher { + + private var _beforeTextChanged: (suspend CoroutineScope.(CharSequence?, Int, Int, Int) -> Unit)? = null + + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { + val handler = _beforeTextChanged ?: return + GlobalScope.launch(context) { + handler(s, start, count, after) + } + } + + fun beforeTextChanged( + listener: suspend CoroutineScope.(CharSequence?, Int, Int, Int) -> Unit + ) { + _beforeTextChanged = listener + } + + private var _onTextChanged: (suspend CoroutineScope.(CharSequence?, Int, Int, Int) -> Unit)? = null + + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + val handler = _onTextChanged ?: return + GlobalScope.launch(context) { + handler(s, start, before, count) + } + } + + fun onTextChanged( + listener: suspend CoroutineScope.(CharSequence?, Int, Int, Int) -> Unit + ) { + _onTextChanged = listener + } + + private var _afterTextChanged: (suspend CoroutineScope.(android.text.Editable?) -> Unit)? = null + + + override fun afterTextChanged(s: android.text.Editable?) { + val handler = _afterTextChanged ?: return + GlobalScope.launch(context) { + handler(s) + } + } + + fun afterTextChanged( + listener: suspend CoroutineScope.(android.text.Editable?) -> Unit + ) { + _afterTextChanged = listener + } + +} \ No newline at end of file diff --git a/presentation/src/main/res/layout/jmt_search_edit_text.xml b/presentation/src/main/res/layout/jmt_search_edit_text.xml index de18161b..8e8946f4 100644 --- a/presentation/src/main/res/layout/jmt_search_edit_text.xml +++ b/presentation/src/main/res/layout/jmt_search_edit_text.xml @@ -19,7 +19,7 @@ android:id="@+id/search_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="12dp" + android:layout_marginStart="@dimen/half_element_spacing" android:layout_marginVertical="19dp" android:src="@drawable/search_text_icon" app:layout_constraintStart_toStartOf="parent" @@ -39,8 +39,9 @@ android:textColorHint="@color/grey200" android:textColor="@color/grey900" android:textCursorDrawable="@null" + android:imeOptions="actionSearch" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toStartOf="@+id/clear_icon" app:layout_constraintStart_toEndOf="@id/search_icon" app:layout_constraintTop_toTopOf="parent" android:inputType="text" From 3b37d1f81d5d1704023a5c6f8690d9917909bd94 Mon Sep 17 00:00:00 2001 From: swit-jim Date: Mon, 4 Mar 2024 19:03:55 +0900 Subject: [PATCH 02/12] =?UTF-8?q?[feat/all=5Fsearch]:=20jmtSearchEditText?= =?UTF-8?q?=20Clear=20=EB=B2=84=ED=8A=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/view/custom/JmtSearchEditText.kt | 10 ++++++++++ presentation/src/main/res/drawable/ic_x_clear.xml | 13 +++++++++++++ .../src/main/res/layout/jmt_search_edit_text.xml | 9 +++++++++ 3 files changed, 32 insertions(+) create mode 100644 presentation/src/main/res/drawable/ic_x_clear.xml diff --git a/presentation/src/main/java/org/gdsc/presentation/view/custom/JmtSearchEditText.kt b/presentation/src/main/java/org/gdsc/presentation/view/custom/JmtSearchEditText.kt index 6871b805..6f921349 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/custom/JmtSearchEditText.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/custom/JmtSearchEditText.kt @@ -56,6 +56,7 @@ class JmtSearchEditText(context: Context, attrs: AttributeSet) : ConstraintLayou addView(binding.root) setCancel() + setClear() setAnimation() setEditorKeyListener() setTextChangedListener() @@ -72,6 +73,14 @@ class JmtSearchEditText(context: Context, attrs: AttributeSet) : ConstraintLayou } } + private fun setClear() { + binding.clearIcon.setOnClickListener { + binding.searchEditText.text?.clear() + searchViewListener?.onSearchClear() + } + } + + private fun setAnimation() { binding.searchEditText.setOnFocusChangeListener { _, hasFocus -> @@ -113,6 +122,7 @@ class JmtSearchEditText(context: Context, attrs: AttributeSet) : ConstraintLayou private fun setTextChangedListener() { binding.searchEditText.textChangedListener { onTextChanged { charSequence, _, _, _ -> + binding.clearIcon.isVisible = !charSequence.isNullOrEmpty() searchViewListener?.onChangeText(charSequence ?: "") if (autoSubmit) { searchViewListener?.onSubmitText(binding.searchEditText.text.toString()) diff --git a/presentation/src/main/res/drawable/ic_x_clear.xml b/presentation/src/main/res/drawable/ic_x_clear.xml new file mode 100644 index 00000000..e1dd73ef --- /dev/null +++ b/presentation/src/main/res/drawable/ic_x_clear.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/presentation/src/main/res/layout/jmt_search_edit_text.xml b/presentation/src/main/res/layout/jmt_search_edit_text.xml index 8e8946f4..ec6239ae 100644 --- a/presentation/src/main/res/layout/jmt_search_edit_text.xml +++ b/presentation/src/main/res/layout/jmt_search_edit_text.xml @@ -47,6 +47,15 @@ android:inputType="text" /> + Date: Mon, 4 Mar 2024 19:04:11 +0900 Subject: [PATCH 03/12] =?UTF-8?q?[feat/all=5Fsearch]:=20=EC=A0=84=EC=B2=B4?= =?UTF-8?q?=20=EA=B2=80=EC=83=89=20=ED=99=94=EB=A9=B4=20=EC=84=9C=EC=B9=98?= =?UTF-8?q?=EB=B0=94=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../allsearch/AllSearchContainerFragment.kt | 51 ++++++++++++++++++ .../view/allsearch/AllSearchFragment.kt | 52 +++++++++++++++++++ .../main/res/layout/fragment_all_search.xml | 17 ++++++ .../layout/fragment_all_search_container.xml | 17 ++++++ .../main/res/navigation/main_nav_graph.xml | 19 +++++-- 5 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchContainerFragment.kt create mode 100644 presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchFragment.kt create mode 100644 presentation/src/main/res/layout/fragment_all_search.xml create mode 100644 presentation/src/main/res/layout/fragment_all_search_container.xml diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchContainerFragment.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchContainerFragment.kt new file mode 100644 index 00000000..16dbae81 --- /dev/null +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchContainerFragment.kt @@ -0,0 +1,51 @@ +package org.gdsc.presentation.view.allsearch + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import dagger.hilt.android.AndroidEntryPoint +import org.gdsc.presentation.base.CancelViewListener +import org.gdsc.presentation.base.SearchViewListener +import org.gdsc.presentation.databinding.FragmentAllSearchContainerBinding + +@AndroidEntryPoint +class AllSearchContainerFragment: Fragment() { + + private var _binding: FragmentAllSearchContainerBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentAllSearchContainerBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.searchBar.setSearchViewListener(searchListener) + binding.searchBar.setCancelViewListener(cancelViewListener) + + arguments?.getString("keyword")?.let { + binding.searchBar.setSearchText(it) + } + } + private val searchListener = object : SearchViewListener { + override fun onSearchClear() {} + override fun changeFocus(focus: Boolean) {} + override fun onChangeText(text: CharSequence) {} + override fun onSubmitText(text: CharSequence) { + if (text.isEmpty()) return + } + } + private val cancelViewListener = object : CancelViewListener { + override fun onCancel() { + findNavController().navigateUp() + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchFragment.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchFragment.kt new file mode 100644 index 00000000..39e017ac --- /dev/null +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchFragment.kt @@ -0,0 +1,52 @@ +package org.gdsc.presentation.view.allsearch + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import dagger.hilt.android.AndroidEntryPoint +import org.gdsc.presentation.base.CancelViewListener +import org.gdsc.presentation.base.SearchViewListener +import org.gdsc.presentation.databinding.FragmentAllSearchBinding + +@AndroidEntryPoint +class AllSearchFragment: Fragment() { + + private var _binding: FragmentAllSearchBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentAllSearchBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.searchBar.setSearchViewListener(searchListener) + binding.searchBar.setCancelViewListener(cancelViewListener) + } + + private val searchListener = object : SearchViewListener { + override fun onSearchClear() {} + override fun changeFocus(focus: Boolean) {} + override fun onChangeText(text: CharSequence) {} + override fun onSubmitText(text: CharSequence) { + if (text.isEmpty()) return + val action = AllSearchFragmentDirections.actionAllSearchFragmentToAllSearchContainerFragment(text.toString()) + findNavController().navigate(action) + } + } + + private val cancelViewListener = object : CancelViewListener { + override fun onCancel() { + findNavController().navigateUp() + } + } +} \ No newline at end of file diff --git a/presentation/src/main/res/layout/fragment_all_search.xml b/presentation/src/main/res/layout/fragment_all_search.xml new file mode 100644 index 00000000..4297ce00 --- /dev/null +++ b/presentation/src/main/res/layout/fragment_all_search.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/fragment_all_search_container.xml b/presentation/src/main/res/layout/fragment_all_search_container.xml new file mode 100644 index 00000000..4297ce00 --- /dev/null +++ b/presentation/src/main/res/layout/fragment_all_search_container.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/navigation/main_nav_graph.xml b/presentation/src/main/res/navigation/main_nav_graph.xml index 78ea478a..1a6d61e9 100644 --- a/presentation/src/main/res/navigation/main_nav_graph.xml +++ b/presentation/src/main/res/navigation/main_nav_graph.xml @@ -16,12 +16,21 @@ + android:name="org.gdsc.presentation.view.allsearch.AllSearchFragment" + android:label="all_search_fragment" + tools:layout="@layout/fragment_all_search" > + android:id="@+id/action_all_search_fragment_to_all_search_container_fragment" + app:destination="@+id/all_search_container_fragment"/> + + + Date: Mon, 4 Mar 2024 19:22:58 +0900 Subject: [PATCH 04/12] =?UTF-8?q?[feat/all=5Fsearch]:=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=20=EA=B2=B0=EA=B3=BC=20=ED=99=94=EB=A9=B4=20ViewPager=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../allsearch/AllSearchContainerFragment.kt | 20 ++++++++++++++ .../allsearch/SearchCategoryAllFragment.kt | 26 +++++++++++++++++++ .../allsearch/SearchCategoryGroupFragment.kt | 26 +++++++++++++++++++ .../allsearch/SearchCategoryPagerAdapter.kt | 24 +++++++++++++++++ .../SearchCategoryRestaurantFragment.kt | 26 +++++++++++++++++++ .../layout/fragment_all_search_container.xml | 19 ++++++++++++++ .../layout/fragment_search_category_all.xml | 15 +++++++++++ .../layout/fragment_search_category_group.xml | 15 +++++++++++ .../fragment_search_category_restaurant.xml | 15 +++++++++++ presentation/src/main/res/values/strings.xml | 5 ++++ 10 files changed, 191 insertions(+) create mode 100644 presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryAllFragment.kt create mode 100644 presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryGroupFragment.kt create mode 100644 presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryPagerAdapter.kt create mode 100644 presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryRestaurantFragment.kt create mode 100644 presentation/src/main/res/layout/fragment_search_category_all.xml create mode 100644 presentation/src/main/res/layout/fragment_search_category_group.xml create mode 100644 presentation/src/main/res/layout/fragment_search_category_restaurant.xml diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchContainerFragment.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchContainerFragment.kt index 16dbae81..989c9911 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchContainerFragment.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchContainerFragment.kt @@ -6,7 +6,9 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController +import com.google.android.material.tabs.TabLayoutMediator 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.FragmentAllSearchContainerBinding @@ -34,7 +36,25 @@ class AllSearchContainerFragment: Fragment() { arguments?.getString("keyword")?.let { binding.searchBar.setSearchText(it) } + + setPager() + setTabLayout() + } + + private fun setPager() { + binding.searchCategoryPager.adapter = SearchCategoryPagerAdapter(this) } + + private fun setTabLayout() { + TabLayoutMediator(binding.tabLayout, binding.searchCategoryPager) { tab, position -> + when (position) { + SearchCategoryPagerAdapter.CATEGORY_ALL -> tab.text = getString(R.string.search_category_all) + SearchCategoryPagerAdapter.CATEGORY_RESTAURANT -> tab.text = getString(R.string.search_category_restaurant) + SearchCategoryPagerAdapter.CATEGORY_GROUP -> tab.text = getString(R.string.search_category_group) + } + }.attach() + } + private val searchListener = object : SearchViewListener { override fun onSearchClear() {} override fun changeFocus(focus: Boolean) {} 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 new file mode 100644 index 00000000..c5e5411f --- /dev/null +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryAllFragment.kt @@ -0,0 +1,26 @@ +package org.gdsc.presentation.view.allsearch + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import org.gdsc.presentation.databinding.FragmentSearchCategoryAllBinding + +class SearchCategoryAllFragment: Fragment() { + + private var _binding: FragmentSearchCategoryAllBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentSearchCategoryAllBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..4e4c9e83 --- /dev/null +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryGroupFragment.kt @@ -0,0 +1,26 @@ +package org.gdsc.presentation.view.allsearch + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import org.gdsc.presentation.databinding.FragmentSearchCategoryGroupBinding + +class SearchCategoryGroupFragment: Fragment() { + + private var _binding: FragmentSearchCategoryGroupBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentSearchCategoryGroupBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + } +} \ No newline at end of file diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryPagerAdapter.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryPagerAdapter.kt new file mode 100644 index 00000000..4b86c891 --- /dev/null +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryPagerAdapter.kt @@ -0,0 +1,24 @@ +package org.gdsc.presentation.view.allsearch + +import androidx.fragment.app.Fragment +import androidx.viewpager2.adapter.FragmentStateAdapter + +class SearchCategoryPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) { + override fun getItemCount() = SEARCH_CATEGORY_PAGER_SIZE + + override fun createFragment(position: Int): Fragment { + return when (position) { + CATEGORY_RESTAURANT -> SearchCategoryRestaurantFragment() + CATEGORY_GROUP -> SearchCategoryGroupFragment() + else -> SearchCategoryAllFragment() + } + } + + companion object { + private const val SEARCH_CATEGORY_PAGER_SIZE = 3 + + const val CATEGORY_ALL = 0 + const val CATEGORY_RESTAURANT = 1 + const val CATEGORY_GROUP = 2 + } +} \ 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 new file mode 100644 index 00000000..be418ce0 --- /dev/null +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryRestaurantFragment.kt @@ -0,0 +1,26 @@ +package org.gdsc.presentation.view.allsearch + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import org.gdsc.presentation.databinding.FragmentSearchCategoryRestaurantBinding + +class SearchCategoryRestaurantFragment: Fragment() { + + private var _binding: FragmentSearchCategoryRestaurantBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentSearchCategoryRestaurantBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + } +} \ No newline at end of file diff --git a/presentation/src/main/res/layout/fragment_all_search_container.xml b/presentation/src/main/res/layout/fragment_all_search_container.xml index 4297ce00..8ffde272 100644 --- a/presentation/src/main/res/layout/fragment_all_search_container.xml +++ b/presentation/src/main/res/layout/fragment_all_search_container.xml @@ -13,5 +13,24 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/fragment_search_category_all.xml b/presentation/src/main/res/layout/fragment_search_category_all.xml new file mode 100644 index 00000000..a7f377f7 --- /dev/null +++ b/presentation/src/main/res/layout/fragment_search_category_all.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/fragment_search_category_group.xml b/presentation/src/main/res/layout/fragment_search_category_group.xml new file mode 100644 index 00000000..f5cb348d --- /dev/null +++ b/presentation/src/main/res/layout/fragment_search_category_group.xml @@ -0,0 +1,15 @@ + + + + + \ 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 new file mode 100644 index 00000000..e625e6a1 --- /dev/null +++ b/presentation/src/main/res/layout/fragment_search_category_restaurant.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index e47b29bc..0fec161b 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -46,6 +46,11 @@ 선택 선택하기 등록하기 + + + 전체 + 맛집 + 그룹 내 위치에서 %s 위치 정보를 가져올 수 없습니다. From f2d04c0564fc5ebf19f2ed8231255b9ef0c90ab6 Mon Sep 17 00:00:00 2001 From: DDUDDY Date: Mon, 4 Mar 2024 21:44:44 +0900 Subject: [PATCH 05/12] =?UTF-8?q?[feat/all=5Fsearch]:=20RestaurantSearchMa?= =?UTF-8?q?pRequest=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gdsc/data/database/RestaurantByMapPagingSource.kt | 10 +++------- .../java/org/gdsc/data/database/RestaurantMediator.kt | 6 +++--- .../org/gdsc/data/datasource/RestaurantDataSource.kt | 2 +- .../gdsc/data/datasource/RestaurantDataSourceImpl.kt | 10 +++++----- .../main/java/org/gdsc/data/network/RestaurantAPI.kt | 6 +++--- ...tSearchMapRequest.kt => RestaurantSearchRequest.kt} | 6 ++++-- .../org/gdsc/domain/repository/RestaurantRepository.kt | 1 - .../domain/usecase/GetRegisteredRestaurantUseCase.kt | 2 -- .../view/mypage/viewmodel/MyPageViewModel.kt | 3 --- 9 files changed, 19 insertions(+), 27 deletions(-) rename domain/src/main/java/org/gdsc/domain/model/request/{RestaurantSearchMapRequest.kt => RestaurantSearchRequest.kt} (76%) diff --git a/data/src/main/java/org/gdsc/data/database/RestaurantByMapPagingSource.kt b/data/src/main/java/org/gdsc/data/database/RestaurantByMapPagingSource.kt index c8916526..2236af2c 100644 --- a/data/src/main/java/org/gdsc/data/database/RestaurantByMapPagingSource.kt +++ b/data/src/main/java/org/gdsc/data/database/RestaurantByMapPagingSource.kt @@ -1,18 +1,14 @@ package org.gdsc.data.database -import android.util.Log import androidx.paging.PagingSource import androidx.paging.PagingState -import androidx.room.PrimaryKey -import kotlinx.coroutines.CoroutineScope import org.gdsc.data.model.RegisteredRestaurantResponse -import org.gdsc.data.model.Response import org.gdsc.data.network.RestaurantAPI -import org.gdsc.domain.model.request.RestaurantSearchMapRequest +import org.gdsc.domain.model.request.RestaurantSearchRequest class RestaurantByMapPagingSource( private val api: RestaurantAPI, - private val restaurantSearchMapRequest: RestaurantSearchMapRequest, + private val restaurantSearchRequest: RestaurantSearchRequest, ): PagingSource() { override suspend fun load(params: LoadParams): LoadResult { val page = params.key ?: 1 @@ -20,7 +16,7 @@ class RestaurantByMapPagingSource( val items = api.getRestaurantLocationInfoByMap( page = page, size = params.loadSize, - restaurantSearchMapRequest = restaurantSearchMapRequest + restaurantSearchRequest = restaurantSearchRequest ) LoadResult.Page( data = items.data.restaurants, diff --git a/data/src/main/java/org/gdsc/data/database/RestaurantMediator.kt b/data/src/main/java/org/gdsc/data/database/RestaurantMediator.kt index ff8455d8..a0bb3c7b 100644 --- a/data/src/main/java/org/gdsc/data/database/RestaurantMediator.kt +++ b/data/src/main/java/org/gdsc/data/database/RestaurantMediator.kt @@ -6,14 +6,14 @@ import androidx.paging.PagingState import androidx.paging.RemoteMediator import androidx.room.withTransaction import org.gdsc.data.network.RestaurantAPI -import org.gdsc.domain.model.request.RestaurantSearchMapRequest +import org.gdsc.domain.model.request.RestaurantSearchRequest import retrofit2.HttpException import java.io.IOException @OptIn(ExperimentalPagingApi::class) class RestaurantMediator( private val userId: Int, - private val restaurantSearchMapRequest: RestaurantSearchMapRequest, + private val restaurantSearchRequest: RestaurantSearchRequest, private val db: RestaurantDatabase, private val api: RestaurantAPI, ): RemoteMediator() { @@ -47,7 +47,7 @@ class RestaurantMediator( userId = userId, page = currentPageNumber, size = state.config.pageSize, - restaurantSearchMapRequest = restaurantSearchMapRequest + restaurantSearchRequest = restaurantSearchRequest ) val repos = response.data diff --git a/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSource.kt b/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSource.kt index 8bdaabd1..ab991a93 100644 --- a/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSource.kt +++ b/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSource.kt @@ -13,7 +13,6 @@ import org.gdsc.domain.model.RestaurantLocationInfo import org.gdsc.domain.model.UserLocation import org.gdsc.domain.model.request.ModifyRestaurantInfoRequest import org.gdsc.domain.model.request.RestaurantRegistrationRequest -import org.gdsc.domain.model.request.RestaurantSearchMapRequest import org.gdsc.domain.model.response.RestaurantInfoResponse interface RestaurantDataSource { @@ -41,4 +40,5 @@ interface RestaurantDataSource { userLocation: Location?, startLocation: Location?, endLocation: Location?, sortType: SortType, foodCategory: FoodCategory?, drinkPossibility: DrinkPossibility? ): Flow> + suspend fun getRegisteredRestaurantsBySearch(keyword: String?, userLocation: Location?): Flow> } \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSourceImpl.kt b/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSourceImpl.kt index 93ee8b55..1b4b01dc 100644 --- a/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSourceImpl.kt +++ b/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSourceImpl.kt @@ -31,7 +31,7 @@ import org.gdsc.domain.model.RestaurantLocationInfo import org.gdsc.domain.model.UserLocation import org.gdsc.domain.model.request.ModifyRestaurantInfoRequest import org.gdsc.domain.model.request.RestaurantRegistrationRequest -import org.gdsc.domain.model.request.RestaurantSearchMapRequest +import org.gdsc.domain.model.request.RestaurantSearchRequest import org.gdsc.domain.model.response.RestaurantInfoResponse import retrofit2.HttpException import javax.inject.Inject @@ -139,10 +139,10 @@ class RestaurantDataSourceImpl @Inject constructor( isCanDrinkLiquor = isCanDrinkLiquor, ) - val restaurantSearchMapRequest = RestaurantSearchMapRequest(filter, locationData) + val restaurantSearchRequest = RestaurantSearchRequest(filter, locationData) val mediator = RestaurantMediator( userId = userId, - restaurantSearchMapRequest = restaurantSearchMapRequest, + restaurantSearchRequest = restaurantSearchRequest, db = db, api = restaurantAPI, ) @@ -176,7 +176,7 @@ class RestaurantDataSourceImpl @Inject constructor( override suspend fun getRestaurantsByMap( userLocation: Location?, startLocation: Location?, endLocation: Location?, sortType: SortType, foodCategory: FoodCategory?, drinkPossibility: DrinkPossibility? ): Flow> { - val restaurantSearchMapRequest = RestaurantSearchMapRequest( + val restaurantSearchRequest = RestaurantSearchRequest( userLocation = userLocation, startLocation = startLocation, endLocation = endLocation, @@ -200,7 +200,7 @@ class RestaurantDataSourceImpl @Inject constructor( )) { RestaurantByMapPagingSource( restaurantAPI, - restaurantSearchMapRequest + restaurantSearchRequest ) }.flow.cachedIn(coroutineScope) } diff --git a/data/src/main/java/org/gdsc/data/network/RestaurantAPI.kt b/data/src/main/java/org/gdsc/data/network/RestaurantAPI.kt index 1c0fc8e2..2b4f630c 100644 --- a/data/src/main/java/org/gdsc/data/network/RestaurantAPI.kt +++ b/data/src/main/java/org/gdsc/data/network/RestaurantAPI.kt @@ -6,7 +6,7 @@ import org.gdsc.data.database.RegisteredRestaurantPaging import org.gdsc.data.model.Response import org.gdsc.domain.model.RestaurantLocationInfo import org.gdsc.domain.model.UserLocation -import org.gdsc.domain.model.request.RestaurantSearchMapRequest +import org.gdsc.domain.model.request.RestaurantSearchRequest import org.gdsc.domain.model.request.ModifyRestaurantInfoRequest import org.gdsc.domain.model.response.RestaurantInfoResponse import org.gdsc.domain.model.response.RestaurantRegistrationResponse @@ -59,7 +59,7 @@ interface RestaurantAPI { @Query("page") page: Int? = null, @Query("size") size: Int? = null, @Query("sort") sort: String? = null, - @Body restaurantSearchMapRequest: RestaurantSearchMapRequest, + @Body restaurantSearchRequest: RestaurantSearchRequest, ): Response @PUT("api/v1/restaurant") @@ -72,7 +72,7 @@ interface RestaurantAPI { @Query("page") page: Int? = null, @Query("size") size: Int? = null, @Query("sort") sort: Array? = null, - @Body restaurantSearchMapRequest: RestaurantSearchMapRequest, + @Body restaurantSearchRequest: RestaurantSearchRequest, ): Response } \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/model/request/RestaurantSearchMapRequest.kt b/domain/src/main/java/org/gdsc/domain/model/request/RestaurantSearchRequest.kt similarity index 76% rename from domain/src/main/java/org/gdsc/domain/model/request/RestaurantSearchMapRequest.kt rename to domain/src/main/java/org/gdsc/domain/model/request/RestaurantSearchRequest.kt index 59750c38..d35e2750 100644 --- a/domain/src/main/java/org/gdsc/domain/model/request/RestaurantSearchMapRequest.kt +++ b/domain/src/main/java/org/gdsc/domain/model/request/RestaurantSearchRequest.kt @@ -4,13 +4,15 @@ import com.google.gson.annotations.SerializedName import org.gdsc.domain.model.Filter import org.gdsc.domain.model.Location -data class RestaurantSearchMapRequest( +data class RestaurantSearchRequest( @SerializedName("filter") - val filter: Filter, + val filter: Filter? = null, @SerializedName("userLocation") val userLocation: Location? = null, @SerializedName("startLocation") val startLocation: Location? = null, @SerializedName("endLocation") val endLocation: Location? = null, + @SerializedName("keyword") + val keyword: String? = null, ) diff --git a/domain/src/main/java/org/gdsc/domain/repository/RestaurantRepository.kt b/domain/src/main/java/org/gdsc/domain/repository/RestaurantRepository.kt index c2b24c7c..1b6c99c4 100644 --- a/domain/src/main/java/org/gdsc/domain/repository/RestaurantRepository.kt +++ b/domain/src/main/java/org/gdsc/domain/repository/RestaurantRepository.kt @@ -12,7 +12,6 @@ import org.gdsc.domain.model.RestaurantLocationInfo import org.gdsc.domain.model.UserLocation import org.gdsc.domain.model.request.ModifyRestaurantInfoRequest import org.gdsc.domain.model.request.RestaurantRegistrationRequest -import org.gdsc.domain.model.request.RestaurantSearchMapRequest import org.gdsc.domain.model.response.RestaurantInfoResponse interface RestaurantRepository { diff --git a/domain/src/main/java/org/gdsc/domain/usecase/GetRegisteredRestaurantUseCase.kt b/domain/src/main/java/org/gdsc/domain/usecase/GetRegisteredRestaurantUseCase.kt index 7eaafc51..fc0485d4 100644 --- a/domain/src/main/java/org/gdsc/domain/usecase/GetRegisteredRestaurantUseCase.kt +++ b/domain/src/main/java/org/gdsc/domain/usecase/GetRegisteredRestaurantUseCase.kt @@ -1,6 +1,5 @@ package org.gdsc.domain.usecase -import androidx.paging.PagingData import kotlinx.coroutines.flow.Flow import org.gdsc.domain.DrinkPossibility import org.gdsc.domain.FoodCategory @@ -8,7 +7,6 @@ import org.gdsc.domain.SortType import org.gdsc.domain.model.Location import org.gdsc.domain.model.PagingResult import org.gdsc.domain.model.RegisteredRestaurant -import org.gdsc.domain.model.request.RestaurantSearchMapRequest import org.gdsc.domain.repository.RestaurantRepository import javax.inject.Inject diff --git a/presentation/src/main/java/org/gdsc/presentation/view/mypage/viewmodel/MyPageViewModel.kt b/presentation/src/main/java/org/gdsc/presentation/view/mypage/viewmodel/MyPageViewModel.kt index f9286182..47668750 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/mypage/viewmodel/MyPageViewModel.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/mypage/viewmodel/MyPageViewModel.kt @@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flatMapMerge import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -24,11 +23,9 @@ import org.gdsc.domain.DrinkPossibility import org.gdsc.domain.Empty import org.gdsc.domain.FoodCategory import org.gdsc.domain.SortType -import org.gdsc.domain.model.Filter import org.gdsc.domain.model.Location import org.gdsc.domain.model.PagingResult import org.gdsc.domain.model.RegisteredRestaurant -import org.gdsc.domain.model.request.RestaurantSearchMapRequest import org.gdsc.domain.model.response.NicknameResponse import org.gdsc.domain.usecase.CheckDuplicatedNicknameUseCase import org.gdsc.domain.usecase.GetRegisteredRestaurantUseCase From 48378230a2e69703702eab380d579ea391b63bdd Mon Sep 17 00:00:00 2001 From: DDUDDY Date: Mon, 4 Mar 2024 21:46:37 +0900 Subject: [PATCH 06/12] =?UTF-8?q?[feat/all=5Fsearch]:=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=20=ED=82=A4=EC=9B=8C=EB=93=9C=EB=A1=9C=20=EC=8B=9D=EB=8B=B9=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RestaurantBySearchPagingSource.kt | 35 +++++++++++++++++++ .../datasource/RestaurantDataSourceImpl.kt | 20 +++++++++-- .../org/gdsc/data/network/RestaurantAPI.kt | 5 +++ .../repository/RestaurantRepositoryImpl.kt | 25 +++++++++++++ .../domain/repository/RestaurantRepository.kt | 1 + .../usecase/GetRestaurantBySearchUseCase.kt | 20 +++++++++++ 6 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 data/src/main/java/org/gdsc/data/database/RestaurantBySearchPagingSource.kt create mode 100644 domain/src/main/java/org/gdsc/domain/usecase/GetRestaurantBySearchUseCase.kt diff --git a/data/src/main/java/org/gdsc/data/database/RestaurantBySearchPagingSource.kt b/data/src/main/java/org/gdsc/data/database/RestaurantBySearchPagingSource.kt new file mode 100644 index 00000000..df5725c0 --- /dev/null +++ b/data/src/main/java/org/gdsc/data/database/RestaurantBySearchPagingSource.kt @@ -0,0 +1,35 @@ +package org.gdsc.data.database + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import org.gdsc.data.model.RegisteredRestaurantResponse +import org.gdsc.data.network.RestaurantAPI +import org.gdsc.domain.model.request.RestaurantSearchRequest + +class RestaurantBySearchPagingSource( + private val api: RestaurantAPI, + private val restaurantSearchRequest: RestaurantSearchRequest, +): 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.getRegisteredRestaurantsBySearch( + restaurantSearchRequest = restaurantSearchRequest + ) + LoadResult.Page( + data = items.data.restaurants, + prevKey = null, + nextKey = if (items.data.restaurants.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/RestaurantDataSourceImpl.kt b/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSourceImpl.kt index 1b4b01dc..a5acdeda 100644 --- a/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSourceImpl.kt +++ b/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSourceImpl.kt @@ -1,13 +1,11 @@ package org.gdsc.data.datasource import android.util.Log -import androidx.lifecycle.asFlow import androidx.paging.ExperimentalPagingApi import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData import androidx.paging.cachedIn -import androidx.paging.liveData import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow @@ -15,6 +13,7 @@ import kotlinx.coroutines.flow.map import okhttp3.RequestBody.Companion.toRequestBody import org.gdsc.data.database.RegisteredRestaurant import org.gdsc.data.database.RestaurantByMapPagingSource +import org.gdsc.data.database.RestaurantBySearchPagingSource import org.gdsc.data.database.RestaurantDatabase import org.gdsc.data.database.RestaurantMediator import org.gdsc.data.model.RegisteredRestaurantResponse @@ -205,4 +204,21 @@ class RestaurantDataSourceImpl @Inject constructor( }.flow.cachedIn(coroutineScope) } + override suspend fun getRegisteredRestaurantsBySearch( + keyword: String?, userLocation: Location? + ): Flow> { + return Pager( + config = PagingConfig( + pageSize = 20, + enablePlaceholders = true + )) { + RestaurantBySearchPagingSource( + restaurantAPI, + RestaurantSearchRequest( + keyword = keyword, + userLocation = userLocation + ) + ) + }.flow.cachedIn(coroutineScope) + } } diff --git a/data/src/main/java/org/gdsc/data/network/RestaurantAPI.kt b/data/src/main/java/org/gdsc/data/network/RestaurantAPI.kt index 2b4f630c..80a09f00 100644 --- a/data/src/main/java/org/gdsc/data/network/RestaurantAPI.kt +++ b/data/src/main/java/org/gdsc/data/network/RestaurantAPI.kt @@ -53,6 +53,11 @@ interface RestaurantAPI { @Part pictures: List, ): Response + @POST("api/v1/restaurant/search") + suspend fun getRegisteredRestaurantsBySearch( + @Body restaurantSearchRequest: RestaurantSearchRequest, + ): Response + @POST("api/v1/restaurant/search/{userid}") suspend fun getRegisteredRestaurants( @Path("userid") userId: Int, diff --git a/data/src/main/java/org/gdsc/data/repository/RestaurantRepositoryImpl.kt b/data/src/main/java/org/gdsc/data/repository/RestaurantRepositoryImpl.kt index 6603b255..3d2d8a23 100644 --- a/data/src/main/java/org/gdsc/data/repository/RestaurantRepositoryImpl.kt +++ b/data/src/main/java/org/gdsc/data/repository/RestaurantRepositoryImpl.kt @@ -120,4 +120,29 @@ class RestaurantRepositoryImpl @Inject constructor( } } } + + override suspend fun getRegisteredRestaurantsBySearch(keyword: String?, userLocation: Location?): Flow> { + return restaurantDataSource.getRegisteredRestaurantsBySearch(keyword, userLocation).map { result -> + result.map { restaurant -> + RegisteredRestaurant( + id = restaurant.id, + name = restaurant.name, + placeUrl = restaurant.placeUrl, + phone = restaurant.phone, + address = restaurant.address, + roadAddress = restaurant.roadAddress, + x = restaurant.x, + y = restaurant.y, + restaurantImageUrl = restaurant.restaurantImageUrl, + introduce = restaurant.introduce, + category = restaurant.category, + userId = restaurant.id, + userNickName = restaurant.userNickName, + userProfileImageUrl = restaurant.userProfileImageUrl, + canDrinkLiquor = restaurant.canDrinkLiquor, + differenceInDistance = restaurant.differenceInDistance, + ) + } + } + } } \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/repository/RestaurantRepository.kt b/domain/src/main/java/org/gdsc/domain/repository/RestaurantRepository.kt index 1b6c99c4..822aa237 100644 --- a/domain/src/main/java/org/gdsc/domain/repository/RestaurantRepository.kt +++ b/domain/src/main/java/org/gdsc/domain/repository/RestaurantRepository.kt @@ -39,4 +39,5 @@ interface RestaurantRepository { userLocation: Location?, startLocation: Location?, endLocation: Location?, sortType: SortType, foodCategory: FoodCategory?, drinkPossibility: DrinkPossibility? ): Flow> + suspend fun getRegisteredRestaurantsBySearch(keyword: String?, userLocation: Location?): Flow> } \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/usecase/GetRestaurantBySearchUseCase.kt b/domain/src/main/java/org/gdsc/domain/usecase/GetRestaurantBySearchUseCase.kt new file mode 100644 index 00000000..f74de716 --- /dev/null +++ b/domain/src/main/java/org/gdsc/domain/usecase/GetRestaurantBySearchUseCase.kt @@ -0,0 +1,20 @@ +package org.gdsc.domain.usecase + +import androidx.paging.PagingData +import kotlinx.coroutines.flow.Flow +import org.gdsc.domain.model.Location +import org.gdsc.domain.model.RegisteredRestaurant +import org.gdsc.domain.repository.RestaurantRepository +import javax.inject.Inject + +class GetRestaurantBySearchUseCase @Inject constructor( + private val restaurantRepository: RestaurantRepository +) { + suspend operator fun invoke( + keyword: String?, + userLocation: Location? + ): Flow> { + + return restaurantRepository.getRegisteredRestaurantsBySearch( keyword, userLocation) + } +} \ No newline at end of file From f6118d2f78ad8338fa2e67ecbf2d6a1a35f016bf Mon Sep 17 00:00:00 2001 From: DDUDDY Date: Mon, 4 Mar 2024 21:46:57 +0900 Subject: [PATCH 07/12] =?UTF-8?q?[feat/all=5Fsearch]:=20AllSearch=20ViewPa?= =?UTF-8?q?ger=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../allsearch/AllSearchContainerFragment.kt | 18 ++- .../view/allsearch/AllSearchViewModel.kt | 72 ++++++++++++ .../allsearch/SearchCategoryAllFragment.kt | 40 ++++++- .../allsearch/SearchCategoryGroupAdapter.kt | 4 + .../allsearch/SearchCategoryGroupFragment.kt | 4 +- .../allsearch/SearchCategoryPagerAdapter.kt | 11 +- .../SearchCategoryRestaurantAdapter.kt | 70 ++++++++++++ .../SearchCategoryRestaurantFragment.kt | 75 ++++++++++++- .../layout/fragment_search_category_all.xml | 84 +++++++++++++- .../fragment_search_category_restaurant.xml | 59 +++++++++- .../res/layout/item_search_restaurant.xml | 105 ++++++++++++++++++ presentation/src/main/res/values/dimens.xml | 1 + 12 files changed, 525 insertions(+), 18 deletions(-) create mode 100644 presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchViewModel.kt create mode 100644 presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryGroupAdapter.kt create mode 100644 presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryRestaurantAdapter.kt create mode 100644 presentation/src/main/res/layout/item_search_restaurant.xml diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchContainerFragment.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchContainerFragment.kt index 989c9911..6ca8017d 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchContainerFragment.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchContainerFragment.kt @@ -5,13 +5,16 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels 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 @AndroidEntryPoint class AllSearchContainerFragment: Fragment() { @@ -19,6 +22,8 @@ class AllSearchContainerFragment: Fragment() { private var _binding: FragmentAllSearchContainerBinding? = null private val binding get() = _binding!! + val viewModel: AllSearchViewModel by viewModels() + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -34,6 +39,7 @@ class AllSearchContainerFragment: Fragment() { binding.searchBar.setCancelViewListener(cancelViewListener) arguments?.getString("keyword")?.let { + viewModel.setSearchKeyword(it) binding.searchBar.setSearchText(it) } @@ -42,7 +48,15 @@ class AllSearchContainerFragment: Fragment() { } private fun setPager() { - binding.searchCategoryPager.adapter = SearchCategoryPagerAdapter(this) + binding.searchCategoryPager.adapter = SearchCategoryPagerAdapter( + this, + viewModel.searchKeyword.value + ) + repeatWhenUiStarted { + viewModel.searchKeyword.collect { + binding.searchCategoryPager.adapter = SearchCategoryPagerAdapter(this, it) + } + } } private fun setTabLayout() { @@ -61,6 +75,8 @@ class AllSearchContainerFragment: Fragment() { override fun onChangeText(text: CharSequence) {} override fun onSubmitText(text: CharSequence) { if (text.isEmpty()) return + viewModel.setSearchKeyword(text.toString()) + binding.searchBar.setSearchText(text.toString()) } } private val cancelViewListener = object : CancelViewListener { 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 new file mode 100644 index 00000000..77598e94 --- /dev/null +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchViewModel.kt @@ -0,0 +1,72 @@ +package org.gdsc.presentation.view.allsearch + +import androidx.lifecycle.ViewModel +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 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.presentation.JmtLocationManager +import javax.inject.Inject + +@HiltViewModel +class AllSearchViewModel @Inject constructor( + private val locationManager: JmtLocationManager, + private val getRestaurantBySearchUseCase: GetRestaurantBySearchUseCase, +): ViewModel(){ + + private var _searchKeyword = MutableStateFlow("") + val searchKeyword: StateFlow + get() = _searchKeyword + + private var _sortTypeState = MutableStateFlow(SortType.DISTANCE) + val sortTypeState: StateFlow + get() = _sortTypeState + + private var _foodCategoryState = MutableStateFlow(FoodCategory.INIT) + val foodCategoryState: StateFlow + get() = _foodCategoryState + + private var _drinkPossibilityState = MutableStateFlow(DrinkPossibility.INIT) + val drinkPossibilityState: StateFlow + get() = _drinkPossibilityState + + + fun setSearchKeyword(keyword: String) { + _searchKeyword.value = keyword + } + + fun setSortType(sortType: SortType) { + _sortTypeState.value = sortType + } + + fun setFoodCategory(foodCategory: FoodCategory) { + _foodCategoryState.value = foodCategory + } + + fun setDrinkPossibility(drinkPossibility: DrinkPossibility) { + _drinkPossibilityState.value = drinkPossibility + } + @OptIn(ExperimentalCoroutinesApi::class) + suspend fun registeredPagingData(): Flow> { + 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) + } +} \ No newline at end of file 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 c5e5411f..e1b45de2 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 @@ -5,13 +5,28 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.viewpager2.widget.ViewPager2 +import dagger.hilt.android.AndroidEntryPoint +import org.gdsc.presentation.R import org.gdsc.presentation.databinding.FragmentSearchCategoryAllBinding +import org.gdsc.presentation.utils.repeatWhenUiStarted -class SearchCategoryAllFragment: Fragment() { +@AndroidEntryPoint +class SearchCategoryAllFragment( + private val searchKeyword: String +): Fragment() { private var _binding: FragmentSearchCategoryAllBinding? = null private val binding get() = _binding!! + + val viewModel: AllSearchViewModel by viewModels() + + + private val searchCategoryRestaurantAdapter = SearchCategoryRestaurantAdapter() + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -22,5 +37,28 @@ class SearchCategoryAllFragment: Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + + binding.restaurantRecyclerView.adapter = searchCategoryRestaurantAdapter + binding.restaurantRecyclerView.layoutManager = LinearLayoutManager(requireContext()) + + binding.icRestaurant.setOnClickListener { + val viewPager = requireActivity().findViewById(R.id.search_category_pager) + viewPager.currentItem = 1 + } + + viewModel.setSearchKeyword(searchKeyword) + observeState() + } + private fun observeState() { + repeatWhenUiStarted { + viewModel.registeredPagingData().collect { + searchCategoryRestaurantAdapter.submitData(it) + } + } + } + + override fun onDestroyView() { + _binding = null + super.onDestroyView() } } \ No newline at end of file diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryGroupAdapter.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryGroupAdapter.kt new file mode 100644 index 00000000..14f7ea75 --- /dev/null +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryGroupAdapter.kt @@ -0,0 +1,4 @@ +package org.gdsc.presentation.view.allsearch + +class SearchCategoryGroupAdapter { +} \ No newline at end of file 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 4e4c9e83..5c595862 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 @@ -7,7 +7,9 @@ import android.view.ViewGroup import androidx.fragment.app.Fragment import org.gdsc.presentation.databinding.FragmentSearchCategoryGroupBinding -class SearchCategoryGroupFragment: Fragment() { +class SearchCategoryGroupFragment( + private val searchKeyword: String +): Fragment() { private var _binding: FragmentSearchCategoryGroupBinding? = null private val binding get() = _binding!! diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryPagerAdapter.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryPagerAdapter.kt index 4b86c891..5923fc6b 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryPagerAdapter.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryPagerAdapter.kt @@ -3,14 +3,17 @@ package org.gdsc.presentation.view.allsearch import androidx.fragment.app.Fragment import androidx.viewpager2.adapter.FragmentStateAdapter -class SearchCategoryPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) { +class SearchCategoryPagerAdapter( + fragment: Fragment, + private val keyword: String +) : FragmentStateAdapter(fragment) { override fun getItemCount() = SEARCH_CATEGORY_PAGER_SIZE override fun createFragment(position: Int): Fragment { return when (position) { - CATEGORY_RESTAURANT -> SearchCategoryRestaurantFragment() - CATEGORY_GROUP -> SearchCategoryGroupFragment() - else -> SearchCategoryAllFragment() + CATEGORY_RESTAURANT -> SearchCategoryRestaurantFragment(keyword) + CATEGORY_GROUP -> SearchCategoryGroupFragment(keyword) + else -> SearchCategoryAllFragment(keyword) } } diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryRestaurantAdapter.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryRestaurantAdapter.kt new file mode 100644 index 00000000..471e2cab --- /dev/null +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryRestaurantAdapter.kt @@ -0,0 +1,70 @@ +package org.gdsc.presentation.view.allsearch + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.paging.PagingDataAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import org.gdsc.domain.model.RegisteredRestaurant +import org.gdsc.presentation.R +import org.gdsc.presentation.databinding.ItemSearchRestaurantBinding + +class SearchCategoryRestaurantAdapter + : PagingDataAdapter(diffCallback) { + companion object { + val diffCallback = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: RegisteredRestaurant, + newItem: RegisteredRestaurant + ): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame( + oldItem: RegisteredRestaurant, + newItem: RegisteredRestaurant + ): Boolean { + return oldItem == newItem + } + } + } + + class RestaurantsWithSearchViewHolder( + private val binding: ItemSearchRestaurantBinding, + ): RecyclerView.ViewHolder(binding.root) { + fun bind(item: RegisteredRestaurant) { + binding.run { + Glide.with(itemView.context) + .load(item.userProfileImageUrl) + .placeholder(R.drawable.base_profile_image) + .into(userProfileImage) + + userName.text = item.userNickName + + Glide.with(itemView.context) + .load(item.restaurantImageUrl) + .placeholder(R.drawable.base_profile_image) + .into(restaurantImage) + + restaurantCategory.text = item.category + restaurantName.text = item.name + } + } + } + + override fun onBindViewHolder(holder: RestaurantsWithSearchViewHolder, position: Int) { + val item = getItem(position) + if (item != null) { + holder.bind(item) + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): RestaurantsWithSearchViewHolder { + val binding = ItemSearchRestaurantBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return SearchCategoryRestaurantAdapter.RestaurantsWithSearchViewHolder(binding) + } +} \ 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 be418ce0..acb1035c 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 @@ -5,13 +5,26 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collectLatest +import org.gdsc.domain.DrinkPossibility +import org.gdsc.domain.FoodCategory +import org.gdsc.domain.SortType import org.gdsc.presentation.databinding.FragmentSearchCategoryRestaurantBinding +import org.gdsc.presentation.utils.repeatWhenUiStarted -class SearchCategoryRestaurantFragment: Fragment() { +@AndroidEntryPoint +class SearchCategoryRestaurantFragment( + private val searchKeyword: String +): Fragment() { private var _binding: FragmentSearchCategoryRestaurantBinding? = null private val binding get() = _binding!! + val viewModel: AllSearchViewModel by viewModels() + private val searchCategoryRestaurantAdapter = SearchCategoryRestaurantAdapter() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -22,5 +35,65 @@ class SearchCategoryRestaurantFragment: Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + + observeState() + setSpinners() + + viewModel.setSearchKeyword(searchKeyword) + + binding.restaurantRecyclerView.adapter = searchCategoryRestaurantAdapter + binding.restaurantRecyclerView.layoutManager = LinearLayoutManager(requireContext()) + + } + + private fun observeState() { + repeatWhenUiStarted { + viewModel.registeredPagingData().collect { + searchCategoryRestaurantAdapter.submitData(it) + } + } + + repeatWhenUiStarted { + viewModel.sortTypeState.collectLatest { + binding.sortSpinner.setMenuTitle(it.text) + } + } + + repeatWhenUiStarted { + viewModel.foodCategoryState.collectLatest { + binding.foodCategorySpinner.setMenuTitle(it.text) + } + } + + repeatWhenUiStarted { + viewModel.drinkPossibilityState.collectLatest { + binding.drinkPossibilitySpinner.setMenuTitle(it.text) + } + } + } + + private fun setSpinners() { + binding.sortSpinner.setMenu( + SortType.getAllText() + ) { + viewModel.setSortType(SortType.values()[it.itemId]) + } + + binding.foodCategorySpinner.setMenu( + FoodCategory.getAllText() + ) { + viewModel.setFoodCategory(FoodCategory.values()[it.itemId]) + } + + binding.drinkPossibilitySpinner.setMenu( + DrinkPossibility.getAllText() + ) { + viewModel.setDrinkPossibility(DrinkPossibility.values()[it.itemId]) + } + } + + override fun onDestroyView() { + _binding = null + super.onDestroyView() } } \ No newline at end of file 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 a7f377f7..4680e67d 100644 --- a/presentation/src/main/res/layout/fragment_search_category_all.xml +++ b/presentation/src/main/res/layout/fragment_search_category_all.xml @@ -1,15 +1,89 @@ + + + + + + + + + + + + + + + + + + android:visibility="gone" + app:constraint_referenced_ids="divider,title_group,ic_group,group_recycler_view"/> \ 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 e625e6a1..24e3c9a7 100644 --- a/presentation/src/main/res/layout/fragment_search_category_restaurant.xml +++ b/presentation/src/main/res/layout/fragment_search_category_restaurant.xml @@ -1,15 +1,64 @@ - + + + + + + + + + + + + + tools:layout_editor_absoluteX="0dp" /> \ No newline at end of file diff --git a/presentation/src/main/res/layout/item_search_restaurant.xml b/presentation/src/main/res/layout/item_search_restaurant.xml new file mode 100644 index 00000000..23650d5a --- /dev/null +++ b/presentation/src/main/res/layout/item_search_restaurant.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/values/dimens.xml b/presentation/src/main/res/values/dimens.xml index 68cf9303..97f6d9cc 100644 --- a/presentation/src/main/res/values/dimens.xml +++ b/presentation/src/main/res/values/dimens.xml @@ -3,6 +3,7 @@ 1dp 2dp 8dp + 12dp 20dp 10dp From f187e31c2c34c10b4f5003ad4294b624b6058167 Mon Sep 17 00:00:00 2001 From: soopeach Date: Sat, 9 Mar 2024 19:29:57 +0900 Subject: [PATCH 08/12] =?UTF-8?q?[feat/all=5Fsearch]:=20}=20=EC=9D=B4?= =?UTF-8?q?=EC=8A=88=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/RestaurantRepositoryImpl.kt | 73 ++++++++++++------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/data/src/main/java/org/gdsc/data/repository/RestaurantRepositoryImpl.kt b/data/src/main/java/org/gdsc/data/repository/RestaurantRepositoryImpl.kt index 812773eb..1d520da9 100644 --- a/data/src/main/java/org/gdsc/data/repository/RestaurantRepositoryImpl.kt +++ b/data/src/main/java/org/gdsc/data/repository/RestaurantRepositoryImpl.kt @@ -30,7 +30,10 @@ class RestaurantRepositoryImpl @Inject constructor( return restaurantDataSource.getRestaurantLocationInfo(query, latitude, longitude, page) } - override suspend fun getRecommendRestaurantInfo(recommendRestaurantId: Int, userLocation: UserLocation): RestaurantInfoResponse { + override suspend fun getRecommendRestaurantInfo( + recommendRestaurantId: Int, + userLocation: UserLocation + ): RestaurantInfoResponse { return restaurantDataSource.getRecommendRestaurantInfo(recommendRestaurantId, userLocation) } @@ -47,7 +50,11 @@ class RestaurantRepositoryImpl @Inject constructor( } override suspend fun getRestaurants( - userId: Int, locationData: Location, sortType: SortType, foodCategory: FoodCategory, drinkPossibility: DrinkPossibility + userId: Int, + locationData: Location, + sortType: SortType, + foodCategory: FoodCategory, + drinkPossibility: DrinkPossibility ): Flow> { return restaurantDataSource.getRestaurants( userId, @@ -78,7 +85,8 @@ class RestaurantRepositoryImpl @Inject constructor( differenceInDistance = restaurant.differenceInDistance, ) restaurantTemp - }, result.totalElementsCount) + }, result.totalElementsCount + ) pagingTemp } } @@ -89,7 +97,12 @@ class RestaurantRepositoryImpl @Inject constructor( } override suspend fun getRestaurantsByMap( - userLocation: Location?, startLocation: Location?, endLocation: Location?, sortType: SortType, foodCategory: FoodCategory?, drinkPossibility: DrinkPossibility? + userLocation: Location?, + startLocation: Location?, + endLocation: Location?, + sortType: SortType, + foodCategory: FoodCategory?, + drinkPossibility: DrinkPossibility? ): Flow> { return restaurantDataSource.getRestaurantsByMap( userLocation, @@ -122,32 +135,38 @@ class RestaurantRepositoryImpl @Inject constructor( } } - override suspend fun getRegisteredRestaurantsBySearch(keyword: String?, userLocation: Location?): Flow> { - return restaurantDataSource.getRegisteredRestaurantsBySearch(keyword, userLocation).map { result -> - result.map { restaurant -> - RegisteredRestaurant( - id = restaurant.id, - name = restaurant.name, - placeUrl = restaurant.placeUrl, - phone = restaurant.phone, - address = restaurant.address, - roadAddress = restaurant.roadAddress, - x = restaurant.x, - y = restaurant.y, - restaurantImageUrl = restaurant.restaurantImageUrl, - introduce = restaurant.introduce, - category = restaurant.category, - userId = restaurant.id, - userNickName = restaurant.userNickName, - userProfileImageUrl = restaurant.userProfileImageUrl, - canDrinkLiquor = restaurant.canDrinkLiquor, - differenceInDistance = restaurant.differenceInDistance, - ) + override suspend fun getRegisteredRestaurantsBySearch( + keyword: String?, + userLocation: Location? + ): Flow> { + return restaurantDataSource.getRegisteredRestaurantsBySearch(keyword, userLocation) + .map { result -> + result.map { restaurant -> + RegisteredRestaurant( + id = restaurant.id, + name = restaurant.name, + placeUrl = restaurant.placeUrl, + phone = restaurant.phone, + address = restaurant.address, + roadAddress = restaurant.roadAddress, + x = restaurant.x, + y = restaurant.y, + restaurantImageUrl = restaurant.restaurantImageUrl, + introduce = restaurant.introduce, + category = restaurant.category, + userId = restaurant.id, + userNickName = restaurant.userNickName, + userProfileImageUrl = restaurant.userProfileImageUrl, + canDrinkLiquor = restaurant.canDrinkLiquor, + differenceInDistance = restaurant.differenceInDistance, + ) + } } - } + } override suspend fun getRestaurantReviews(restaurantId: Int): List { return restaurantDataSource.getRestaurantReviews(restaurantId).reviewList } -} \ No newline at end of file + +} From 1b8fd05cacc45c7d14cc4c5cfbcbe53798afa448 Mon Sep 17 00:00:00 2001 From: soopeach Date: Sun, 10 Mar 2024 23:20:26 +0900 Subject: [PATCH 09/12] =?UTF-8?q?[feat/all=5Fsearch]:=20=EC=B5=9C=EA=B7=BC?= =?UTF-8?q?=20=EA=B2=80=EC=83=89=20=ED=82=A4=EC=9B=8C=EB=93=9C=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EA=B4=80=EB=A0=A8=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/gdsc/data/LocalHistoryDataStore.kt | 53 +++++++++++++++++++ .../gdsc/data/datasource/UserDataSource.kt | 6 +++ .../data/datasource/UserDataSourceImpl.kt | 16 +++++- .../java/org/gdsc/data/di/DatabaseModule.kt | 5 ++ .../data/repository/UserRepositoryImpl.kt | 12 +++++ .../gdsc/domain/repository/UserRepository.kt | 6 +++ .../user/DeleteSearchedKeywordUseCase.kt | 13 +++++ .../user/GetSearchedKeywordsUseCase.kt | 12 +++++ .../user/UpdateSearchedKeywordUseCase.kt | 13 +++++ 9 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 data/src/main/java/org/gdsc/data/LocalHistoryDataStore.kt create mode 100644 domain/src/main/java/org/gdsc/domain/usecase/user/DeleteSearchedKeywordUseCase.kt create mode 100644 domain/src/main/java/org/gdsc/domain/usecase/user/GetSearchedKeywordsUseCase.kt create mode 100644 domain/src/main/java/org/gdsc/domain/usecase/user/UpdateSearchedKeywordUseCase.kt diff --git a/data/src/main/java/org/gdsc/data/LocalHistoryDataStore.kt b/data/src/main/java/org/gdsc/data/LocalHistoryDataStore.kt new file mode 100644 index 00000000..de653802 --- /dev/null +++ b/data/src/main/java/org/gdsc/data/LocalHistoryDataStore.kt @@ -0,0 +1,53 @@ +package org.gdsc.data + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +val Context.historyDataStore: DataStore by preferencesDataStore(name = "history") + +class LocalHistoryDataStore @Inject constructor(private val context: Context) { + private val searchedKeywordKey = stringPreferencesKey("accessToken") + + suspend fun updateSearchedKeyword(newKeyword: String): List { + val previous = getSearchedKeywordToken()?.toConvertedList() ?: emptyList() + val newHistories = (previous + newKeyword).distinct().toConvertedString() + updateSearchedKeywordToken(newHistories) + return newHistories.toConvertedList() + } + + suspend fun getSearchedKeyword() = getSearchedKeywordToken()?.toConvertedList() ?: emptyList() + + suspend fun deleteSearchedKeyword(targetKeyword: String): List { + val previous = getSearchedKeywordToken()?.toConvertedList() ?: emptyList() + val newHistories = previous.filter { it != targetKeyword }.toConvertedString() + updateSearchedKeywordToken(newHistories) + return newHistories.toConvertedList() + } + + private suspend fun getSearchedKeywordToken() = context.historyDataStore.data + .map { preferences -> + preferences[searchedKeywordKey] + }.first() + + private suspend fun updateSearchedKeywordToken(accessToken: String) { + context.historyDataStore.edit { preferences -> + preferences[searchedKeywordKey] = accessToken + } + } + + private fun List.toConvertedString(): String { + return this.toString().replace("[", "").replace("]", "") + } + + private fun String.toConvertedList(): List { + return this.split(",").map { it.trim() } + } + +} \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/datasource/UserDataSource.kt b/data/src/main/java/org/gdsc/data/datasource/UserDataSource.kt index 5260ba2c..56d422c3 100644 --- a/data/src/main/java/org/gdsc/data/datasource/UserDataSource.kt +++ b/data/src/main/java/org/gdsc/data/datasource/UserDataSource.kt @@ -23,4 +23,10 @@ interface UserDataSource { suspend fun postUserLogout(refreshToken: String): String suspend fun postUserSignout(): String + + suspend fun getSearchedKeywords(): List + + suspend fun updateSearchedKeyword(newKeyword: String): List + + suspend fun deleteSearchedKeyword(targetKeyword: String): List } \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/datasource/UserDataSourceImpl.kt b/data/src/main/java/org/gdsc/data/datasource/UserDataSourceImpl.kt index 07f739d7..98e193cb 100644 --- a/data/src/main/java/org/gdsc/data/datasource/UserDataSourceImpl.kt +++ b/data/src/main/java/org/gdsc/data/datasource/UserDataSourceImpl.kt @@ -1,6 +1,7 @@ package org.gdsc.data.datasource import okhttp3.MultipartBody +import org.gdsc.data.LocalHistoryDataStore import org.gdsc.data.network.UserAPI import org.gdsc.domain.model.Response import org.gdsc.domain.model.request.NicknameRequest @@ -11,7 +12,8 @@ import retrofit2.HttpException import javax.inject.Inject class UserDataSourceImpl @Inject constructor( - private val userAPI: UserAPI + private val userAPI: UserAPI, + private val localHistoryDataStore: LocalHistoryDataStore ) : UserDataSource { override suspend fun postNickname(nicknameRequest: NicknameRequest): NicknameResponse { return userAPI.postNickname(nicknameRequest).data @@ -69,4 +71,16 @@ class UserDataSourceImpl @Inject constructor( override suspend fun postUserSignout(): String { return userAPI.postUserSignOut().code } + + override suspend fun getSearchedKeywords(): List { + return localHistoryDataStore.getSearchedKeyword() + } + + override suspend fun updateSearchedKeyword(newKeyword: String): List { + return localHistoryDataStore.updateSearchedKeyword(newKeyword) + } + + override suspend fun deleteSearchedKeyword(targetKeyword: String): List { + return localHistoryDataStore.deleteSearchedKeyword(targetKeyword) + } } \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/di/DatabaseModule.kt b/data/src/main/java/org/gdsc/data/di/DatabaseModule.kt index ec27e5ab..0ee9bd7e 100644 --- a/data/src/main/java/org/gdsc/data/di/DatabaseModule.kt +++ b/data/src/main/java/org/gdsc/data/di/DatabaseModule.kt @@ -6,6 +6,7 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import org.gdsc.data.LocalHistoryDataStore import org.gdsc.data.database.RestaurantDatabase import javax.inject.Singleton @@ -24,4 +25,8 @@ class DatabaseModule { @Singleton @Provides fun provideRestaurantDao(database: RestaurantDatabase) = database.restaurantDao() + + @Singleton + @Provides + fun provideHistoryDataStore(@ApplicationContext context: Context) = LocalHistoryDataStore(context) } \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/repository/UserRepositoryImpl.kt b/data/src/main/java/org/gdsc/data/repository/UserRepositoryImpl.kt index dabb5d35..95a0ac75 100644 --- a/data/src/main/java/org/gdsc/data/repository/UserRepositoryImpl.kt +++ b/data/src/main/java/org/gdsc/data/repository/UserRepositoryImpl.kt @@ -44,4 +44,16 @@ class UserRepositoryImpl @Inject constructor( return userDataSource.postUserSignout() } + override suspend fun getSearchedKeywords(): List { + return userDataSource.getSearchedKeywords() + } + + override suspend fun updateSearchedKeyword(newKeyword: String): List { + return userDataSource.updateSearchedKeyword(newKeyword) + } + + override suspend fun deleteSearchedKeyword(targetKeyword: String): List { + return userDataSource.deleteSearchedKeyword(targetKeyword) + } + } \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/repository/UserRepository.kt b/domain/src/main/java/org/gdsc/domain/repository/UserRepository.kt index 7d0c872f..7c9fd494 100644 --- a/domain/src/main/java/org/gdsc/domain/repository/UserRepository.kt +++ b/domain/src/main/java/org/gdsc/domain/repository/UserRepository.kt @@ -23,4 +23,10 @@ interface UserRepository { suspend fun postUserLogout(refreshToken: String): String suspend fun postUserSignout(): String + + suspend fun getSearchedKeywords(): List + + suspend fun updateSearchedKeyword(newKeyword: String): List + + suspend fun deleteSearchedKeyword(targetKeyword: String): List } \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/usecase/user/DeleteSearchedKeywordUseCase.kt b/domain/src/main/java/org/gdsc/domain/usecase/user/DeleteSearchedKeywordUseCase.kt new file mode 100644 index 00000000..bfc3e479 --- /dev/null +++ b/domain/src/main/java/org/gdsc/domain/usecase/user/DeleteSearchedKeywordUseCase.kt @@ -0,0 +1,13 @@ +package org.gdsc.domain.usecase.user + +import org.gdsc.domain.repository.UserRepository +import javax.inject.Inject + +class DeleteSearchedKeywordUseCase @Inject constructor( + private val userRepository: UserRepository +) { + + suspend operator fun invoke(keyword: String): List { + return userRepository.deleteSearchedKeyword(keyword) + } +} \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/usecase/user/GetSearchedKeywordsUseCase.kt b/domain/src/main/java/org/gdsc/domain/usecase/user/GetSearchedKeywordsUseCase.kt new file mode 100644 index 00000000..fb383b35 --- /dev/null +++ b/domain/src/main/java/org/gdsc/domain/usecase/user/GetSearchedKeywordsUseCase.kt @@ -0,0 +1,12 @@ +package org.gdsc.domain.usecase.user + +import org.gdsc.domain.repository.UserRepository +import javax.inject.Inject + +class GetSearchedKeywordsUseCase @Inject constructor( + private val userRepository: UserRepository +) { + suspend operator fun invoke(): List { + return userRepository.getSearchedKeywords() + } +} \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/usecase/user/UpdateSearchedKeywordUseCase.kt b/domain/src/main/java/org/gdsc/domain/usecase/user/UpdateSearchedKeywordUseCase.kt new file mode 100644 index 00000000..f80374ed --- /dev/null +++ b/domain/src/main/java/org/gdsc/domain/usecase/user/UpdateSearchedKeywordUseCase.kt @@ -0,0 +1,13 @@ +package org.gdsc.domain.usecase.user + +import org.gdsc.domain.repository.UserRepository +import javax.inject.Inject + +class UpdateSearchedKeywordUseCase @Inject constructor( + private val userRepository: UserRepository +) { + + suspend operator fun invoke(keyword: String): List { + return userRepository.updateSearchedKeyword(keyword) + } +} \ No newline at end of file From cfa24a71f810ee2ee2d3c990d00f5e9a43ce8d9d Mon Sep 17 00:00:00 2001 From: soopeach Date: Mon, 11 Mar 2024 03:11:42 +0900 Subject: [PATCH 10/12] =?UTF-8?q?[feat/all=5Fsearch]:=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=ED=95=9C=20=EB=A7=9B=EC=A7=91=EC=9D=84=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EA=B0=9C=EC=88=98=EB=A7=8C=20=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=EB=8A=94=20UseCase=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasource/RestaurantDataSource.kt | 2 + .../datasource/RestaurantDataSourceImpl.kt | 66 +++++++++++++++---- .../repository/RestaurantRepositoryImpl.kt | 28 ++++++++ .../domain/repository/RestaurantRepository.kt | 2 + ...RestaurantBySearchWithLimitCountUseCase.kt | 19 ++++++ 5 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 domain/src/main/java/org/gdsc/domain/usecase/GetRestaurantBySearchWithLimitCountUseCase.kt diff --git a/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSource.kt b/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSource.kt index 41f4c567..b40f4f2b 100644 --- a/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSource.kt +++ b/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSource.kt @@ -43,6 +43,8 @@ interface RestaurantDataSource { suspend fun getRegisteredRestaurantsBySearch(keyword: String?, userLocation: Location?): Flow> + suspend fun getRegisteredRestaurantsBySearchWithLimitCount(keyword: String?, userLocation: Location?, limit: Int): List + suspend fun getRestaurantReviews(restaurantId: Int): ReviewPaging } \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSourceImpl.kt b/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSourceImpl.kt index 7972ebff..6f99d9ca 100644 --- a/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSourceImpl.kt +++ b/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSourceImpl.kt @@ -41,7 +41,7 @@ class RestaurantDataSourceImpl @Inject constructor( private val db: RestaurantDatabase, ) : RestaurantDataSource { - private val coroutineScope : CoroutineScope = CoroutineScope(Dispatchers.IO) + private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO) override suspend fun getRestaurantLocationInfo( query: String, latitude: String, @@ -52,7 +52,10 @@ class RestaurantDataSourceImpl @Inject constructor( return restaurantAPI.getRestaurantLocationInfo(query, latitude, longitude, page).data } - override suspend fun getRecommendRestaurantInfo(recommendRestaurantId: Int, userLocation: UserLocation): RestaurantInfoResponse { + override suspend fun getRecommendRestaurantInfo( + recommendRestaurantId: Int, + userLocation: UserLocation + ): RestaurantInfoResponse { return restaurantAPI.getRecommendRestaurantInfo(recommendRestaurantId, userLocation).data } @@ -87,7 +90,8 @@ class RestaurantDataSourceImpl @Inject constructor( mapOf( "name" to restaurantRegistrationRequest.name.toRequestBody(), "introduce" to restaurantRegistrationRequest.introduce.toRequestBody(), - "categoryId" to restaurantRegistrationRequest.categoryId.toString().toRequestBody(), + "categoryId" to restaurantRegistrationRequest.categoryId.toString() + .toRequestBody(), "canDrinkLiquor" to restaurantRegistrationRequest.canDrinkLiquor.toString() .toRequestBody(), "goWellWithLiquor" to restaurantRegistrationRequest.goWellWithLiquor.toRequestBody(), @@ -118,7 +122,11 @@ class RestaurantDataSourceImpl @Inject constructor( @OptIn(ExperimentalPagingApi::class) override suspend fun getRestaurants( - userId: Int, locationData: Location, sortType: SortType, foodCategory: FoodCategory, drinkPossibility: DrinkPossibility + userId: Int, + locationData: Location, + sortType: SortType, + foodCategory: FoodCategory, + drinkPossibility: DrinkPossibility ): Flow> { val categoryFilter = when (foodCategory) { FoodCategory.INIT, FoodCategory.ETC -> null @@ -136,7 +144,7 @@ class RestaurantDataSourceImpl @Inject constructor( FoodCategory.INIT, FoodCategory.ETC -> String.Empty else -> foodCategory.key }, - isCanDrinkLiquor = isCanDrinkLiquor, + isCanDrinkLiquor = isCanDrinkLiquor, ) val restaurantSearchRequest = RestaurantSearchRequest(filter, locationData) @@ -156,9 +164,23 @@ class RestaurantDataSourceImpl @Inject constructor( ) { with(db.restaurantDao()) { when (sortType) { - SortType.DISTANCE -> getRegisteredRestaurantsSortedDistance(userId, categoryFilter, isCanDrinkLiquor) - SortType.RECENCY -> getRegisteredRestaurantsSortedRecent(userId, categoryFilter, isCanDrinkLiquor) - SortType.LIKED -> getRegisteredRestaurants(userId, categoryFilter, isCanDrinkLiquor) + SortType.DISTANCE -> getRegisteredRestaurantsSortedDistance( + userId, + categoryFilter, + isCanDrinkLiquor + ) + + SortType.RECENCY -> getRegisteredRestaurantsSortedRecent( + userId, + categoryFilter, + isCanDrinkLiquor + ) + + SortType.LIKED -> getRegisteredRestaurants( + userId, + categoryFilter, + isCanDrinkLiquor + ) } } @@ -174,7 +196,12 @@ class RestaurantDataSourceImpl @Inject constructor( } override suspend fun getRestaurantsByMap( - userLocation: Location?, startLocation: Location?, endLocation: Location?, sortType: SortType, foodCategory: FoodCategory?, drinkPossibility: DrinkPossibility? + userLocation: Location?, + startLocation: Location?, + endLocation: Location?, + sortType: SortType, + foodCategory: FoodCategory?, + drinkPossibility: DrinkPossibility? ): Flow> { val restaurantSearchRequest = RestaurantSearchRequest( userLocation = userLocation, @@ -197,7 +224,8 @@ class RestaurantDataSourceImpl @Inject constructor( config = PagingConfig( pageSize = 20, enablePlaceholders = true - )) { + ) + ) { RestaurantByMapPagingSource( restaurantAPI, restaurantSearchRequest @@ -212,7 +240,8 @@ class RestaurantDataSourceImpl @Inject constructor( config = PagingConfig( pageSize = 20, enablePlaceholders = true - )) { + ) + ) { RestaurantBySearchPagingSource( restaurantAPI, RestaurantSearchRequest( @@ -223,6 +252,21 @@ class RestaurantDataSourceImpl @Inject constructor( }.flow.cachedIn(coroutineScope) } + override suspend fun getRegisteredRestaurantsBySearchWithLimitCount( + keyword: String?, + userLocation: Location?, + limit: Int + ): List { + + return restaurantAPI. + getRegisteredRestaurantsBySearch( + RestaurantSearchRequest( + keyword = keyword, + userLocation = userLocation + ) + ).data.restaurants.take(limit) + } + override suspend fun getRestaurantReviews(restaurantId: Int): ReviewPaging { return restaurantAPI.getRestaurantReviews(restaurantId).data } diff --git a/data/src/main/java/org/gdsc/data/repository/RestaurantRepositoryImpl.kt b/data/src/main/java/org/gdsc/data/repository/RestaurantRepositoryImpl.kt index 1d520da9..9d819334 100644 --- a/data/src/main/java/org/gdsc/data/repository/RestaurantRepositoryImpl.kt +++ b/data/src/main/java/org/gdsc/data/repository/RestaurantRepositoryImpl.kt @@ -169,4 +169,32 @@ class RestaurantRepositoryImpl @Inject constructor( } + override suspend fun getRegisteredRestaurantsBySearchWithLimitCount( + keyword: String?, + userLocation: Location?, + limit: Int + ): List { + return restaurantDataSource.getRegisteredRestaurantsBySearchWithLimitCount(keyword, userLocation, limit) + .map { restaurant -> + RegisteredRestaurant( + id = restaurant.id, + name = restaurant.name, + placeUrl = restaurant.placeUrl, + phone = restaurant.phone, + address = restaurant.address, + roadAddress = restaurant.roadAddress, + x = restaurant.x, + y = restaurant.y, + restaurantImageUrl = restaurant.restaurantImageUrl, + introduce = restaurant.introduce, + category = restaurant.category, + userId = restaurant.id, + userNickName = restaurant.userNickName, + userProfileImageUrl = restaurant.userProfileImageUrl, + canDrinkLiquor = restaurant.canDrinkLiquor, + differenceInDistance = restaurant.differenceInDistance, + ) + } + } + } diff --git a/domain/src/main/java/org/gdsc/domain/repository/RestaurantRepository.kt b/domain/src/main/java/org/gdsc/domain/repository/RestaurantRepository.kt index c0f5a6bc..12a0513a 100644 --- a/domain/src/main/java/org/gdsc/domain/repository/RestaurantRepository.kt +++ b/domain/src/main/java/org/gdsc/domain/repository/RestaurantRepository.kt @@ -44,4 +44,6 @@ interface RestaurantRepository { suspend fun getRestaurantReviews(restaurantId: Int): List + suspend fun getRegisteredRestaurantsBySearchWithLimitCount(keyword: String?, userLocation: Location?, limit: Int): List + } \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/usecase/GetRestaurantBySearchWithLimitCountUseCase.kt b/domain/src/main/java/org/gdsc/domain/usecase/GetRestaurantBySearchWithLimitCountUseCase.kt new file mode 100644 index 00000000..5e24719f --- /dev/null +++ b/domain/src/main/java/org/gdsc/domain/usecase/GetRestaurantBySearchWithLimitCountUseCase.kt @@ -0,0 +1,19 @@ +package org.gdsc.domain.usecase + +import org.gdsc.domain.model.Location +import org.gdsc.domain.model.RegisteredRestaurant +import org.gdsc.domain.repository.RestaurantRepository +import javax.inject.Inject + +class GetRestaurantBySearchWithLimitCountUseCase @Inject constructor( + private val restaurantRepository: RestaurantRepository +) { + suspend operator fun invoke( + keyword: String?, + userLocation: Location?, + limit: Int + ): List { + + return restaurantRepository.getRegisteredRestaurantsBySearchWithLimitCount(keyword, userLocation, limit) + } +} \ No newline at end of file From 449380a6eed36c050165dc06510adae5c21e28f7 Mon Sep 17 00:00:00 2001 From: soopeach Date: Mon, 11 Mar 2024 11:12:57 +0900 Subject: [PATCH 11/12] =?UTF-8?q?[feat/all=5Fsearch]:=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=20=ED=82=A4=EC=9B=8C=EB=93=9C=EB=A5=BC=20=EB=AA=A8=EB=91=90=20?= =?UTF-8?q?=EC=A7=80=EC=9A=B0=EB=8A=94=20UseCase=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/gdsc/data/LocalHistoryDataStore.kt | 9 +++++++-- .../java/org/gdsc/data/datasource/UserDataSource.kt | 2 ++ .../org/gdsc/data/datasource/UserDataSourceImpl.kt | 4 ++++ .../org/gdsc/data/repository/UserRepositoryImpl.kt | 4 ++++ .../java/org/gdsc/domain/repository/UserRepository.kt | 2 ++ .../domain/usecase/user/InitSearchedKeywordUseCase.kt | 10 ++++++++++ 6 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 domain/src/main/java/org/gdsc/domain/usecase/user/InitSearchedKeywordUseCase.kt diff --git a/data/src/main/java/org/gdsc/data/LocalHistoryDataStore.kt b/data/src/main/java/org/gdsc/data/LocalHistoryDataStore.kt index de653802..320d80d3 100644 --- a/data/src/main/java/org/gdsc/data/LocalHistoryDataStore.kt +++ b/data/src/main/java/org/gdsc/data/LocalHistoryDataStore.kt @@ -31,14 +31,19 @@ class LocalHistoryDataStore @Inject constructor(private val context: Context) { return newHistories.toConvertedList() } + suspend fun initSearchedKeyword(): List { + updateSearchedKeywordToken(emptyList().toConvertedString()) + return emptyList() + } + private suspend fun getSearchedKeywordToken() = context.historyDataStore.data .map { preferences -> preferences[searchedKeywordKey] }.first() - private suspend fun updateSearchedKeywordToken(accessToken: String) { + private suspend fun updateSearchedKeywordToken(newKeywords: String) { context.historyDataStore.edit { preferences -> - preferences[searchedKeywordKey] = accessToken + preferences[searchedKeywordKey] = newKeywords } } diff --git a/data/src/main/java/org/gdsc/data/datasource/UserDataSource.kt b/data/src/main/java/org/gdsc/data/datasource/UserDataSource.kt index 56d422c3..12b0c3cd 100644 --- a/data/src/main/java/org/gdsc/data/datasource/UserDataSource.kt +++ b/data/src/main/java/org/gdsc/data/datasource/UserDataSource.kt @@ -29,4 +29,6 @@ interface UserDataSource { suspend fun updateSearchedKeyword(newKeyword: String): List suspend fun deleteSearchedKeyword(targetKeyword: String): List + + suspend fun initSearchedKeyword(): List } \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/datasource/UserDataSourceImpl.kt b/data/src/main/java/org/gdsc/data/datasource/UserDataSourceImpl.kt index 98e193cb..f485628c 100644 --- a/data/src/main/java/org/gdsc/data/datasource/UserDataSourceImpl.kt +++ b/data/src/main/java/org/gdsc/data/datasource/UserDataSourceImpl.kt @@ -83,4 +83,8 @@ class UserDataSourceImpl @Inject constructor( override suspend fun deleteSearchedKeyword(targetKeyword: String): List { return localHistoryDataStore.deleteSearchedKeyword(targetKeyword) } + + override suspend fun initSearchedKeyword(): List { + return localHistoryDataStore.initSearchedKeyword() + } } \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/repository/UserRepositoryImpl.kt b/data/src/main/java/org/gdsc/data/repository/UserRepositoryImpl.kt index 95a0ac75..912bac34 100644 --- a/data/src/main/java/org/gdsc/data/repository/UserRepositoryImpl.kt +++ b/data/src/main/java/org/gdsc/data/repository/UserRepositoryImpl.kt @@ -56,4 +56,8 @@ class UserRepositoryImpl @Inject constructor( return userDataSource.deleteSearchedKeyword(targetKeyword) } + override suspend fun initSearchedKeyword(): List { + return userDataSource.initSearchedKeyword() + } + } \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/repository/UserRepository.kt b/domain/src/main/java/org/gdsc/domain/repository/UserRepository.kt index 7c9fd494..0a205e32 100644 --- a/domain/src/main/java/org/gdsc/domain/repository/UserRepository.kt +++ b/domain/src/main/java/org/gdsc/domain/repository/UserRepository.kt @@ -29,4 +29,6 @@ interface UserRepository { suspend fun updateSearchedKeyword(newKeyword: String): List suspend fun deleteSearchedKeyword(targetKeyword: String): List + + suspend fun initSearchedKeyword(): List } \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/usecase/user/InitSearchedKeywordUseCase.kt b/domain/src/main/java/org/gdsc/domain/usecase/user/InitSearchedKeywordUseCase.kt new file mode 100644 index 00000000..a47fe068 --- /dev/null +++ b/domain/src/main/java/org/gdsc/domain/usecase/user/InitSearchedKeywordUseCase.kt @@ -0,0 +1,10 @@ +package org.gdsc.domain.usecase.user + +import org.gdsc.domain.repository.UserRepository +import javax.inject.Inject + +class InitSearchedKeywordUseCase @Inject constructor( + private val userRepository: UserRepository +) { + suspend operator fun invoke() = userRepository.initSearchedKeyword() +} \ No newline at end of file From 1d6d62d64f28b9b1cb0441df86ff9f97bb8cb327 Mon Sep 17 00:00:00 2001 From: soopeach Date: Mon, 11 Mar 2024 20:46:45 +0900 Subject: [PATCH 12/12] =?UTF-8?q?[feat/all=5Fsearch]:=20=EA=B7=B8=EB=A3=B9?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=A0=9C=EC=99=B8=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=8C=80=EB=9E=B5?= =?UTF-8?q?=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/gdsc/domain/model/GroupInfo.kt | 13 ++ .../domain/model/response/GroupResponse.kt | 13 ++ .../allsearch/AllSearchContainerFragment.kt | 17 +- .../view/allsearch/AllSearchFragment.kt | 97 +++++++++- .../view/allsearch/AllSearchViewModel.kt | 100 ++++++++-- .../allsearch/SearchCategoryAllFragment.kt | 65 ++++++- .../allsearch/SearchCategoryGroupAdapter.kt | 4 - .../allsearch/SearchCategoryGroupFragment.kt | 3 + .../SearchCategoryRestaurantFragment.kt | 6 +- .../adapter/SearchCategoryGroupAdapter.kt | 64 +++++++ .../SearchCategoryGroupPreviewAdapter.kt | 69 +++++++ .../SearchCategoryPagerAdapter.kt | 5 +- .../SearchCategoryRestaurantAdapter.kt | 15 +- .../SearchCategoryRestaurantPreviewAdapter.kt | 74 ++++++++ .../main/res/layout/fragment_all_search.xml | 41 ++++- .../layout/fragment_search_category_all.xml | 173 ++++++++++-------- .../src/main/res/layout/item_search_group.xml | 103 +++++++++++ .../res/layout/item_search_restaurant.xml | 3 +- 18 files changed, 754 insertions(+), 111 deletions(-) create mode 100644 domain/src/main/java/org/gdsc/domain/model/GroupInfo.kt create mode 100644 domain/src/main/java/org/gdsc/domain/model/response/GroupResponse.kt delete mode 100644 presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryGroupAdapter.kt create mode 100644 presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryGroupAdapter.kt create mode 100644 presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryGroupPreviewAdapter.kt rename presentation/src/main/java/org/gdsc/presentation/view/allsearch/{ => adapter}/SearchCategoryPagerAdapter.kt (74%) rename presentation/src/main/java/org/gdsc/presentation/view/allsearch/{ => adapter}/SearchCategoryRestaurantAdapter.kt (85%) create mode 100644 presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryRestaurantPreviewAdapter.kt create mode 100644 presentation/src/main/res/layout/item_search_group.xml diff --git a/domain/src/main/java/org/gdsc/domain/model/GroupInfo.kt b/domain/src/main/java/org/gdsc/domain/model/GroupInfo.kt new file mode 100644 index 00000000..21989d6a --- /dev/null +++ b/domain/src/main/java/org/gdsc/domain/model/GroupInfo.kt @@ -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 +) \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/model/response/GroupResponse.kt b/domain/src/main/java/org/gdsc/domain/model/response/GroupResponse.kt new file mode 100644 index 00000000..4ce060ef --- /dev/null +++ b/domain/src/main/java/org/gdsc/domain/model/response/GroupResponse.kt @@ -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 +) \ No newline at end of file diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchContainerFragment.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchContainerFragment.kt index 6ca8017d..8d59bbf8 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchContainerFragment.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchContainerFragment.kt @@ -5,16 +5,17 @@ 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() { @@ -22,7 +23,9 @@ 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?, @@ -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) @@ -84,4 +89,10 @@ class AllSearchContainerFragment: Fragment() { findNavController().navigateUp() } } + + override fun onDestroyView() { + _binding = null + parent.changeToolbarVisible(true) + super.onDestroyView() + } } \ No newline at end of file diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchFragment.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchFragment.kt index 39e017ac..90835c1b 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchFragment.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/AllSearchFragment.kt @@ -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? @@ -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 { @@ -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) } } @@ -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() + } } \ 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 77598e94..7ba86fa8 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 @@ -5,20 +5,22 @@ 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 @@ -26,7 +28,52 @@ import javax.inject.Inject 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 @@ -44,6 +91,20 @@ class AllSearchViewModel @Inject constructor( val drinkPossibilityState: StateFlow get() = _drinkPossibilityState + private var _searchedRestaurantState = + MutableStateFlow>(PagingData.empty()) + val searchedRestaurantState: StateFlow> + get() = _searchedRestaurantState + + private var _searchedRestaurantPreviewState = + MutableStateFlow>(emptyList()) + val searchedRestaurantPreviewState: StateFlow> + get() = _searchedRestaurantPreviewState + + + private var _searchedKeywordsState = MutableStateFlow>(emptyList()) + val searchedKeywordsState: StateFlow> + get() = _searchedKeywordsState fun setSearchKeyword(keyword: String) { _searchKeyword.value = keyword @@ -60,13 +121,24 @@ class AllSearchViewModel @Inject constructor( fun setDrinkPossibility(drinkPossibility: DrinkPossibility) { _drinkPossibilityState.value = drinkPossibility } - @OptIn(ExperimentalCoroutinesApi::class) - suspend fun registeredPagingData(): Flow> { - 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() + } } + + } \ No newline at end of file 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 e1b45de2..3c4ee35e 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 @@ -5,13 +5,19 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.recyclerview.widget.LinearLayoutManager import androidx.viewpager2.widget.ViewPager2 import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collect +import org.gdsc.domain.model.GroupInfo import org.gdsc.presentation.R import org.gdsc.presentation.databinding.FragmentSearchCategoryAllBinding import org.gdsc.presentation.utils.repeatWhenUiStarted +import org.gdsc.presentation.view.allsearch.adapter.SearchCategoryGroupPreviewAdapter +import org.gdsc.presentation.view.allsearch.adapter.SearchCategoryRestaurantAdapter +import org.gdsc.presentation.view.allsearch.adapter.SearchCategoryRestaurantPreviewAdapter @AndroidEntryPoint class SearchCategoryAllFragment( @@ -21,11 +27,10 @@ class SearchCategoryAllFragment( private var _binding: FragmentSearchCategoryAllBinding? = null private val binding get() = _binding!! + val viewModel: AllSearchViewModel by activityViewModels() - val viewModel: AllSearchViewModel by viewModels() - - - private val searchCategoryRestaurantAdapter = SearchCategoryRestaurantAdapter() + private val searchCategoryRestaurantPreviewAdapter = SearchCategoryRestaurantPreviewAdapter() + private val searchCategoryGroupPreviewAdapter = SearchCategoryGroupPreviewAdapter() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -38,9 +43,16 @@ class SearchCategoryAllFragment( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding.restaurantRecyclerView.adapter = searchCategoryRestaurantAdapter + binding.restaurantRecyclerView.adapter = searchCategoryRestaurantPreviewAdapter binding.restaurantRecyclerView.layoutManager = LinearLayoutManager(requireContext()) + binding.groupRecyclerView.adapter = searchCategoryGroupPreviewAdapter + binding.groupRecyclerView.layoutManager = LinearLayoutManager(requireContext()) + + // Todo: New Adapter With Real APi + binding.recommendedRestaurantRecyclerView.adapter = searchCategoryRestaurantPreviewAdapter + binding.recommendedRestaurantRecyclerView.layoutManager = LinearLayoutManager(requireContext()) + binding.icRestaurant.setOnClickListener { val viewPager = requireActivity().findViewById(R.id.search_category_pager) viewPager.currentItem = 1 @@ -51,10 +63,49 @@ class SearchCategoryAllFragment( } private fun observeState() { repeatWhenUiStarted { - viewModel.registeredPagingData().collect { - searchCategoryRestaurantAdapter.submitData(it) + viewModel.searchedRestaurantPreviewState.collect { + searchCategoryRestaurantPreviewAdapter.submitList(it) } } + + // Todo: Real APi + searchCategoryGroupPreviewAdapter.submitList( + listOf( + GroupInfo( + "https://avatars.githubusercontent.com/u/58663494?v=4", + 1, + "햄버거 먹으러 갈 사람 여기여기 모여라", + "버거 대마왕", + "https://avatars.githubusercontent.com/u/58663494?v=4", + false, + (0 .. 500).shuffled().first(), + false, + (0 .. 500).shuffled().first() + ), + GroupInfo( + "https://avatars.githubusercontent.com/u/58663494?v=4", + 2, + "햄버거 먹으러 갈 사람 여기여기 모여라", + "버거 대마왕", + "https://avatars.githubusercontent.com/u/58663494?v=4", + false, + (0 .. 500).shuffled().first(), + false, + (0 .. 500).shuffled().first() + ), + GroupInfo( + "https://avatars.githubusercontent.com/u/58663494?v=4", + 3, + "햄버거 먹으러 갈 사람 여기여기 모여라", + "버거 대마왕", + "https://avatars.githubusercontent.com/u/58663494?v=4", + false, + (0 .. 500).shuffled().first(), + false, + (0 .. 500).shuffled().first() + ), + ) + ) } override fun onDestroyView() { diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryGroupAdapter.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryGroupAdapter.kt deleted file mode 100644 index 14f7ea75..00000000 --- a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryGroupAdapter.kt +++ /dev/null @@ -1,4 +0,0 @@ -package org.gdsc.presentation.view.allsearch - -class SearchCategoryGroupAdapter { -} \ No newline at end of file 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 5c595862..487d0ff7 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 @@ -5,6 +5,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import org.gdsc.presentation.databinding.FragmentSearchCategoryGroupBinding class SearchCategoryGroupFragment( @@ -14,6 +15,8 @@ class SearchCategoryGroupFragment( private var _binding: FragmentSearchCategoryGroupBinding? = null private val binding get() = _binding!! + private val viewModel: AllSearchViewModel by activityViewModels() + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? 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 acb1035c..cd13104f 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 @@ -5,6 +5,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint @@ -14,6 +15,7 @@ import org.gdsc.domain.FoodCategory import org.gdsc.domain.SortType import org.gdsc.presentation.databinding.FragmentSearchCategoryRestaurantBinding import org.gdsc.presentation.utils.repeatWhenUiStarted +import org.gdsc.presentation.view.allsearch.adapter.SearchCategoryRestaurantAdapter @AndroidEntryPoint class SearchCategoryRestaurantFragment( @@ -23,7 +25,7 @@ class SearchCategoryRestaurantFragment( private var _binding: FragmentSearchCategoryRestaurantBinding? = null private val binding get() = _binding!! - val viewModel: AllSearchViewModel by viewModels() + val viewModel: AllSearchViewModel by activityViewModels() private val searchCategoryRestaurantAdapter = SearchCategoryRestaurantAdapter() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -48,7 +50,7 @@ class SearchCategoryRestaurantFragment( private fun observeState() { repeatWhenUiStarted { - viewModel.registeredPagingData().collect { + viewModel.searchedRestaurantState.collect { searchCategoryRestaurantAdapter.submitData(it) } } 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 new file mode 100644 index 00000000..e8d5e37a --- /dev/null +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryGroupAdapter.kt @@ -0,0 +1,64 @@ +package org.gdsc.presentation.view.allsearch.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +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.presentation.R +import org.gdsc.presentation.databinding.ItemSearchGroupBinding + +class SearchCategoryGroupAdapter() : + PagingDataAdapter( + DiffCallback + ) { + + companion object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: GroupInfo, newItem: GroupInfo): Boolean { + return oldItem.groupId == newItem.groupId + } + + override fun areContentsTheSame(oldItem: GroupInfo, newItem: GroupInfo): Boolean { + return oldItem == newItem + } + } + + class SearchCategoryGroupViewHolder( + private val binding: ItemSearchGroupBinding, + ) : RecyclerView.ViewHolder(binding.root) { + fun bind(item: GroupInfo) { + binding.run { + Glide.with(itemView.context) + .load(item.groupProfileImageUrl) + .placeholder(R.drawable.base_profile_image) + .into(ivGroupImage) + + tvGroupName.text = item.groupName + tvIntroduction.text = item.groupIntroduce + tvMemberCount.text = item.memberCnt.toString() + tvRestaurantCount.text = item.restaurantCnt.toString() + } + } + } + + override fun onBindViewHolder( + holder: SearchCategoryGroupViewHolder, + position: Int + ) { + val item = getItem(position) + if (item != null) { + holder.bind(item) + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): SearchCategoryGroupViewHolder { + val binding = + ItemSearchGroupBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return SearchCategoryGroupViewHolder(binding) + } +} \ No newline at end of file diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryGroupPreviewAdapter.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryGroupPreviewAdapter.kt new file mode 100644 index 00000000..00e6c93e --- /dev/null +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryGroupPreviewAdapter.kt @@ -0,0 +1,69 @@ +package org.gdsc.presentation.view.allsearch.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import org.gdsc.domain.model.GroupInfo +import org.gdsc.presentation.R +import org.gdsc.presentation.databinding.ItemSearchGroupBinding + +class SearchCategoryGroupPreviewAdapter + : ListAdapter( + diffCallback +) { + + companion object { + val diffCallback = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: GroupInfo, + newItem: GroupInfo + ): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame( + oldItem: GroupInfo, + newItem: GroupInfo + ): Boolean { + return oldItem == newItem + } + } + } + + class GroupWithSearchPreviewViewHolder( + private val binding: ItemSearchGroupBinding, + ): RecyclerView.ViewHolder(binding.root) { + fun bind(item: GroupInfo) { + binding.run { + Glide.with(itemView.context) + .load(item.groupProfileImageUrl) + .placeholder(R.drawable.base_profile_image) + .into(ivGroupImage) + + tvGroupName.text = item.groupName + tvIntroduction.text = item.groupIntroduce + tvMemberCount.text = item.memberCnt.toString() + tvRestaurantCount.text = item.restaurantCnt.toString() + } + } + } + + override fun onBindViewHolder(holder: GroupWithSearchPreviewViewHolder, position: Int) { + val item = getItem(position) + if (item != null) { + holder.bind(item) + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): GroupWithSearchPreviewViewHolder { + val binding = ItemSearchGroupBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return GroupWithSearchPreviewViewHolder(binding) + } + +} \ No newline at end of file diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryPagerAdapter.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryPagerAdapter.kt similarity index 74% rename from presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryPagerAdapter.kt rename to presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryPagerAdapter.kt index 5923fc6b..820017ac 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryPagerAdapter.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryPagerAdapter.kt @@ -1,7 +1,10 @@ -package org.gdsc.presentation.view.allsearch +package org.gdsc.presentation.view.allsearch.adapter import androidx.fragment.app.Fragment import androidx.viewpager2.adapter.FragmentStateAdapter +import org.gdsc.presentation.view.allsearch.SearchCategoryAllFragment +import org.gdsc.presentation.view.allsearch.SearchCategoryGroupFragment +import org.gdsc.presentation.view.allsearch.SearchCategoryRestaurantFragment class SearchCategoryPagerAdapter( fragment: Fragment, diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryRestaurantAdapter.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryRestaurantAdapter.kt similarity index 85% rename from presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryRestaurantAdapter.kt rename to presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryRestaurantAdapter.kt index 471e2cab..90b39ed4 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/SearchCategoryRestaurantAdapter.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryRestaurantAdapter.kt @@ -1,7 +1,8 @@ -package org.gdsc.presentation.view.allsearch +package org.gdsc.presentation.view.allsearch.adapter import android.view.LayoutInflater import android.view.ViewGroup +import androidx.annotation.IntRange import androidx.paging.PagingDataAdapter import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView @@ -11,7 +12,11 @@ import org.gdsc.presentation.R import org.gdsc.presentation.databinding.ItemSearchRestaurantBinding class SearchCategoryRestaurantAdapter - : PagingDataAdapter(diffCallback) { + : PagingDataAdapter( + diffCallback +) { + + private var maxItemCount = Int.MAX_VALUE companion object { val diffCallback = object : DiffUtil.ItemCallback() { override fun areItemsTheSame( @@ -65,6 +70,10 @@ class SearchCategoryRestaurantAdapter viewType: Int ): RestaurantsWithSearchViewHolder { val binding = ItemSearchRestaurantBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return SearchCategoryRestaurantAdapter.RestaurantsWithSearchViewHolder(binding) + return RestaurantsWithSearchViewHolder(binding) + } + + fun setMaxItemCount(@IntRange(from = 1, to = Long.MAX_VALUE) maxItemCount: Int) { + this.maxItemCount = maxItemCount } } \ No newline at end of file diff --git a/presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryRestaurantPreviewAdapter.kt b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryRestaurantPreviewAdapter.kt new file mode 100644 index 00000000..4bf2c7b1 --- /dev/null +++ b/presentation/src/main/java/org/gdsc/presentation/view/allsearch/adapter/SearchCategoryRestaurantPreviewAdapter.kt @@ -0,0 +1,74 @@ +package org.gdsc.presentation.view.allsearch.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import org.gdsc.domain.model.RegisteredRestaurant +import org.gdsc.presentation.R +import org.gdsc.presentation.databinding.ItemSearchRestaurantBinding + +class SearchCategoryRestaurantPreviewAdapter + : ListAdapter( + diffCallback +) { + + companion object { + val diffCallback = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: RegisteredRestaurant, + newItem: RegisteredRestaurant + ): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame( + oldItem: RegisteredRestaurant, + newItem: RegisteredRestaurant + ): Boolean { + return oldItem == newItem + } + } + } + + class RestaurantsWithSearchPreviewViewHolder( + private val binding: ItemSearchRestaurantBinding, + ): RecyclerView.ViewHolder(binding.root) { + fun bind(item: RegisteredRestaurant) { + binding.run { + Glide.with(itemView.context) + .load(item.userProfileImageUrl) + .placeholder(R.drawable.base_profile_image) + .into(userProfileImage) + + userName.text = item.userNickName + + Glide.with(itemView.context) + .load(item.restaurantImageUrl) + .placeholder(R.drawable.base_profile_image) + .into(restaurantImage) + + restaurantCategory.text = item.category + restaurantName.text = item.name + } + } + } + + override fun onBindViewHolder(holder: RestaurantsWithSearchPreviewViewHolder, position: Int) { + val item = getItem(position) + if (item != null) { + holder.bind(item) + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): RestaurantsWithSearchPreviewViewHolder { + val binding = ItemSearchRestaurantBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return RestaurantsWithSearchPreviewViewHolder(binding) + } + +} \ No newline at end of file diff --git a/presentation/src/main/res/layout/fragment_all_search.xml b/presentation/src/main/res/layout/fragment_all_search.xml index 4297ce00..4e73cb01 100644 --- a/presentation/src/main/res/layout/fragment_all_search.xml +++ b/presentation/src/main/res/layout/fragment_all_search.xml @@ -1,8 +1,8 @@ + android:layout_height="match_parent"> + + + + + + + + \ No newline at end of file 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 4680e67d..99ac8c7d 100644 --- a/presentation/src/main/res/layout/fragment_search_category_all.xml +++ b/presentation/src/main/res/layout/fragment_search_category_all.xml @@ -1,89 +1,118 @@ - - + android:layout_height="match_parent"> - + + - - + + - - + + - - - \ No newline at end of file + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/item_search_group.xml b/presentation/src/main/res/layout/item_search_group.xml new file mode 100644 index 00000000..4ace9436 --- /dev/null +++ b/presentation/src/main/res/layout/item_search_group.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/item_search_restaurant.xml b/presentation/src/main/res/layout/item_search_restaurant.xml index 23650d5a..bd66f033 100644 --- a/presentation/src/main/res/layout/item_search_restaurant.xml +++ b/presentation/src/main/res/layout/item_search_restaurant.xml @@ -1,7 +1,8 @@