Skip to content

Commit dd3c9de

Browse files
authored
🔀 #35 from boostcampwm-2022/feat/groupDetail
그룹 상세보기 페이지 구현
2 parents 6014d8f + 9e423f2 commit dd3c9de

21 files changed

+925
-3
lines changed

presentation/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ plugins {
44
id "kotlin-kapt"
55
id "dagger.hilt.android.plugin"
66
id "androidx.navigation.safeargs.kotlin"
7+
id "kotlin-parcelize"
78
}
89

910
android {

presentation/src/main/java/com/whyranoid/presentation/community/CommunityFragment.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,11 @@ internal class CommunityFragment :
6666
}
6767
}
6868
}
69+
70+
override fun onDestroyView() {
71+
// 뷰페이저 메모리 누수 해결
72+
binding.viewPager.adapter = null
73+
74+
super.onDestroyView()
75+
}
6976
}

presentation/src/main/java/com/whyranoid/presentation/community/CommunityItemFragment.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ import android.view.View
66
import androidx.core.view.isVisible
77
import androidx.fragment.app.viewModels
88
import androidx.lifecycle.lifecycleScope
9-
import com.google.android.material.snackbar.Snackbar
9+
import androidx.navigation.fragment.findNavController
1010
import com.whyranoid.presentation.R
1111
import com.whyranoid.presentation.base.BaseFragment
1212
import com.whyranoid.presentation.databinding.FragmentCommunityItemBinding
13+
import com.whyranoid.presentation.model.toGroupInfoUiModel
1314
import com.whyranoid.presentation.util.repeatWhenUiStarted
1415
import dagger.hilt.android.AndroidEntryPoint
1516
import kotlinx.coroutines.launch
@@ -62,8 +63,9 @@ internal class CommunityItemFragment :
6263
private fun handleEvent(event: Event) {
6364
when (event) {
6465
is Event.CategoryItemClick -> {
65-
Snackbar.make(binding.root, "${event.groupInfo.name} 클릭됨", Snackbar.LENGTH_SHORT)
66-
.show()
66+
val action =
67+
CommunityFragmentDirections.actionCommunityFragmentToGroupDetailFragment(event.groupInfo.toGroupInfoUiModel())
68+
findNavController().navigate(action)
6769
}
6870
}
6971
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.whyranoid.presentation.community.group.detail
2+
3+
sealed class Event {
4+
object RecruitButtonClick : Event()
5+
object ExitGroupButtonClick : Event()
6+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package com.whyranoid.presentation.community.group.detail
2+
3+
import android.os.Bundle
4+
import android.view.View
5+
import android.widget.Toast
6+
import androidx.fragment.app.viewModels
7+
import androidx.navigation.fragment.navArgs
8+
import com.google.android.material.snackbar.Snackbar
9+
import com.whyranoid.presentation.R
10+
import com.whyranoid.presentation.base.BaseFragment
11+
import com.whyranoid.presentation.databinding.FragmentGroupDetailBinding
12+
import com.whyranoid.presentation.util.repeatWhenUiStarted
13+
import dagger.hilt.android.AndroidEntryPoint
14+
15+
@AndroidEntryPoint
16+
internal class GroupDetailFragment :
17+
BaseFragment<FragmentGroupDetailBinding>(R.layout.fragment_group_detail) {
18+
19+
private val viewModel: GroupDetailViewModel by viewModels()
20+
private val groupDetailArgs by navArgs<GroupDetailFragmentArgs>()
21+
22+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
23+
super.onViewCreated(view, savedInstanceState)
24+
25+
setupMenu()
26+
handleEvent()
27+
setBindingData()
28+
setNotificationAdapter()
29+
}
30+
31+
private fun setupMenu() {
32+
with(binding.topAppBar) {
33+
// TODO : uid를 DataStore에서 가져올 수 있도록 변경
34+
if (groupDetailArgs.groupInfo.leader.name == "soopeach") {
35+
inflateMenu(R.menu.group_detail_menu)
36+
}
37+
38+
setOnMenuItemClickListener { menuItem ->
39+
when (menuItem.itemId) {
40+
R.id.setting_group -> {
41+
// TODO : BottomSheetDialog Material Theme 적용
42+
val dialog = GroupSettingDialog(
43+
// TODO : 그룹 수정으로 이동
44+
onEditButtonClickListener = {
45+
Toast.makeText(context, "그룹 수정하기", Toast.LENGTH_SHORT).show()
46+
},
47+
// TODO : 그룹 삭제
48+
onDeleteButtonClickListener = {
49+
Toast.makeText(context, "그룹 삭제하기", Toast.LENGTH_SHORT).show()
50+
}
51+
)
52+
53+
dialog.show(
54+
requireActivity().supportFragmentManager,
55+
GroupSettingDialog.TAG
56+
)
57+
58+
true
59+
}
60+
else -> {
61+
false
62+
}
63+
}
64+
}
65+
}
66+
}
67+
68+
private fun handleEvent() {
69+
repeatWhenUiStarted {
70+
viewModel.eventFlow.collect { event ->
71+
when (event) {
72+
// TODO : 홍보 글 쓰러가기
73+
Event.RecruitButtonClick -> {
74+
Snackbar.make(
75+
binding.root,
76+
getString(R.string.text_recruit),
77+
Snackbar.LENGTH_SHORT
78+
).show()
79+
}
80+
// TODO : 그룹 나가기
81+
Event.ExitGroupButtonClick -> {
82+
Snackbar.make(
83+
binding.root,
84+
getString(R.string.text_exit_group),
85+
Snackbar.LENGTH_SHORT
86+
).show()
87+
}
88+
}
89+
}
90+
}
91+
}
92+
93+
private fun setBindingData() {
94+
with(binding) {
95+
viewModel = viewModel
96+
groupInfo = groupDetailArgs.groupInfo
97+
// TODO : uid를 DataStore에서 가져올 수 있도록 변경
98+
// TODO : ViewModel로 옮기기
99+
isLeader = groupDetailArgs.groupInfo.leader.name == "soopeach"
100+
}
101+
}
102+
103+
// TODO : uid를 DataStore에서 가져올 수 있도록 변경
104+
private fun setNotificationAdapter() {
105+
val notificationAdapter = GroupNotificationAdapter("hsjeon")
106+
107+
binding.notificationRecyclerView.adapter = notificationAdapter
108+
repeatWhenUiStarted {
109+
viewModel.mergedNotifications.collect { notifications ->
110+
notificationAdapter.submitList(notifications)
111+
}
112+
}
113+
}
114+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.whyranoid.presentation.community.group.detail
2+
3+
import androidx.lifecycle.ViewModel
4+
import androidx.lifecycle.viewModelScope
5+
import com.whyranoid.domain.model.FinishNotification
6+
import com.whyranoid.domain.model.GroupNotification
7+
import com.whyranoid.domain.model.StartNotification
8+
import com.whyranoid.domain.usecase.GetGroupNotificationsUseCase
9+
import dagger.hilt.android.lifecycle.HiltViewModel
10+
import kotlinx.coroutines.flow.MutableSharedFlow
11+
import kotlinx.coroutines.flow.MutableStateFlow
12+
import kotlinx.coroutines.flow.SharedFlow
13+
import kotlinx.coroutines.flow.asSharedFlow
14+
import kotlinx.coroutines.flow.launchIn
15+
import kotlinx.coroutines.flow.onEach
16+
import kotlinx.coroutines.launch
17+
import javax.inject.Inject
18+
19+
@HiltViewModel
20+
class GroupDetailViewModel @Inject constructor(
21+
getGroupNotificationsUseCase: GetGroupNotificationsUseCase
22+
) : ViewModel() {
23+
24+
private val _eventFlow = MutableSharedFlow<Event>()
25+
val eventFlow: SharedFlow<Event>
26+
get() = _eventFlow.asSharedFlow()
27+
28+
private val startNotification = MutableStateFlow<List<GroupNotification>>(emptyList())
29+
private val finishNotification = MutableStateFlow<List<GroupNotification>>(emptyList())
30+
val mergedNotifications = MutableStateFlow<List<GroupNotification>>(emptyList())
31+
32+
init {
33+
// TODO : 그룹 아이디를 프레그먼트에서 받아오도록 변경
34+
getGroupNotificationsUseCase("수피치 그룹1").onEach { notifications ->
35+
36+
if (notifications.isNotEmpty() && notifications.first() is StartNotification) {
37+
startNotification.value = notifications
38+
} else {
39+
finishNotification.value = notifications
40+
}
41+
42+
mergedNotifications.value =
43+
(startNotification.value + finishNotification.value)
44+
.sortedBy { notification ->
45+
when (notification) {
46+
is StartNotification -> notification.startedAt
47+
is FinishNotification -> notification.runningHistory.startedAt
48+
}
49+
}
50+
}.launchIn(viewModelScope)
51+
}
52+
53+
fun onRecruitButtonClicked() {
54+
emitEvent(Event.RecruitButtonClick)
55+
}
56+
57+
fun onExitGroupButtonClicked() {
58+
emitEvent(Event.ExitGroupButtonClick)
59+
}
60+
61+
private fun emitEvent(event: Event) {
62+
when (event) {
63+
Event.RecruitButtonClick,
64+
Event.ExitGroupButtonClick -> {
65+
viewModelScope.launch {
66+
_eventFlow.emit(event)
67+
}
68+
}
69+
}
70+
}
71+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package com.whyranoid.presentation.community.group.detail
2+
3+
import android.view.LayoutInflater
4+
import android.view.ViewGroup
5+
import androidx.databinding.ViewDataBinding
6+
import androidx.recyclerview.widget.DiffUtil
7+
import androidx.recyclerview.widget.ListAdapter
8+
import androidx.recyclerview.widget.RecyclerView
9+
import com.whyranoid.domain.model.FinishNotification
10+
import com.whyranoid.domain.model.GroupNotification
11+
import com.whyranoid.domain.model.StartNotification
12+
import com.whyranoid.presentation.databinding.MyFinishNotificationItemBinding
13+
import com.whyranoid.presentation.databinding.MyStartNotificationItemBinding
14+
import com.whyranoid.presentation.databinding.OtherFinishNotificationItemBinding
15+
import com.whyranoid.presentation.databinding.OtherStartNotificationItemBinding
16+
17+
class GroupNotificationAdapter(private val myUid: String) :
18+
ListAdapter<GroupNotification, GroupNotificationAdapter.NotificationViewHolder>(
19+
notificationDiffUtil
20+
) {
21+
22+
companion object {
23+
private val notificationDiffUtil = object : DiffUtil.ItemCallback<GroupNotification>() {
24+
override fun areItemsTheSame(
25+
oldItem: GroupNotification,
26+
newItem: GroupNotification
27+
): Boolean =
28+
oldItem == newItem
29+
30+
override fun areContentsTheSame(
31+
oldItem: GroupNotification,
32+
newItem: GroupNotification
33+
): Boolean =
34+
oldItem.uid == newItem.uid
35+
}
36+
37+
const val MY_START_NOTIFICATION_TYPE = 0
38+
const val MY_FINISH_NOTIFICATION_TYPE = 1
39+
const val OTHER_START_NOTIFICATION_TYPE = 2
40+
const val OTHER_FINISH_NOTIFICATION_TYPE = 3
41+
}
42+
43+
abstract class NotificationViewHolder(binding: ViewDataBinding) :
44+
RecyclerView.ViewHolder(binding.root) {
45+
abstract fun bind(notification: GroupNotification)
46+
}
47+
48+
class MyStartNotificationViewHolder(private val binding: MyStartNotificationItemBinding) :
49+
NotificationViewHolder(binding) {
50+
51+
override fun bind(notification: GroupNotification) {
52+
if (notification is StartNotification) {
53+
binding.notifications = notification
54+
}
55+
}
56+
}
57+
58+
class MyFinishNotificationViewHolder(private val binding: MyFinishNotificationItemBinding) :
59+
NotificationViewHolder(binding) {
60+
61+
override fun bind(notification: GroupNotification) {
62+
if (notification is FinishNotification) {
63+
binding.notifications = notification
64+
}
65+
}
66+
}
67+
68+
class OtherStartNotificationViewHolder(private val binding: OtherStartNotificationItemBinding) :
69+
NotificationViewHolder(binding) {
70+
71+
override fun bind(notification: GroupNotification) {
72+
if (notification is StartNotification) {
73+
binding.notifications = notification
74+
}
75+
}
76+
}
77+
78+
class OtherFinishNotificationViewHolder(private val binding: OtherFinishNotificationItemBinding) :
79+
NotificationViewHolder(binding) {
80+
81+
override fun bind(notification: GroupNotification) {
82+
if (notification is FinishNotification) {
83+
binding.notifications = notification
84+
}
85+
}
86+
}
87+
88+
override fun getItemViewType(position: Int): Int {
89+
return when (val notification = getItem(position)) {
90+
is StartNotification -> {
91+
if (notification.uid == myUid) MY_START_NOTIFICATION_TYPE
92+
else OTHER_START_NOTIFICATION_TYPE
93+
}
94+
else -> {
95+
if (notification.uid == myUid) MY_FINISH_NOTIFICATION_TYPE
96+
else OTHER_FINISH_NOTIFICATION_TYPE
97+
}
98+
}
99+
}
100+
101+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotificationViewHolder {
102+
return when (viewType) {
103+
MY_START_NOTIFICATION_TYPE -> {
104+
val layoutInflater =
105+
MyStartNotificationItemBinding.inflate(
106+
LayoutInflater.from(parent.context),
107+
parent,
108+
false
109+
)
110+
MyStartNotificationViewHolder(layoutInflater)
111+
}
112+
MY_FINISH_NOTIFICATION_TYPE -> {
113+
val layoutInflater =
114+
MyFinishNotificationItemBinding.inflate(
115+
LayoutInflater.from(parent.context),
116+
parent,
117+
false
118+
)
119+
MyFinishNotificationViewHolder(layoutInflater)
120+
}
121+
OTHER_START_NOTIFICATION_TYPE -> {
122+
val layoutInflater =
123+
OtherStartNotificationItemBinding.inflate(
124+
LayoutInflater.from(parent.context),
125+
parent,
126+
false
127+
)
128+
OtherStartNotificationViewHolder(layoutInflater)
129+
}
130+
else -> {
131+
val layoutInflater =
132+
OtherFinishNotificationItemBinding.inflate(
133+
LayoutInflater.from(parent.context),
134+
parent,
135+
false
136+
)
137+
OtherFinishNotificationViewHolder(layoutInflater)
138+
}
139+
}
140+
}
141+
142+
override fun onBindViewHolder(holder: NotificationViewHolder, position: Int) {
143+
holder.bind(getItem(position))
144+
}
145+
}

0 commit comments

Comments
 (0)