Skip to content

Commit

Permalink
Merge pull request #196 from TeamPINGLE/feat-list-view
Browse files Browse the repository at this point in the history
[feat] 리스트뷰 구현
  • Loading branch information
jihyunniiii authored Feb 25, 2024
2 parents ef26e01 + 81243e0 commit c032ee5
Show file tree
Hide file tree
Showing 28 changed files with 793 additions and 198 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.sopt.pingle.presentation.type

import androidx.annotation.StringRes
import org.sopt.pingle.R

enum class MainListOrderType(
@StringRes val mainListOrderStringRes: Int
) {
NEW(mainListOrderStringRes = R.string.main_list_order_new),
UPCOMING(mainListOrderStringRes = R.string.main_list_order_upcoming)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.sopt.pingle.presentation.type

import androidx.annotation.ColorRes
import org.sopt.pingle.R

enum class PingleCardType(
@ColorRes val participationStatusColorRes: Int
) {
MAP(
participationStatusColorRes = R.color.g_10
),
MAINLIST(
participationStatusColorRes = R.color.g_09
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import org.sopt.pingle.domain.usecase.GetPingleListUseCase
import org.sopt.pingle.domain.usecase.PostPingleJoinUseCase
import org.sopt.pingle.presentation.model.MarkerModel
import org.sopt.pingle.presentation.type.CategoryType
import org.sopt.pingle.presentation.type.MainListOrderType
import org.sopt.pingle.util.view.UiState

@HiltViewModel
Expand Down Expand Up @@ -55,6 +56,9 @@ class HomeViewModel @Inject constructor(
private val _pingleDeleteState = MutableSharedFlow<UiState<Unit?>>()
val pingleDeleteState get() = _pingleDeleteState.asSharedFlow()

private val _mainListOrderType = MutableStateFlow<MainListOrderType>(MainListOrderType.NEW)
val mainListOrderType get() = _mainListOrderType.asStateFlow()

fun setCategory(category: CategoryType?) {
_category.value = category
}
Expand Down Expand Up @@ -105,6 +109,10 @@ class HomeViewModel @Inject constructor(
}
}

fun setMainListOrderType(mainListOrderType: MainListOrderType) {
_mainListOrderType.value = mainListOrderType
}

fun getGroupName(): String = localStorage.groupName

fun getPinListWithoutFilter() {
Expand Down Expand Up @@ -183,6 +191,99 @@ class HomeViewModel @Inject constructor(
}
}

val dummyPingleList = listOf<PingleEntity>(
PingleEntity(
id = 1L,
category = "STUDY",
name = "강남 모각작팟을 구합니다!!!!",
ownerName = "박소현",
location = "하얀집 2호점",
date = "2023-12-31",
startAt = "17:00:00",
endAt = "23:00:00",
maxParticipants = 99,
curParticipants = 11,
isParticipating = false,
chatLink = "https://github.com/TeamPINGLE/PINGLE-ANDROID",
isOwner = false
),
PingleEntity(
id = 2L,
category = "MULTI",
name = "모각공하고 곱창먹자",
ownerName = "하지은",
location = "푸지미곱창",
date = "2024-02-23",
startAt = "14:00:00",
endAt = "23:00:00",
maxParticipants = 12,
curParticipants = 4,
isParticipating = true,
chatLink = "https://github.com/TeamPINGLE/PINGLE-ANDROID",
isOwner = false
),
PingleEntity(
id = 3L,
category = "OTHERS",
name = "국민대 졸업하는 김승연 축하하러 올사람",
ownerName = "김승연",
location = "국민대학교",
date = "2024-02-14",
startAt = "14:30:00",
endAt = "21:30:00",
maxParticipants = 14,
curParticipants = 14,
isParticipating = false,
chatLink = "https://github.com/TeamPINGLE/PINGLE-ANDROID",
isOwner = false
),
PingleEntity(
id = 4L,
category = "PLAY",
name = "건빵 QA",
ownerName = "배지현",
location = "서울역",
date = "2024-02-23",
startAt = "18:30:00",
endAt = "21:30:00",
maxParticipants = 13,
curParticipants = 13,
isParticipating = true,
chatLink = "https://github.com/TeamPINGLE/PINGLE-ANDROID",
isOwner = false
),
PingleEntity(
id = 5L,
category = "PLAY",
name = "안핑이들 집합 ㅋ.ㅋ",
ownerName = "배지현",
location = "포어플랜",
date = "2024-02-27",
startAt = "19:00:00",
endAt = "23:00:00",
maxParticipants = 4,
curParticipants = 4,
isParticipating = true,
chatLink = "https://github.com/TeamPINGLE/PINGLE-ANDROID",
isOwner = true
),
PingleEntity(
id = 6L,
category = "OTHERS",
name = "핑글",
ownerName = "배지현",
location = "핑글핑글",
date = "2024-02-29",
startAt = "01:00:00",
endAt = "03:00:00",
maxParticipants = 24,
curParticipants = 13,
isParticipating = true,
chatLink = "https://github.com/TeamPINGLE/PINGLE-ANDROID",
isOwner = true
)
)

companion object {
const val DEFAULT_SELECTED_MARKER_POSITION = -1
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.sopt.pingle.presentation.ui.main.home.mainlist

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import org.sopt.pingle.databinding.ItemMainListPingleCardBinding
import org.sopt.pingle.domain.model.PingleEntity
import org.sopt.pingle.util.view.ItemDiffCallback

class MainListAdapter(
private val navigateToParticipant: (Long) -> Unit,
private val navigateToWebViewWithChatLink: (String) -> Unit,
private val showPingleJoinModalDialogFragment: (PingleEntity) -> Unit,
private val showPingleCancelModalDialogFragment: (PingleEntity) -> Unit,
private val showPingleDeleteModalDialogFragment: (PingleEntity) -> Unit
) : ListAdapter<PingleEntity, MainListViewHolder>(
ItemDiffCallback<PingleEntity>(
onContentsTheSame = { old, new -> old == new },
onItemsTheSame = { old, new -> old.id == new.id }
)
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainListViewHolder =
MainListViewHolder(
binding = ItemMainListPingleCardBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
),
context = parent.context,
navigateToParticipant = navigateToParticipant,
navigateToWebViewWithChatLink = navigateToWebViewWithChatLink,
showPingleJoinModalDialogFragment = showPingleJoinModalDialogFragment,
showPingleCancelModalDialogFragment = showPingleCancelModalDialogFragment,
showPingleDeleteModalDialogFragment = showPingleDeleteModalDialogFragment
)

override fun onBindViewHolder(holder: MainListViewHolder, position: Int) {
holder.onBind(pingleEntity = currentList[position])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,110 @@ package org.sopt.pingle.presentation.ui.main.home.mainlist

import android.os.Bundle
import android.view.View
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.sopt.pingle.R
import org.sopt.pingle.databinding.FragmentMainListBinding
import org.sopt.pingle.presentation.type.MainListOrderType
import org.sopt.pingle.presentation.ui.main.home.HomeViewModel
import org.sopt.pingle.util.base.BindingFragment
import org.sopt.pingle.util.fragment.navigateToWebView
import org.sopt.pingle.util.fragment.stringOf
import org.sopt.pingle.util.view.PingleCardUtils

@AndroidEntryPoint
class MainListFragment : BindingFragment<FragmentMainListBinding>(R.layout.fragment_main_list) {
private val homeViewModel: HomeViewModel by activityViewModels()
private lateinit var mainListAdapter: MainListAdapter

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

initLayout()
addListeners()
collectData()
}

override fun onDestroyView() {
binding.rvMainList.adapter = null
super.onDestroyView()
}

private fun initLayout() {
mainListAdapter = MainListAdapter(
navigateToParticipant = { id ->
context?.let {
PingleCardUtils.navigateToParticipant(
it,
id
)
}
},
navigateToWebViewWithChatLink = { chatLink -> startActivity(navigateToWebView(chatLink)) },
showPingleJoinModalDialogFragment = { pingleEntity ->
PingleCardUtils.showPingleJoinModalDialogFragment(
fragment = this,
postPingleJoin = { homeViewModel.postPingleJoin(pingleEntity.id) },
pingleEntity = pingleEntity
)
},
showPingleCancelModalDialogFragment = { pingleEntity ->
PingleCardUtils.showPingleCancelModalDialogFragment(
fragment = this,
deletePingleCancel = { homeViewModel.deletePingleCancel(pingleEntity.id) }
)
},
showPingleDeleteModalDialogFragment = { pingleEntity ->
PingleCardUtils.showMapDeleteModalDialogFragment(
fragment = this,
deletePingleDelete = { homeViewModel.deletePingleDelete(pingleEntity.id) }
)
}
)
binding.rvMainList.adapter = mainListAdapter
mainListAdapter.submitList(homeViewModel.dummyPingleList)

// TODO 서버통신 구현 후 collectData 함수로 해당 로직 이동
with(homeViewModel.dummyPingleList) {
binding.tvMainListEmpty.visibility = if (isEmpty()) View.VISIBLE else View.INVISIBLE
binding.tvMainListEmpty.text = stringOf(R.string.main_list_empty_pingle)
}
}

private fun addListeners() {
with(binding) {
layoutMainListOrder.setOnClickListener {
(layoutMainListOrderMenu.visibility == View.VISIBLE).let { isVisible ->
layoutMainListOrderMenu.visibility =
if (isVisible) View.INVISIBLE else View.VISIBLE
}
}

tvMainListOrderMenuNew.setOnClickListener {
homeViewModel.setMainListOrderType(
MainListOrderType.NEW
)
layoutMainListOrderMenu.visibility = View.INVISIBLE
}

tvMainListOrderMenuUpcoming.setOnClickListener {
homeViewModel.setMainListOrderType(
MainListOrderType.UPCOMING
)
layoutMainListOrderMenu.visibility = View.INVISIBLE
}
}
}

private fun collectData() {
homeViewModel.mainListOrderType.flowWithLifecycle(viewLifecycleOwner.lifecycle)
.onEach { mainListOrderType ->
binding.tvMainListOrderType.text =
stringOf(mainListOrderType.mainListOrderStringRes)
}.launchIn(viewLifecycleOwner.lifecycleScope)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.sopt.pingle.presentation.ui.main.home.mainlist

import android.content.Context
import android.view.View
import androidx.appcompat.content.res.AppCompatResources
import androidx.recyclerview.widget.RecyclerView
import org.sopt.pingle.R
import org.sopt.pingle.databinding.ItemMainListPingleCardBinding
import org.sopt.pingle.domain.model.PingleEntity
import org.sopt.pingle.presentation.type.PingleCardType

class MainListViewHolder(
private val binding: ItemMainListPingleCardBinding,
private val context: Context,
private val navigateToParticipant: (Long) -> Unit,
private val navigateToWebViewWithChatLink: (String) -> Unit,
private val showPingleJoinModalDialogFragment: (PingleEntity) -> Unit,
private val showPingleCancelModalDialogFragment: (PingleEntity) -> Unit,
private val showPingleDeleteModalDialogFragment: (PingleEntity) -> Unit
) : RecyclerView.ViewHolder(binding.root) {
init {
with(binding) {
ivMainListPingleCardBottomArrowUp.setOnClickListener {
setCardExpandable(isExpanded = false)
}

ivMainListPingleCardBottomArrowDown.setOnClickListener {
setCardExpandable(isExpanded = true)
}
}
}

private fun setCardExpandable(isExpanded: Boolean) {
with(binding) {
ivMainListPingleCardBottomArrowUp.visibility =
if (isExpanded) View.VISIBLE else View.GONE
pingleCardBottomMainListPingleCard.visibility =
if (isExpanded) View.VISIBLE else View.GONE
ivMainListPingleCardBottomArrowDown.visibility =
if (isExpanded) View.GONE else View.VISIBLE
layoutMainListPingleCardBottom.background = AppCompatResources.getDrawable(
context,
if (isExpanded) R.drawable.shape_border_radius_15 else R.drawable.shape_border_radius_8
)
}
}

fun onBind(pingleEntity: PingleEntity) {
with(binding.pingleCardTopMainListPingleCard) {
initLayout(pingleEntity = pingleEntity, pingleCardType = PingleCardType.MAINLIST)
setOnParticipationStatusLayoutClick {
navigateToParticipant(pingleEntity.id)
}
}

with(binding.pingleCardBottomMainListPingleCard) {
initLayout(pingleEntity)
setOnChatButtonClick {
navigateToWebViewWithChatLink(pingleEntity.chatLink)
}
setOnParticipateButtonClick {
when {
pingleEntity.isOwner -> showPingleDeleteModalDialogFragment(pingleEntity)
pingleEntity.isParticipating -> showPingleCancelModalDialogFragment(pingleEntity)
else -> showPingleJoinModalDialogFragment(pingleEntity)
}
}
}
}
}
Loading

0 comments on commit c032ee5

Please sign in to comment.