diff --git a/app/src/main/java/org/sopt/pingle/domain/model/PingleEntity.kt b/app/src/main/java/org/sopt/pingle/domain/model/PingleEntity.kt new file mode 100644 index 00000000..d32dd6ea --- /dev/null +++ b/app/src/main/java/org/sopt/pingle/domain/model/PingleEntity.kt @@ -0,0 +1,16 @@ +package org.sopt.pingle.domain.model + +data class PingleEntity( + val id: Long, + val category: String, + val name: String, + val ownerName: String, + val location: String, + val date: String, + val startAt: String, + val endAt: String, + val maxParticipants: Int, + val curParticipants: Int, + val isParticipating: Boolean, + val chatLink: String +) diff --git a/app/src/main/java/org/sopt/pingle/presentation/type/CategoryType.kt b/app/src/main/java/org/sopt/pingle/presentation/type/CategoryType.kt index 3a965793..d9f38b0b 100644 --- a/app/src/main/java/org/sopt/pingle/presentation/type/CategoryType.kt +++ b/app/src/main/java/org/sopt/pingle/presentation/type/CategoryType.kt @@ -35,11 +35,16 @@ enum class CategoryType( backgroundBadgeColor = R.color.badge_yellow, categoryNameRes = R.string.category_multi ), - OTHER( + OTHERS( textColor = R.color.g_01, activatedOutLinedColor = R.color.g_01, backgroundChipColor = R.color.g_10, backgroundBadgeColor = R.color.g_07, categoryNameRes = R.string.category_others - ) + ); + + companion object { + fun fromString(categoryName: String) = + CategoryType.values().first() { it.name == categoryName } + } } diff --git a/app/src/main/java/org/sopt/pingle/presentation/ui/common/AllModalDialogFragment.kt b/app/src/main/java/org/sopt/pingle/presentation/ui/common/AllModalDialogFragment.kt index 90e20162..4c30fa7b 100644 --- a/app/src/main/java/org/sopt/pingle/presentation/ui/common/AllModalDialogFragment.kt +++ b/app/src/main/java/org/sopt/pingle/presentation/ui/common/AllModalDialogFragment.kt @@ -1,5 +1,6 @@ package org.sopt.pingle.presentation.ui.common +import android.content.DialogInterface import android.os.Bundle import android.view.View import org.sopt.pingle.R @@ -12,7 +13,8 @@ class AllModalDialogFragment( private val buttonText: String, private val textButtonText: String, private val clickBtn: () -> Unit, - private val clickTextBtn: () -> Unit + private val clickTextBtn: () -> Unit, + private val onDialogClosed: () -> Unit = {} ) : BindingDialogFragment(R.layout.dialog_all_modal) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -21,6 +23,11 @@ class AllModalDialogFragment( addListeners() } + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + onDialogClosed() + } + private fun initLayout() { with(binding) { tvAllModalTitle.text = title diff --git a/app/src/main/java/org/sopt/pingle/presentation/ui/main/home/map/MapFragment.kt b/app/src/main/java/org/sopt/pingle/presentation/ui/main/home/map/MapFragment.kt index 074d46f3..fa265515 100644 --- a/app/src/main/java/org/sopt/pingle/presentation/ui/main/home/map/MapFragment.kt +++ b/app/src/main/java/org/sopt/pingle/presentation/ui/main/home/map/MapFragment.kt @@ -28,9 +28,13 @@ import org.sopt.pingle.R import org.sopt.pingle.databinding.FragmentMapBinding import org.sopt.pingle.presentation.mapper.toMarker import org.sopt.pingle.presentation.type.CategoryType +import org.sopt.pingle.presentation.ui.common.AllModalDialogFragment import org.sopt.pingle.util.base.BindingFragment +import org.sopt.pingle.util.component.OnPingleCardClickListener import org.sopt.pingle.util.component.PingleChip +import org.sopt.pingle.util.fragment.navigateToWebView import org.sopt.pingle.util.fragment.showToast +import org.sopt.pingle.util.fragment.stringOf class MapFragment : BindingFragment(R.layout.fragment_map), OnMapReadyCallback { private val mapViewModel by viewModels() @@ -79,7 +83,20 @@ class MapFragment : BindingFragment(R.layout.fragment_map), chipMapCategoryPlay.setChipCategoryType(CategoryType.PLAY) chipMapCategoryStudy.setChipCategoryType(CategoryType.STUDY) chipMapCategoryMulti.setChipCategoryType(CategoryType.MULTI) - chipMapCategoryOthers.setChipCategoryType(CategoryType.OTHER) + chipMapCategoryOthers.setChipCategoryType(CategoryType.OTHERS) + cardMap.initLayout(mapViewModel.dummyPingle) + cardMap.listener = object : OnPingleCardClickListener { + override fun onPingleCardChatBtnClickListener() { + startActivity(navigateToWebView(mapViewModel.dummyPingle.chatLink)) + } + + override fun onPingleCardParticipateBtnClickListener() { + when (mapViewModel.dummyPingle.isParticipating) { + true -> showMapCancelModalDialogFragment() + false -> showMapModalDialogFragment() + } + } + } } } @@ -168,6 +185,30 @@ class MapFragment : BindingFragment(R.layout.fragment_map), } } + private fun showMapCancelModalDialogFragment() { + AllModalDialogFragment( + title = stringOf(R.string.map_cancel_modal_title), + detail = stringOf(R.string.map_cancel_modal_detail), + buttonText = stringOf(R.string.map_cancel_modal_button_text), + textButtonText = stringOf(R.string.map_cancel_modal_text_button_text), + clickBtn = { mapViewModel.cancelPingle() }, + clickTextBtn = { }, + onDialogClosed = { binding.cardMap.initLayout(mapViewModel.dummyPingle) } + ).show(childFragmentManager, MAP_CANCEL_MODAL) + } + + private fun showMapModalDialogFragment() { + with(mapViewModel.dummyPingle) { + MapModalDialogFragment( + category = CategoryType.fromString(categoryName = category), + name = name, + ownerName = ownerName, + clickBtn = { mapViewModel.joinPingle() }, + onDialogClosed = { binding.cardMap.initLayout(mapViewModel.dummyPingle) } + ).show(childFragmentManager, MAP_MODAL) + } + } + companion object { private const val LOCATION_PERMISSION_REQUEST_CODE = 1000 private val LOCATION_PERMISSIONS = arrayOf( @@ -175,5 +216,7 @@ class MapFragment : BindingFragment(R.layout.fragment_map), Manifest.permission.ACCESS_COARSE_LOCATION ) private const val SINGLE_SELECTION = 0 + private const val MAP_CANCEL_MODAL = "mapCancelModal" + private const val MAP_MODAL = "mapModal" } } diff --git a/app/src/main/java/org/sopt/pingle/presentation/ui/main/home/map/MapModalDialogFragment.kt b/app/src/main/java/org/sopt/pingle/presentation/ui/main/home/map/MapModalDialogFragment.kt index b2a628a2..490ab2a8 100644 --- a/app/src/main/java/org/sopt/pingle/presentation/ui/main/home/map/MapModalDialogFragment.kt +++ b/app/src/main/java/org/sopt/pingle/presentation/ui/main/home/map/MapModalDialogFragment.kt @@ -1,5 +1,6 @@ package org.sopt.pingle.presentation.ui.main.home.map +import android.content.DialogInterface import android.os.Bundle import android.view.View import org.sopt.pingle.R @@ -12,7 +13,8 @@ class MapModalDialogFragment( private val category: CategoryType, private val name: String, private val ownerName: String, - private val clickBtn: () -> Unit + private val clickBtn: () -> Unit, + private val onDialogClosed: () -> Unit = {} ) : BindingDialogFragment(R.layout.dialog_map_modal) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -22,6 +24,11 @@ class MapModalDialogFragment( addListeners() } + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + onDialogClosed.invoke() + } + private fun initLayout() { with(binding) { badgeMapModalPingleInfoCategory.setBadgeCategoryType(category) diff --git a/app/src/main/java/org/sopt/pingle/presentation/ui/main/home/map/MapViewModel.kt b/app/src/main/java/org/sopt/pingle/presentation/ui/main/home/map/MapViewModel.kt index d4a9947d..69ce4bd9 100644 --- a/app/src/main/java/org/sopt/pingle/presentation/ui/main/home/map/MapViewModel.kt +++ b/app/src/main/java/org/sopt/pingle/presentation/ui/main/home/map/MapViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import org.sopt.pingle.domain.model.PinEntity +import org.sopt.pingle.domain.model.PingleEntity import org.sopt.pingle.presentation.type.CategoryType class MapViewModel() : ViewModel() { @@ -38,10 +39,59 @@ class MapViewModel() : ViewModel() { ) ) + var dummyPingle = PingleEntity( + id = 1, + category = "PLAY", + name = "핑글핑글핑글 ~", + ownerName = "배지현", + location = "길음역", + date = "2023-01-06", + startAt = "10:30:00", + endAt = "22:34:00", + maxParticipants = 5, + curParticipants = 4, + isParticipating = false, + chatLink = "https://github.com/TeamPINGLE/PINGLE-ANDROID" + ) + private val _category = MutableStateFlow(null) val category get() = _category.asStateFlow() fun setCategory(category: CategoryType?) { _category.value = category } + + fun cancelPingle() { + dummyPingle = PingleEntity( + id = 1, + category = "PLAY", + name = "핑글핑글핑글 ~", + ownerName = "배지현", + location = "길음역", + date = "2023-01-06", + startAt = "10:30:00", + endAt = "22:34:00", + maxParticipants = 5, + curParticipants = 4, + isParticipating = false, + chatLink = "https://github.com/TeamPINGLE/PINGLE-ANDROID" + ) + } + + fun joinPingle() { + dummyPingle = PingleEntity( + id = 1, + category = "PLAY", + name = "핑글핑글핑글 ~", + ownerName = "배지현", + location = "길음역", + date = "2023-01-06", + startAt = "10:30:00", + endAt = "22:34:00", + maxParticipants = 5, + curParticipants = 5, + isParticipating = true, + chatLink = "https://github.com/TeamPINGLE/PINGLE-ANDROID" + ) + } } diff --git a/app/src/main/java/org/sopt/pingle/util/component/PingleCard.kt b/app/src/main/java/org/sopt/pingle/util/component/PingleCard.kt new file mode 100644 index 00000000..72d640e0 --- /dev/null +++ b/app/src/main/java/org/sopt/pingle/util/component/PingleCard.kt @@ -0,0 +1,132 @@ +package org.sopt.pingle.util.component + +import android.annotation.SuppressLint +import android.content.Context +import android.text.Spannable +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.text.style.TextAppearanceSpan +import android.util.AttributeSet +import android.view.LayoutInflater +import androidx.constraintlayout.widget.ConstraintLayout +import java.time.LocalDate +import java.time.LocalTime +import java.time.format.DateTimeFormatter +import org.sopt.pingle.R +import org.sopt.pingle.databinding.CardPingleBinding +import org.sopt.pingle.domain.model.PingleEntity +import org.sopt.pingle.presentation.type.CategoryType +import org.sopt.pingle.util.view.colorOf +import org.sopt.pingle.util.view.stringOf + +@SuppressLint("CustomViewStyleable") +class PingleCard @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr) { + private val binding: CardPingleBinding + var listener: OnPingleCardClickListener? = null + + init { + binding = CardPingleBinding.inflate(LayoutInflater.from(context), this, true) + + addListeners() + } + + private fun addListeners() { + binding.btnCardBottomMapChat.setOnClickListener { + listener?.onPingleCardChatBtnClickListener() + } + + binding.btnCardBottomMapParticipate.setOnClickListener { + listener?.onPingleCardParticipateBtnClickListener() + } + } + + fun initLayout(pingleEntity: PingleEntity) { + val category: CategoryType = CategoryType.fromString(pingleEntity.category) + + with(binding) { + badgeCardTopInfo.setBadgeCategoryType(category) + tvCardTopInfoName.text = pingleEntity.name + tvCardTopInfoName.setTextColor(colorOf(category.textColor)) + tvCardTopInfoOwnerName.text = pingleEntity.ownerName + tvCardBottomCalenderDetail.text = pingleEntity.convertToCalenderDetail() + tvCardBottomMapDetail.text = pingleEntity.location + btnCardBottomMapChat.isEnabled = pingleEntity.isParticipating + btnCardBottomMapParticipate.text = when (pingleEntity.isParticipating) { + true -> stringOf(R.string.map_card_cancel) + false -> stringOf(R.string.map_card_participate) + } + btnCardBottomMapParticipate.isEnabled = + pingleEntity.isParticipating || !pingleEntity.isCompleted() + + if (pingleEntity.isCompleted()) { + with(tvCardTopInfoParticipantDetail) { + text = stringOf(R.string.map_card_completed) + setTextAppearance(R.style.TextAppearance_Pingle_Sub_Semi_16) + } + } else { + with(tvCardTopInfoParticipantDetail) { + val participantDetail = context.getString( + R.string.map_card_participant_detail, + pingleEntity.curParticipants, + pingleEntity.maxParticipants + ) + text = SpannableString(participantDetail).apply { + setSpan( + ForegroundColorSpan( + colorOf(category.textColor) + ), + CUR_PARTICIPANTS_START, + pingleEntity.curParticipants.toString().length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + setSpan( + TextAppearanceSpan( + context, + R.style.TextAppearance_Pingle_Title_Semi_30 + ), + CUR_PARTICIPANTS_START, + pingleEntity.curParticipants.toString().length + 1, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + setSpan( + TextAppearanceSpan( + context, + R.style.TextAppearance_Pingle_Title_Semi_20 + ), + pingleEntity.curParticipants.toString().length + 1, + participantDetail.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + } + } + } + } + + companion object { + const val CUR_PARTICIPANTS_START = 0 + } +} + +interface OnPingleCardClickListener { + fun onPingleCardChatBtnClickListener() + fun onPingleCardParticipateBtnClickListener() +} + +fun PingleEntity.isCompleted() = maxParticipants == curParticipants + +fun PingleEntity.convertToCalenderDetail(): String { + val localDate = LocalDate.parse(date, DateTimeFormatter.ISO_LOCAL_DATE) + val startTime = LocalTime.parse(startAt, DateTimeFormatter.ISO_LOCAL_TIME) + val endTime = LocalTime.parse(endAt, DateTimeFormatter.ISO_LOCAL_TIME) + + return buildString { + append("${localDate.year}년 ${localDate.monthValue}월 ${localDate.dayOfMonth}일\n") + append("${startTime.format(DateTimeFormatter.ofPattern("HH:mm"))} ~ ") + append("${endTime.format(DateTimeFormatter.ofPattern("HH:mm"))}") + } +} diff --git a/app/src/main/res/color/selector_pingle_btn_m.xml b/app/src/main/res/color/selector_pingle_btn_m.xml new file mode 100644 index 00000000..df8f2c15 --- /dev/null +++ b/app/src/main/res/color/selector_pingle_btn_m.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/selector_pingle_btn_s.xml b/app/src/main/res/color/selector_pingle_btn_s.xml new file mode 100644 index 00000000..7b6fae3d --- /dev/null +++ b/app/src/main/res/color/selector_pingle_btn_s.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_oval_stroke_1.xml b/app/src/main/res/drawable/shape_oval_stroke_1.xml new file mode 100644 index 00000000..4b79d3aa --- /dev/null +++ b/app/src/main/res/drawable/shape_oval_stroke_1.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/card_pingle.xml b/app/src/main/res/layout/card_pingle.xml new file mode 100644 index 00000000..dee84ad1 --- /dev/null +++ b/app/src/main/res/layout/card_pingle.xml @@ -0,0 +1,259 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_map.xml b/app/src/main/res/layout/fragment_map.xml index 1a378bc8..04d62eec 100644 --- a/app/src/main/res/layout/fragment_map.xml +++ b/app/src/main/res/layout/fragment_map.xml @@ -85,5 +85,15 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" /> + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 7febdf1c..3df7c155 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -39,6 +39,7 @@ 42dp 42dp 10dp + 95dp 50dp @@ -50,4 +51,8 @@ 24dp 34dp 24dp + + + 24dp + 81dp \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6695d9d0..871c7545 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -39,6 +39,9 @@ 오전 오후 + 링크를 입력해주세요 + 채팅방 링크 + 입장하기 단체 입장\n완료! @@ -59,6 +62,20 @@ 이 핑글에 참여할까요? - 링크를 입력해주세요 - 채팅방 링크 + + + 참여자 + %d/%d + 일시 + 장소 + 대화하기 + 참여하기 + 취소하기 + 모집완료 + + + 참여를 취소하시겠어요? + 취소한 모임은 언제든 다시 신청할 수 있어요 + 취소하기 + 돌아가기 \ No newline at end of file diff --git a/app/src/main/res/values/text_appearance.xml b/app/src/main/res/values/text_appearance.xml index 22734dff..c2d353f3 100644 --- a/app/src/main/res/values/text_appearance.xml +++ b/app/src/main/res/values/text_appearance.xml @@ -30,7 +30,7 @@ @font/suit_semibold - + + +