Skip to content

Commit c36009e

Browse files
authored
[MERGE] #21 -> develop
[UI/#21] 검색뷰 / UI 구현
2 parents f1558d5 + c9fa8b2 commit c36009e

File tree

17 files changed

+788
-12
lines changed

17 files changed

+788
-12
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@
5151
android:exported="false"
5252
android:screenOrientation="portrait" />
5353

54+
<activity
55+
android:name="co.orange.presentation.search.SearchActivity"
56+
android:exported="false"
57+
android:screenOrientation="portrait" />
58+
5459
</application>
5560

5661
</manifest>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package co.orange.domain.entity.response
2+
3+
data class SearchModel(
4+
val topSearchedList: List<String>,
5+
val recentViewedList: List<ProductModel>,
6+
)

presentation/src/main/java/co/orange/presentation/main/home/HomeFragment.kt

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package co.orange.presentation.main.home
22

3+
import android.content.Intent
34
import android.os.Bundle
45
import android.view.View
56
import androidx.fragment.app.activityViewModels
67
import androidx.recyclerview.widget.GridLayoutManager
78
import co.orange.domain.entity.response.ProductModel
89
import co.orange.presentation.detail.DetailActivity
910
import co.orange.presentation.main.home.HomeAdapter.Companion.VIEW_TYPE_BANNER
11+
import co.orange.presentation.search.SearchActivity
1012
import dagger.hilt.android.AndroidEntryPoint
1113
import kr.genti.core.base.BaseFragment
1214
import kr.genti.core.extension.dpToPx
@@ -32,6 +34,7 @@ class HomeFragment() : BaseFragment<FragmentHomeBinding>(R.layout.fragment_home)
3234
initView()
3335
initAdapter()
3436
initSellBtnListener()
37+
initSearchBtnListener()
3538
setGridRecyclerView()
3639
setRecyclerViewDeco()
3740
setItemList()
@@ -51,7 +54,7 @@ class HomeFragment() : BaseFragment<FragmentHomeBinding>(R.layout.fragment_home)
5154
binding.rvHome.adapter = adapter
5255
}
5356

54-
//TODO: 버튼 리스너 설정
57+
// TODO: 버튼 리스너 설정
5558
private fun initBannerClickListener(unit: Unit) {}
5659

5760
private fun initProductClickListener(item: ProductModel) {
@@ -67,26 +70,36 @@ class HomeFragment() : BaseFragment<FragmentHomeBinding>(R.layout.fragment_home)
6770
}
6871
}
6972

73+
private fun initSearchBtnListener() {
74+
binding.btnSearch.setOnSingleClickListener {
75+
Intent(requireContext(), SearchActivity::class.java).apply {
76+
startActivity(this)
77+
}
78+
}
79+
}
80+
7081
private fun setGridRecyclerView() {
71-
binding.rvHome.layoutManager = GridLayoutManager(context, 2).apply {
72-
spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
73-
override fun getSpanSize(position: Int): Int {
74-
return when (adapter.getItemViewType(position)) {
75-
VIEW_TYPE_BANNER -> 2
76-
else -> 1
82+
binding.rvHome.layoutManager =
83+
GridLayoutManager(context, 2).apply {
84+
spanSizeLookup =
85+
object : GridLayoutManager.SpanSizeLookup() {
86+
override fun getSpanSize(position: Int): Int {
87+
return when (adapter.getItemViewType(position)) {
88+
VIEW_TYPE_BANNER -> 2
89+
else -> 1
90+
}
91+
}
7792
}
78-
}
7993
}
80-
}
8194
}
8295

8396
private fun setRecyclerViewDeco() {
8497
binding.rvHome.addItemDecoration(
8598
GridItemDecoration(
8699
spanCount = 2,
87100
spacing = 30.dpToPx(requireContext()),
88-
bottomPadding = 50.dpToPx(requireContext())
89-
)
101+
bottomPadding = 50.dpToPx(requireContext()),
102+
),
90103
)
91104
}
92105

