Skip to content

Commit 3d88aa8

Browse files
authored
Merge pull request #537 from projects200/refactor/compose-feed-list-536
[Refactor] 피드 목록 화면 Compose 전환 #536
2 parents 4280aee + 4f876f7 commit 3d88aa8

8 files changed

Lines changed: 420 additions & 515 deletions

File tree

feature/feed/src/main/java/com/project200/undabang/feature/feed/list/FeedListAdapter.kt

Lines changed: 0 additions & 99 deletions
This file was deleted.

feature/feed/src/main/java/com/project200/undabang/feature/feed/list/FeedListFragment.kt

Lines changed: 64 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,87 @@
11
package com.project200.undabang.feature.feed.list
22

33
import android.os.Bundle
4+
import android.view.LayoutInflater
45
import android.view.View
6+
import android.view.ViewGroup
7+
import androidx.compose.runtime.getValue
8+
import androidx.compose.ui.platform.ComposeView
9+
import androidx.fragment.app.Fragment
510
import androidx.fragment.app.viewModels
11+
import androidx.lifecycle.Lifecycle
12+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
13+
import androidx.lifecycle.lifecycleScope
14+
import androidx.lifecycle.repeatOnLifecycle
615
import androidx.navigation.fragment.findNavController
7-
import androidx.recyclerview.widget.LinearLayoutManager
8-
import androidx.recyclerview.widget.RecyclerView
9-
import com.project200.domain.model.ExerciseType
10-
import com.project200.presentation.base.BindingFragment
16+
import com.project200.presentation.compose.applyAppTheme
1117
import com.project200.presentation.compose.components.feedback.UndabangBottomSheet
1218
import com.project200.presentation.utils.collectToast
13-
import com.project200.undabang.feature.feed.R
14-
import com.project200.undabang.feature.feed.databinding.FragmentFeedListBinding
1519
import dagger.hilt.android.AndroidEntryPoint
20+
import kotlinx.coroutines.launch
1621

