diff --git a/app/src/main/java/org/sopt/pingle/presentation/type/MeridiemType.kt b/app/src/main/java/org/sopt/pingle/presentation/type/MeridiemType.kt new file mode 100644 index 00000000..2fb91545 --- /dev/null +++ b/app/src/main/java/org/sopt/pingle/presentation/type/MeridiemType.kt @@ -0,0 +1,10 @@ +package org.sopt.pingle.presentation.type + +import androidx.annotation.StringRes +import org.sopt.pingle.R + +enum class MeridiemType( + @StringRes val meridiemStringRes: Int +) { + AM(R.string.plan_time_am), PM(R.string.plan_time_pm) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/pingle/presentation/ui/main/plan/PlanDateDialogFragment.kt b/app/src/main/java/org/sopt/pingle/presentation/ui/main/plan/PlanDateDialogFragment.kt new file mode 100644 index 00000000..80c8f3e5 --- /dev/null +++ b/app/src/main/java/org/sopt/pingle/presentation/ui/main/plan/PlanDateDialogFragment.kt @@ -0,0 +1,71 @@ +package org.sopt.pingle.presentation.ui.main.plan + +import android.os.Bundle +import android.view.View +import android.widget.NumberPicker +import org.sopt.pingle.R +import org.sopt.pingle.databinding.DialogDatePickerBinding +import org.sopt.pingle.util.base.BindingBottomSheetDialogFragment + +class PlanDateDialogFragment( + private val onDialogClosed: (year: Int, month: Int, day: Int) -> Unit +) : + BindingBottomSheetDialogFragment(R.layout.dialog_date_picker) { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + isCancelable = false + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + initLayout() + addListeners() + } + + private fun initLayout() { + val yearPicker = binding.npDatePickerYear + yearPicker.apply { + wrapSelectorWheel = false // 순한 안되게 막기 + descendantFocusability = NumberPicker.FOCUS_BLOCK_DESCENDANTS // editText 설정 해제 + minValue = YEAR_MIN + maxValue = YEAR_MAX + } + + val hoursPicker = binding.npDatePickerMonth + hoursPicker.apply { + wrapSelectorWheel = false + descendantFocusability = NumberPicker.FOCUS_BLOCK_DESCENDANTS + minValue = MONTH_MIN + maxValue = MONTH_MAX + } + val dayPicker = binding.npDatePickerDay + dayPicker.apply { + wrapSelectorWheel = false + descendantFocusability = NumberPicker.FOCUS_BLOCK_DESCENDANTS + minValue = DAY_MIN + maxValue = DAY_MAX + } + } + + private fun addListeners() { + binding.tvDatePickerDone.setOnClickListener { + onDialogClosed( + binding.npDatePickerYear.value, + binding.npDatePickerMonth.value, + binding.npDatePickerDay.value + ) + dismiss() + } + } + + companion object { + const val YEAR_MIN = 2024 + const val YEAR_MAX = 2050 + const val MONTH_MIN = 1 + const val MONTH_MAX = 12 + const val DAY_MIN = 1 + const val DAY_MAX = 31 + } +} diff --git a/app/src/main/java/org/sopt/pingle/presentation/ui/main/plan/PlanDateTimeFragment.kt b/app/src/main/java/org/sopt/pingle/presentation/ui/main/plan/PlanDateTimeFragment.kt index e522a8a7..92b5023e 100644 --- a/app/src/main/java/org/sopt/pingle/presentation/ui/main/plan/PlanDateTimeFragment.kt +++ b/app/src/main/java/org/sopt/pingle/presentation/ui/main/plan/PlanDateTimeFragment.kt @@ -2,12 +2,89 @@ package org.sopt.pingle.presentation.ui.main.plan import android.os.Bundle import android.view.View +import androidx.fragment.app.activityViewModels +import java.text.SimpleDateFormat +import java.time.LocalDate import org.sopt.pingle.R import org.sopt.pingle.databinding.FragmentPlanDateTimeBinding import org.sopt.pingle.util.base.BindingFragment +import timber.log.Timber -class PlanDateTimeFragment : BindingFragment(R.layout.fragment_plan_date_time) { +class PlanDateTimeFragment : + BindingFragment(R.layout.fragment_plan_date_time) { + private val planViewModel: PlanViewModel by activityViewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + + addListeners() + } + + private fun addListeners() { + binding.includePlanTextWithTitleDate.root.setOnClickListener { + val dialogDateFragment = PlanDateDialogFragment(::onDateDialogFragmentClosed) + dialogDateFragment.show( + parentFragmentManager, + dialogDateFragment.tag + ) + } + + binding.includePlanTextWithTitleStartTime.root.setOnClickListener { + val dialogStartTimeFragment = PlanTimeDialogFragment(::onTimeDialogFragmentClosed) + dialogStartTimeFragment.show( + parentFragmentManager, + dialogStartTimeFragment.tag + ) + planViewModel.setSelectedTimeType(START_TIME) + } + + binding.includePlanTextWithTitleEndTime.root.setOnClickListener { + val dialogEndTimeFragment = PlanTimeDialogFragment(::onTimeDialogFragmentClosed) + dialogEndTimeFragment.show( + parentFragmentManager, + dialogEndTimeFragment.tag + ) + planViewModel.setSelectedTimeType(END_TIME) + } + } + + // TODO format 관련 refactor + private fun onDateDialogFragmentClosed(year: Int, month: Int, day: Int) { + val dateFormat = SimpleDateFormat("yyyy-MM-dd") + val todayLocalDate = dateFormat.parse(LocalDate.now().toString()) + val selectedFormatDate = String.format("%d-%02d-%02d", year, month, day) + val selectedLocalDate = dateFormat.parse(selectedFormatDate) + + if (selectedLocalDate != null) { + if (selectedLocalDate.before(todayLocalDate)) { + // TODO 에러 스낵바 노출 + } else { + binding.includePlanTextWithTitleDate.tvText.text = String.format( + "%d년 %d월 %d일", + year, + month, + day + ) + planViewModel.setPlanDate(selectedFormatDate) + } + } + } + + // TODO 시간 포맷 나오면 수정 예정 + // TODO {} 수정 예정 + private fun onTimeDialogFragmentClosed(time: String) { + when (planViewModel.selectedTimeType.value) { + START_TIME -> { + Timber.d(START_TIME) + } + + END_TIME -> { + Timber.d(END_TIME) + } + } + } + + companion object { + const val START_TIME = "startTime" + const val END_TIME = "endTime" } } diff --git a/app/src/main/java/org/sopt/pingle/presentation/ui/main/plan/PlanTimeDialogFragment.kt b/app/src/main/java/org/sopt/pingle/presentation/ui/main/plan/PlanTimeDialogFragment.kt new file mode 100644 index 00000000..df7831af --- /dev/null +++ b/app/src/main/java/org/sopt/pingle/presentation/ui/main/plan/PlanTimeDialogFragment.kt @@ -0,0 +1,81 @@ +package org.sopt.pingle.presentation.ui.main.plan + +import android.os.Bundle +import android.view.View +import android.widget.NumberPicker +import org.sopt.pingle.R +import org.sopt.pingle.databinding.DialogTimePickerBinding +import org.sopt.pingle.presentation.type.MeridiemType +import org.sopt.pingle.util.base.BindingBottomSheetDialogFragment + +class PlanTimeDialogFragment( + private val onDialogClosed: (String) -> Unit +) : + BindingBottomSheetDialogFragment(R.layout.dialog_time_picker) { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + isCancelable = false + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + initLayout() + addListeners() + } + + private fun initLayout() { + val meridiemPicker = binding.npTimePickerMeridiem + meridiemPicker.apply { + wrapSelectorWheel = false // 순한 안되게 막기 + descendantFocusability = NumberPicker.FOCUS_BLOCK_DESCENDANTS // editText 설정 해제 + minValue = 0 + maxValue = 1 + displayedValues = + arrayOf( + getString(MeridiemType.AM.meridiemStringRes), + getString(MeridiemType.PM.meridiemStringRes) + ) + } + + val hoursPicker = binding.npTimePickerHour + hoursPicker.apply { + wrapSelectorWheel = false // 순한 안되게 막기 + descendantFocusability = NumberPicker.FOCUS_BLOCK_DESCENDANTS // editText 설정 해제 + minValue = HOUR_MIN + maxValue = HOUR_MAX + } + val minutesPicker = binding.npTimePickerMinute + minutesPicker.apply { + wrapSelectorWheel = false // 순한 안되게 막기 + descendantFocusability = NumberPicker.FOCUS_BLOCK_DESCENDANTS // editText 설정 해제 + minValue = MINUTE_MIN + maxValue = MINUTE_MAX + setFormatter { value -> String.format("%02d", value) } + } + } + + private fun addListeners() { + binding.tvTimePickerDone.setOnClickListener { + onDialogClosed("aaa") + // 시간 설정 후 editText에 띄우기 +// with(binding) { +// val timeFormat = +// String.format("%02d:%02d:00", npTimePickerHour.value, npTimePickerMinute.value) +// onDialogClosed(timeFormat) +// Log.d( +// "aaa", +// String.format("%02d:%02d:00", npTimePickerHour.value, npTimePickerMinute.value) +// ) +// } + dismiss() + } + } + + companion object { + const val HOUR_MIN = 1 + const val HOUR_MAX = 12 + const val MINUTE_MIN = 0 + const val MINUTE_MAX = 59 + } +} diff --git a/app/src/main/java/org/sopt/pingle/presentation/ui/main/plan/PlanViewModel.kt b/app/src/main/java/org/sopt/pingle/presentation/ui/main/plan/PlanViewModel.kt index e3d14475..71967d00 100644 --- a/app/src/main/java/org/sopt/pingle/presentation/ui/main/plan/PlanViewModel.kt +++ b/app/src/main/java/org/sopt/pingle/presentation/ui/main/plan/PlanViewModel.kt @@ -7,11 +7,23 @@ import kotlinx.coroutines.flow.asStateFlow class PlanViewModel : ViewModel() { private val _currentPage = MutableStateFlow(FIRST_PAGE_POSITION) val currentPage get() = _currentPage.asStateFlow() + private val _planDate = MutableStateFlow(null) + val planDate get() = _planDate.asStateFlow() + private val _selectedTimeType = MutableStateFlow(null) + val selectedTimeType get() = _selectedTimeType.asStateFlow() fun setCurrentPage(position: Int) { _currentPage.value = position } + fun setPlanDate(planDate: String) { + _planDate.value = planDate + } + + fun setSelectedTimeType(timeType: String) { + _selectedTimeType.value = timeType + } + companion object { const val FIRST_PAGE_POSITION = 0 } diff --git a/app/src/main/java/org/sopt/pingle/util/component/PingleEditText.kt b/app/src/main/java/org/sopt/pingle/util/component/PingleEditText.kt index bd5dcbf5..1d3d3c2d 100644 --- a/app/src/main/java/org/sopt/pingle/util/component/PingleEditText.kt +++ b/app/src/main/java/org/sopt/pingle/util/component/PingleEditText.kt @@ -10,11 +10,13 @@ import org.sopt.pingle.R import org.sopt.pingle.databinding.EditTextPingleBinding @SuppressLint("CustomViewStyleable") -class PingleEditText( +class PingleEditText @JvmOverloads constructor( context: Context, - attrs: AttributeSet -) : ConstraintLayout(context, attrs) { + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr) { private lateinit var binding: EditTextPingleBinding + private var _etText: String? = null init { val typedArray = context.obtainStyledAttributes(attrs, R.styleable.pingleEditText) @@ -33,6 +35,9 @@ class PingleEditText( val hint = getString(R.styleable.pingleEditText_hint) binding.etEditText.hint = hint + + _etText = getString(R.styleable.pingleEditText_etText) + binding.etEditText.setText(_etText) } } } diff --git a/app/src/main/res/layout/dialog_date_picker.xml b/app/src/main/res/layout/dialog_date_picker.xml new file mode 100644 index 00000000..71d3458a --- /dev/null +++ b/app/src/main/res/layout/dialog_date_picker.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_time_picker.xml b/app/src/main/res/layout/dialog_time_picker.xml new file mode 100644 index 00000000..6889035a --- /dev/null +++ b/app/src/main/res/layout/dialog_time_picker.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/edit_text_pingle.xml b/app/src/main/res/layout/edit_text_pingle.xml index 1294d0a4..9a68d1d7 100644 --- a/app/src/main/res/layout/edit_text_pingle.xml +++ b/app/src/main/res/layout/edit_text_pingle.xml @@ -34,6 +34,5 @@ android:textCursorDrawable="@drawable/cursor_drawble" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/tv_title" - tools:text="에딧텍스트 내용" /> + app:layout_constraintTop_toBottomOf="@id/tv_title" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_plan_date_time.xml b/app/src/main/res/layout/fragment_plan_date_time.xml index 67ed5d4c..a36baa85 100644 --- a/app/src/main/res/layout/fragment_plan_date_time.xml +++ b/app/src/main/res/layout/fragment_plan_date_time.xml @@ -1,5 +1,6 @@ - + @@ -9,5 +10,54 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_text_with_title.xml b/app/src/main/res/layout/view_text_with_title.xml new file mode 100644 index 00000000..7c7c30d4 --- /dev/null +++ b/app/src/main/res/layout/view_text_with_title.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 306e95d8..0e55199d 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -1,7 +1,8 @@ - - + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c6ba93ec..62d915e7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -31,17 +31,25 @@ 모임 개설자로부터 받은 초대코드를 입력해주세요. 입장하기 - - 입장하기 - 단체 입장\n완료! - %s에서 - 핑글 여정을 함께해보세요! - 나중에 만드시겠어요? 나가기 다음으로 핑글 개최하기 + 오전 + 오후 + + + 입장하기 + 단체 입장\n완료! + %s에서 + 핑글 여정을 함께해보세요! + 핑글러들과\n언제 만날까요? + 핑글 날짜 + 만날 날짜를 선택해주세요 + 시작 시각 + 00:00 + 종료 시각 참여하기 diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index d0c2517f..cc30e141 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -73,4 +73,10 @@ 50% + + \ No newline at end of file