@@ -98,4 +111,4 @@ class HomeFragment() : BaseFragment<FragmentHomeBinding>(R.layout.fragment_home)
98111
super.onDestroyView()
99112
_adapter = null
100113
}
101-
}
114+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package co.orange.presentation.search
2+
3+
import android.content.Context
4+
import android.os.Bundle
5+
import android.view.inputmethod.InputMethodManager
6+
import androidx.activity.viewModels
7+
import androidx.core.view.isVisible
8+
import androidx.core.widget.doAfterTextChanged
9+
import androidx.lifecycle.lifecycleScope
10+
import co.orange.domain.entity.response.ProductModel
11+
import co.orange.presentation.detail.DetailActivity
12+
import dagger.hilt.android.AndroidEntryPoint
13+
import kotlinx.coroutines.Job
14+
import kotlinx.coroutines.delay
15+
import kotlinx.coroutines.launch
16+
import kr.genti.core.base.BaseActivity
17+
import kr.genti.core.extension.setOnSingleClickListener
18+
import kr.genti.presentation.R
19+
import kr.genti.presentation.databinding.ActivitySearchBinding
20+
21+
@AndroidEntryPoint
22+
class SearchActivity :
23+
BaseActivity<ActivitySearchBinding>(R.layout.activity_search) {
24+
private val viewModel by viewModels<SearchViewModel>()
25+
26+
private var _keywordAdapter: SearchWordAdapter? = null
27+
val keywordAdapter
28+
get() = requireNotNull(_keywordAdapter) { getString(R.string.adapter_not_initialized_error_msg) }
29+
30+
private var _recentAdapter: SearchItemAdapter? = null
31+
val recentAdapter
32+
get() = requireNotNull(_recentAdapter) { getString(R.string.adapter_not_initialized_error_msg) }
33+
34+
private var _resultAdapter: SearchItemAdapter? = null
35+
val resultAdapter
36+
get() = requireNotNull(_resultAdapter) { getString(R.string.adapter_not_initialized_error_msg) }
37+
38+
private var searchJob: Job? = null
39+
40+
override fun onCreate(savedInstanceState: Bundle?) {
41+
super.onCreate(savedInstanceState)
42+
43+
initFocusToEditText()
44+
initBackBtnListener()
45+
initEmptyBtnListener()
46+
initKeywordAdapter()
47+
initRecentAdapter()
48+
initResultAdapter()
49+
setDebounceSearch()
50+
setKeywordList()
51+
setRecentList()
52+
}
53+
54+
private fun initFocusToEditText() {
55+
val inputMethodManager =
56+
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
57+
binding.etSearch.requestFocus()
58+
inputMethodManager.showSoftInput(
59+
binding.etSearch,
60+
InputMethodManager.SHOW_IMPLICIT,
61+
)
62+
}
63+
64+
private fun initBackBtnListener() {
65+
binding.btnBack.setOnSingleClickListener { finish() }
66+
}
67+
68+
private fun initEmptyBtnListener() {
69+
binding.btnEmpty.setOnSingleClickListener {
70+
binding.etSearch.setText("")
71+
}
72+
}
73+
74+
private fun initKeywordAdapter() {
75+
_keywordAdapter =
76+
SearchWordAdapter(
77+
keywordClick = ::initKeywordClickListener,
78+
)
79+
binding.rvSearchWord.adapter = keywordAdapter
80+
}
81+
82+
private fun initKeywordClickListener(keyword: String) {
83+
binding.etSearch.setText(keyword)
84+
}
85+
86+
private fun initRecentAdapter() {
87+
_recentAdapter =
88+
SearchItemAdapter(
89+
itemClick = ::initItemClickListener,
90+
)
91+
binding.rvSearchRecent.adapter = recentAdapter
92+
}
93+
94+
private fun initResultAdapter() {
95+
_resultAdapter =
96+
SearchItemAdapter(
97+
itemClick = ::initItemClickListener,
98+
)
99+
binding.rvSearchResult.adapter = resultAdapter
100+
}
101+
102+
private fun initItemClickListener(item: ProductModel) {
103+
DetailActivity.createIntent(this, item.productId, item.imgUrl, item.originPrice, item.salePrice)
104+
.apply { startActivity(this) }
105+
}
106+
107+
private fun setDebounceSearch() {
108+
binding.etSearch.doAfterTextChanged { text ->
109+
searchJob?.cancel()
110+
binding.layoutBeforeSearch.isVisible = text.isNullOrBlank()
111+
if (!text.isNullOrBlank()) {
112+
searchJob =
113+
lifecycleScope.launch {
114+
delay(DEBOUNCE_TIME)
115+
text.toString().let { text ->
116+
// TODO : 검색
117+
binding.layoutAfterSearch.isVisible = true
118+
binding.tvSearchedText.text = getString(R.string.add_quotation, text)
119+
resultAdapter.addList(viewModel.mockItemList)
120+
}
121+
}
122+
} else {
123+
binding.layoutAfterSearch.isVisible = false
124+
}
125+
}
126+
}
127+
128+
private fun setKeywordList() {
129+
keywordAdapter.addList(viewModel.mockSearchModel.topSearchedList)
130+
}
131+
132+
private fun setRecentList() {
133+
recentAdapter.addList(viewModel.mockSearchModel.recentViewedList)
134+
}
135+
136+
override fun onDestroy() {
137+
super.onDestroy()
138+
_keywordAdapter = null
139+
_recentAdapter = null
140+
_resultAdapter = null
141+
}
142+
143+
companion object {
144+
const val DEBOUNCE_TIME = 500L
145+
}
146+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package co.orange.presentation.search
2+
3+
import android.view.LayoutInflater
4+
import android.view.ViewGroup
5+
import androidx.recyclerview.widget.ListAdapter
6+
import co.orange.domain.entity.response.ProductModel
7+
import kr.genti.core.util.ItemDiffCallback
8+
import kr.genti.presentation.databinding.ItemSearchProductBinding
9+
10+
class SearchItemAdapter(
11+
private val itemClick: (ProductModel) -> (Unit),
12+
) : ListAdapter<ProductModel, SearchItemViewHolder>(diffUtil) {
13+
override fun onCreateViewHolder(
14+
parent: ViewGroup,
15+
viewType: Int,
16+
): SearchItemViewHolder {
17+
val inflater by lazy { LayoutInflater.from(parent.context) }
18+
val binding: ItemSearchProductBinding =
19+
ItemSearchProductBinding.inflate(inflater, parent, false)
20+
return SearchItemViewHolder(binding, itemClick)
21+
}
22+
23+
override fun onBindViewHolder(
24+
holder: SearchItemViewHolder,
25+
position: Int,
26+
) {
27+
val item = getItem(position) ?: return
28+
holder.onBind(item)
29+
}
30+
31+
fun addList(newItems: List<ProductModel>) {
32+
val currentItems = currentList.toMutableList()
33+
currentItems.addAll(newItems)
34+
submitList(currentItems)
35+
}
36+
37+
companion object {
38+
private val diffUtil =
39+
ItemDiffCallback<ProductModel>(
40+
onItemsTheSame = { old, new -> old.productId == new.productId },
41+
onContentsTheSame = { old, new -> old == new },
42+
)
43+
}
44+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package co.orange.presentation.search
2+
3+
import android.graphics.Paint
4+
import androidx.recyclerview.widget.RecyclerView
5+
import co.orange.domain.entity.response.ProductModel
6+
import coil.load
7+
import kr.genti.core.extension.breakLines
8+
import kr.genti.core.extension.setNumberForm
9+
import kr.genti.core.extension.setOnSingleClickListener
10+
import kr.genti.core.extension.setOverThousand
11+
import kr.genti.presentation.databinding.ItemSearchProductBinding
12+
13+
class SearchItemViewHolder(
14+
val binding: ItemSearchProductBinding,
15+
val itemClick: (ProductModel) -> (Unit),
16+
) :
17+
RecyclerView.ViewHolder(binding.root) {
18+
fun onBind(item: ProductModel) {
19+
with(binding) {
20+
tvSearchItemTitle.text = item.name.breakLines()
21+
ivSearchItem.load(item.imgUrl)
22+
tvSearchItemRealPrice.apply {
23+
text = item.originPrice.setNumberForm()
24+
setPaintFlags(Paint.STRIKE_THRU_TEXT_FLAG)
25+
}
26+
tvSearchItemNowPrice.text = item.salePrice.setNumberForm()
27+
tvSearchItemLike.text = item.interestCount.setOverThousand()
28+
29+
root.setOnSingleClickListener {
30+
itemClick(item)
31+
}
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)