1722
@AndroidEntryPoint
18-
class FeedListFragment : BindingFragment<FragmentFeedListBinding>(R.layout.fragment_feed_list) {
23+
class FeedListFragment : Fragment() {
1924
private val viewModel: FeedListViewModel by viewModels()
20-
private lateinit var feedAdapter: FeedListAdapter
2125

22-
override fun getViewBinding(view: View): FragmentFeedListBinding {
23-
return FragmentFeedListBinding.bind(view)
24-
}
26+
override fun onCreateView(
27+
inflater: LayoutInflater,
28+
container: ViewGroup?,
29+
savedInstanceState: Bundle?,
30+
): View =
31+
ComposeView(requireContext()).apply {
32+
applyAppTheme {
33+
val feeds by viewModel.feedList.collectAsStateWithLifecycle()
34+
val selectedType by viewModel.selectedType.collectAsStateWithLifecycle()
35+
val isLoading by viewModel.isLoading.collectAsStateWithLifecycle()
36+
val isEmpty by viewModel.isEmpty.collectAsStateWithLifecycle()
37+
38+
FeedListScreen(
39+
feeds = feeds,
40+
selectedTypeName = selectedType?.name,
41+
isLoading = isLoading,
42+
isEmpty = isEmpty,
43+
onFeedClick = { feedId ->
44+
findNavController().navigate(
45+
FeedListFragmentDirections.actionFeedListFragmentToFeedDetailFragment(feedId),
46+
)
47+
},
48+
onAddClick = {
49+
findNavController().navigate(
50+
FeedListFragmentDirections.actionFeedListFragmentToFeedFormFragment(),
51+
)
52+
},
53+
onCategoryClick = viewModel::requestShowCategoryBottomSheet,
54+
onClearCategory = viewModel::clearType,
55+
onLoadMore = {
56+
if (viewModel.canLoadMore()) {
57+
viewModel.loadFeeds()
58+
}
59+
},
60+
)
61+
}
62+
}
2563

2664
override fun onViewCreated(
2765
view: View,
2866
savedInstanceState: Bundle?,
2967
) {
3068
super.onViewCreated(view, savedInstanceState)
31-
initAdapter()
32-
initToolbar()
33-
initView()
34-
initObserver()
3569
observeRefreshSignal()
70+
collectToast(viewModel.toastEvent)
71+
72+
viewLifecycleOwner.lifecycleScope.launch {
73+
repeatOnLifecycle(Lifecycle.State.STARTED) {
74+
viewModel.showCategoryBottomSheet.collect { types ->
75+
UndabangBottomSheet.showSelection(
76+
fragmentManager = parentFragmentManager,
77+
items = types.map { it.name },
78+
) { selectedName ->
79+
val selected = types.find { it.name == selectedName }
80+
viewModel.selectType(selected)
81+
}
82+
}
83+
}
84+
}
3685
}
3786

3887
private fun observeRefreshSignal() {
@@ -45,131 +94,6 @@ class FeedListFragment : BindingFragment<FragmentFeedListBinding>(R.layout.fragm
4594
}
4695
}
4796

48-
private fun initAdapter() {
49-
feedAdapter =
50-
FeedListAdapter(
51-
onItemClick = { feed ->
52-
findNavController().navigate(
53-
FeedListFragmentDirections.actionFeedListFragmentToFeedDetailFragment(feed.feedId),
54-
)
55-
},
56-
)
57-
}
58-
59-
private fun initToolbar() {
60-
binding.baseToolbar.apply {
61-
setTitle(getString(R.string.feed_title))
62-
showBackButton(false)
63-
setSubButton(R.drawable.ic_feed_add) {
64-
findNavController().navigate(
65-
FeedListFragmentDirections.actionFeedListFragmentToFeedFormFragment(),
66-
)
67-
}
68-
setSecondarySubButton(R.drawable.ic_category) {
69-
showCategoryBottomSheet()
70-
}
71-
}
72-
}
73-
74-
private fun showCategoryBottomSheet() {
75-
viewModel.requestShowCategoryBottomSheet()
76-
}
77-
78-
private fun displayCategoryBottomSheet(exerciseTypes: List<ExerciseType>) {
79-
val names = exerciseTypes.map { it.name }
80-
UndabangBottomSheet.showSelection(
81-
fragmentManager = parentFragmentManager,
82-
items = names,
83-
) { selectedName ->
84-
val selectedType = exerciseTypes.find { it.name == selectedName }
85-
viewModel.selectType(selectedType)
86-
}
87-
viewModel.onCategoryBottomSheetShown()
88-
}
89-
90-
private fun initView() {
91-
binding.swipeRefreshLayout.setOnRefreshListener {
92-
viewModel.loadFeeds(isRefresh = true)
93-
}
94-
95-
binding.feedListRv.apply {
96-
adapter = feedAdapter
97-
layoutManager = LinearLayoutManager(context)
98-
addOnScrollListener(
99-
object : RecyclerView.OnScrollListener() {
100-
override fun onScrolled(
101-
recyclerView: RecyclerView,
102-
dx: Int,
103-
dy: Int,
104-
) {
105-
super.onScrolled(recyclerView, dx, dy)
106-
107-
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
108-
val totalItemCount = layoutManager.itemCount
109-
val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
110-
111-
if (viewModel.canLoadMore() && totalItemCount <= (lastVisibleItem + 5)) {
112-
viewModel.loadFeeds()
113-
}
114-
}
115-
},
116-
)
117-
}
118-
}
119-
120-
private fun initObserver() {
121-
viewModel.feedList.observe(viewLifecycleOwner) { feeds ->
122-
feedAdapter.submitList(feeds)
123-
}
124-
125-
viewModel.showEmptyView.observe(viewLifecycleOwner) { showEmpty ->
126-
binding.emptyTv.visibility = if (showEmpty) View.VISIBLE else View.GONE
127-
}
128-
129-
viewModel.showCategoryBottomSheet.observe(viewLifecycleOwner) { exerciseTypes ->
130-
if (!exerciseTypes.isNullOrEmpty()) {
131-
displayCategoryBottomSheet(exerciseTypes)
132-
}
133-
}
134-
135-
viewModel.selectedType.observe(viewLifecycleOwner) { type ->
136-
binding.baseToolbar.apply {
137-
if (type != null) {
138-
setTitle(type.name)
139-
setSecondarySubButton(null)
140-
showBackButton(true) { viewModel.clearType() }
141-
} else {
142-
setTitle(getString(R.string.feed_title))
143-
setSecondarySubButton(R.drawable.ic_category) {
144-
showCategoryBottomSheet()
145-
}
146-
showBackButton(false)
147-
}
148-
}
149-
}
150-
151-
viewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
152-
val isInitialLoading = isLoading && feedAdapter.itemCount == 0
153-
if (isInitialLoading) {
154-
binding.shimmerLayout.visibility = View.VISIBLE
155-
binding.shimmerLayout.startShimmer()
156-
binding.swipeRefreshLayout.visibility = View.GONE
157-
binding.emptyTv.visibility = View.GONE
158-
} else {
159-
binding.shimmerLayout.stopShimmer()
160-
binding.shimmerLayout.visibility = View.GONE
161-
binding.swipeRefreshLayout.visibility = View.VISIBLE
162-
binding.swipeRefreshLayout.isRefreshing = false
163-
}
164-
}
165-
166-
collectToast(viewModel.toastEvent)
167-
168-
viewModel.currentMemberId.observe(viewLifecycleOwner) { memberId ->
169-
feedAdapter.setCurrentMemberId(memberId)
170-
}
171-
}
172-
17397
companion object {
17498
const val REFRESH_KEY = "feed_list_refresh"
17599
}

0 commit comments

Comments
 (0)