Skip to content

Commit ff44e44

Browse files
committed
feature: 찜 목록 불러오는 로직 구현
- 찜 해제 시 postBoardLike api 호출로 상태 반영 - 목록에서 찜 해제된 게시글 제거
1 parent 73a56f2 commit ff44e44

File tree

5 files changed

+312
-1
lines changed

5 files changed

+312
-1
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.catchmate.presentation.interaction
2+
3+
interface OnPostItemAllRemovedListener {
4+
fun onPostItemAllRemoved()
5+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.catchmate.presentation.interaction
2+
3+
interface OnPostItemToggleClickListener {
4+
fun onPostItemToggleClicked(
5+
boardId: Long,
6+
position: Int,
7+
)
8+
}

CatchMate/presentation/src/main/java/com/catchmate/presentation/view/favorite/FavoriteFragment.kt

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,39 @@
11
package com.catchmate.presentation.view.favorite
22

33
import android.os.Bundle
4+
import android.util.Log
45
import android.view.LayoutInflater
56
import android.view.View
67
import android.view.ViewGroup
78
import androidx.fragment.app.Fragment
9+
import androidx.fragment.app.viewModels
10+
import androidx.navigation.NavOptions
11+
import androidx.navigation.fragment.findNavController
12+
import androidx.recyclerview.widget.LinearLayoutManager
813
import com.catchmate.presentation.R
914
import com.catchmate.presentation.databinding.FragmentFavoriteBinding
15+
import com.catchmate.presentation.interaction.OnPostItemAllRemovedListener
16+
import com.catchmate.presentation.interaction.OnPostItemClickListener
17+
import com.catchmate.presentation.interaction.OnPostItemToggleClickListener
18+
import com.catchmate.presentation.viewmodel.FavoriteViewModel
19+
import com.catchmate.presentation.viewmodel.LocalDataViewMdoel
20+
import dagger.hilt.android.AndroidEntryPoint
1021

11-
class FavoriteFragment : Fragment() {
22+
@AndroidEntryPoint
23+
class FavoriteFragment :
24+
Fragment(),
25+
OnPostItemClickListener,
26+
OnPostItemToggleClickListener,
27+
OnPostItemAllRemovedListener {
1228
private var _binding: FragmentFavoriteBinding? = null
1329
val binding get() = _binding!!
1430

31+
private val localDataViewModel: LocalDataViewMdoel by viewModels()
32+
private val favoriteViewModel: FavoriteViewModel by viewModels()
33+
34+
private lateinit var accessToken: String
35+
private lateinit var refreshToken: String
36+
1537
override fun onCreateView(
1638
inflater: LayoutInflater,
1739
container: ViewGroup?,
@@ -26,18 +48,106 @@ class FavoriteFragment : Fragment() {
2648
savedInstanceState: Bundle?,
2749
) {
2850
super.onViewCreated(view, savedInstanceState)
51+
getTokens()
2952
initHeader()
53+
initRecyclerView()
3054
}
3155

3256
override fun onDestroyView() {
3357
super.onDestroyView()
3458
_binding = null
3559
}
3660

61+
private fun getTokens() {
62+
localDataViewModel.getAccessToken()
63+
localDataViewModel.getRefreshToken()
64+
localDataViewModel.accessToken.observe(viewLifecycleOwner) { accessToken ->
65+
if (accessToken != null) {
66+
this.accessToken = accessToken
67+
initViewModel()
68+
}
69+
}
70+
localDataViewModel.refreshToken.observe(viewLifecycleOwner) { refreshToken ->
71+
if (refreshToken != null) {
72+
this.refreshToken = refreshToken
73+
}
74+
}
75+
}
76+
3777
private fun initHeader() {
3878
binding.layoutHeaderFavorite.apply {
3979
tvHeaderTextTitle.setText(R.string.favorite_title)
4080
imgbtnHeaderTextBack.visibility = View.GONE
4181
}
4282
}
83+
84+
private fun initViewModel() {
85+
favoriteViewModel.getBoardLikedList()
86+
favoriteViewModel.boardListResponse.observe(viewLifecycleOwner) { boardLikedList ->
87+
if (boardLikedList.isNotEmpty()) {
88+
binding.layoutFavoriteNoList.visibility = View.GONE
89+
binding.rvFavoritePost.visibility = View.VISIBLE
90+
val adapter = binding.rvFavoritePost.adapter as FavoritePostAdapter
91+
adapter.updateLikedList(boardLikedList)
92+
} else {
93+
binding.layoutFavoriteNoList.visibility = View.VISIBLE
94+
binding.rvFavoritePost.visibility = View.GONE
95+
}
96+
}
97+
98+
favoriteViewModel.navigateToLogin.observe(viewLifecycleOwner) { isTrue ->
99+
if (isTrue) {
100+
val navOptions =
101+
NavOptions
102+
.Builder()
103+
.setPopUpTo(R.id.favoriteFragment, true)
104+
.build()
105+
findNavController().navigate(R.id.action_favoriteFragment_to_loginFragment, null, navOptions)
106+
}
107+
}
108+
109+
favoriteViewModel.errorMessage.observe(viewLifecycleOwner) { errorMessage ->
110+
errorMessage?.let {
111+
Log.e("Reissue Error", it)
112+
}
113+
}
114+
}
115+
116+
private fun initRecyclerView() {
117+
binding.rvFavoritePost.apply {
118+
adapter =
119+
FavoritePostAdapter(
120+
requireContext(),
121+
layoutInflater,
122+
this@FavoriteFragment,
123+
this@FavoriteFragment,
124+
this@FavoriteFragment,
125+
)
126+
layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
127+
}
128+
}
129+
130+
override fun onPostItemClicked(boardId: Long) {
131+
val bundle = Bundle()
132+
bundle.putLong("boardId", boardId)
133+
findNavController().navigate(R.id.action_favoriteFragment_to_readPostFragment, bundle)
134+
}
135+
136+
override fun onPostItemToggleClicked(
137+
boardId: Long,
138+
position: Int,
139+
) {
140+
favoriteViewModel.postBoardLike(boardId, FAVORITE_CANCEL_FLAG)
141+
val adapter = binding.rvFavoritePost.adapter as FavoritePostAdapter
142+
adapter.removeUnlikedPost(position)
143+
}
144+
145+
override fun onPostItemAllRemoved() {
146+
binding.rvFavoritePost.visibility = View.GONE
147+
binding.layoutFavoriteNoList.visibility = View.VISIBLE
148+
}
149+
150+
companion object {
151+
const val FAVORITE_CANCEL_FLAG = 0
152+
}
43153
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package com.catchmate.presentation.view.favorite
2+
3+
import android.content.Context
4+
import android.util.Log
5+
import android.view.LayoutInflater
6+
import android.view.View
7+
import android.view.ViewGroup
8+
import android.widget.ImageView
9+
import android.widget.TextView
10+
import android.widget.ToggleButton
11+
import androidx.cardview.widget.CardView
12+
import androidx.core.content.ContextCompat
13+
import androidx.recyclerview.widget.RecyclerView
14+
import com.catchmate.domain.model.BoardListResponse
15+
import com.catchmate.presentation.R
16+
import com.catchmate.presentation.databinding.ItemHomePostBinding
17+
import com.catchmate.presentation.interaction.OnPostItemAllRemovedListener
18+
import com.catchmate.presentation.interaction.OnPostItemClickListener
19+
import com.catchmate.presentation.interaction.OnPostItemToggleClickListener
20+
import com.catchmate.presentation.util.DateUtils
21+
import com.catchmate.presentation.util.ResourceUtil
22+
import kotlinx.coroutines.runBlocking
23+
24+
class FavoritePostAdapter(
25+
private val context: Context,
26+
private val layoutInflater: LayoutInflater,
27+
private val onPostItemClickListener: OnPostItemClickListener,
28+
private val onPostItemToggleClickListener: OnPostItemToggleClickListener,
29+
private val onPostItemAllRemovedListener: OnPostItemAllRemovedListener,
30+
) : RecyclerView.Adapter<FavoritePostAdapter.FavoritePostViewHolder>() {
31+
private var likedList: MutableList<BoardListResponse> = mutableListOf()
32+
33+
fun updateLikedList(newList: List<BoardListResponse>) {
34+
likedList.clear()
35+
newList.forEach { boardListResponse ->
36+
likedList.add(boardListResponse)
37+
}
38+
notifyDataSetChanged()
39+
}
40+
41+
fun removeUnlikedPost(position: Int) {
42+
runBlocking {
43+
likedList.removeAt(position)
44+
notifyItemRemoved(position)
45+
if (likedList.size == 0) {
46+
onPostItemAllRemovedListener.onPostItemAllRemoved()
47+
}
48+
}
49+
}
50+
51+
inner class FavoritePostViewHolder(
52+
itemBinding: ItemHomePostBinding,
53+
) : RecyclerView.ViewHolder(itemBinding.root) {
54+
val cvItemLayout: CardView = itemBinding.cvItemHomePost
55+
val tvItemCount: TextView = itemBinding.tvItemHomePostMemberCount
56+
val tvItemDate: TextView = itemBinding.tvItemHomePostDate
57+
val tvItemTime: TextView = itemBinding.tvItemHomePostTime
58+
val tvItemPlace: TextView = itemBinding.tvItemHomePostPlace
59+
val tvItemTitle: TextView = itemBinding.tvItemHomePostTitle
60+
val ivItemHomeTeamBg: ImageView = itemBinding.ivItemHomePostHomeTeamBg
61+
val ivItemAwayTeamBg: ImageView = itemBinding.ivItemHomePostAwayTeamBg
62+
val ivItemHomeTeamLogo: ImageView = itemBinding.ivItemHomePostHomeTeamLogo
63+
val ivItemAwayTeamLogo: ImageView = itemBinding.ivItemHomePostAwayTeamLogo
64+
val tbItemPostFavorite: ToggleButton = itemBinding.btnItemHomePostFavorite
65+
66+
init {
67+
tbItemPostFavorite.setOnCheckedChangeListener { _, _ ->
68+
val pos = absoluteAdapterPosition
69+
Log.e("Current Pos", pos.toString())
70+
onPostItemToggleClickListener.onPostItemToggleClicked(likedList[pos].boardId, pos)
71+
}
72+
73+
cvItemLayout.setOnClickListener {
74+
val pos = absoluteAdapterPosition
75+
Log.e("Current Pos", pos.toString())
76+
onPostItemClickListener.onPostItemClicked(likedList[pos].boardId)
77+
}
78+
}
79+
}
80+
81+
override fun onCreateViewHolder(
82+
parent: ViewGroup,
83+
viewType: Int,
84+
): FavoritePostViewHolder {
85+
val itemBinding = ItemHomePostBinding.inflate(layoutInflater)
86+
itemBinding.root.layoutParams =
87+
ViewGroup.LayoutParams(
88+
ViewGroup.LayoutParams.MATCH_PARENT,
89+
ViewGroup.LayoutParams.WRAP_CONTENT,
90+
)
91+
return FavoritePostViewHolder(itemBinding)
92+
}
93+
94+
override fun getItemCount(): Int = likedList.size
95+
96+
override fun onBindViewHolder(
97+
holder: FavoritePostViewHolder,
98+
position: Int,
99+
) {
100+
val board = likedList[position]
101+
holder.apply {
102+
tbItemPostFavorite.isChecked = true
103+
tbItemPostFavorite.visibility = View.VISIBLE
104+
105+
if (board.currentPerson == board.maxPerson) {
106+
tvItemCount.text = "${board.currentPerson}/${board.maxPerson} 마감"
107+
tvItemCount.setBackgroundResource(R.drawable.shape_all_rect_r12_grey100)
108+
tvItemCount.setTextColor(ContextCompat.getColor(context, R.color.grey500))
109+
} else {
110+
tvItemCount.text = "${board.currentPerson}/${board.maxPerson}"
111+
tvItemCount.setBackgroundResource(R.drawable.shape_all_rect_r12_brand50)
112+
tvItemCount.setTextColor(ContextCompat.getColor(context, R.color.brand500))
113+
}
114+
115+
val dateTimePair = DateUtils.formatISODateTime(board.gameDate)
116+
tvItemDate.text = dateTimePair.first
117+
tvItemTime.text = dateTimePair.second
118+
tvItemPlace.text = board.location
119+
tvItemTitle.text = board.title
120+
121+
val isCheerTeam = board.homeTeam == board.cheerTeam
122+
123+
ResourceUtil.setTeamViewResources(board.homeTeam, isCheerTeam, ivItemHomeTeamBg, ivItemHomeTeamLogo, "home", context)
124+
ResourceUtil.setTeamViewResources(board.awayTeam, !isCheerTeam, ivItemAwayTeamBg, ivItemAwayTeamLogo, "home", context)
125+
}
126+
}
127+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.catchmate.presentation.viewmodel
2+
3+
import androidx.lifecycle.LiveData
4+
import androidx.lifecycle.MutableLiveData
5+
import androidx.lifecycle.ViewModel
6+
import androidx.lifecycle.viewModelScope
7+
import com.catchmate.domain.exception.ReissueFailureException
8+
import com.catchmate.domain.model.BoardListResponse
9+
import com.catchmate.domain.usecase.BoardLikeUseCase
10+
import dagger.hilt.android.lifecycle.HiltViewModel
11+
import kotlinx.coroutines.launch
12+
import javax.inject.Inject
13+
14+
@HiltViewModel
15+
class FavoriteViewModel
16+
@Inject
17+
constructor(
18+
private val boardLikeUseCase: BoardLikeUseCase,
19+
) : ViewModel() {
20+
private val _boardListResponse = MutableLiveData<List<BoardListResponse>>()
21+
val boardListResponse: LiveData<List<BoardListResponse>>
22+
get() = _boardListResponse
23+
24+
private val _errorMessage = MutableLiveData<String>()
25+
val errorMessage: LiveData<String>
26+
get() = _errorMessage
27+
28+
private val _navigateToLogin = MutableLiveData<Boolean>()
29+
val navigateToLogin: LiveData<Boolean>
30+
get() = _navigateToLogin
31+
32+
private val _boardLikeResponse = MutableLiveData<Int>()
33+
val boardLikeResponse: LiveData<Int>
34+
get() = _boardLikeResponse
35+
36+
fun postBoardLike(
37+
boardId: Long,
38+
flag: Int,
39+
) {
40+
viewModelScope.launch {
41+
_boardLikeResponse.value = boardLikeUseCase.postBoardLike(boardId, flag)
42+
}
43+
}
44+
45+
fun getBoardLikedList() {
46+
viewModelScope.launch {
47+
val result = boardLikeUseCase.getBoardLikedList()
48+
49+
result
50+
.onSuccess { boardLikedList ->
51+
_boardListResponse.value = boardLikedList
52+
}.onFailure { exception ->
53+
if (exception is ReissueFailureException) {
54+
_navigateToLogin.value = true
55+
} else {
56+
_errorMessage.value = exception.message
57+
}
58+
}
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)