Skip to content

Commit

Permalink
feat: 날짜, 시간 선택 기능 구현, 주소검색 기능 연결 (#171)
Browse files Browse the repository at this point in the history
* refactor: 뷰모델 팩토리를 뷰모델의 companion object에서 구현하는 방식으로 변경

* feat: 모집마감 시간 클릭 시 date time picker를 띄우는 기능 구현

* feat: 날짜, 시간 선택 기능 구현

* feat: 주소 검색 기능 연결

* refactor: 함수명 수정, 함수분리

* refactor: ktFormat 적용

* refactor: string으로 분리, 상수화

* fix: string 수정

* chore: CI workflow 파일 수정

* chore: CI workflow 파일 수정

* chore: CI workflow 파일 수정3

* chore: CI workflow 파일 수정4

* feat: 공모가 정상적으로 게시되었을 시 "공모가 게시되었어요!" 라는 토스트를 띄우고 공모글 작성 프래그먼트를 종료하는 기능 구현

* feat: 토스트가 화면 중앙에 뜨는 문제 수정

* refactor: 사용되지 않는 파일 삭제

* refactor: xml 뷰 id 수정

* refactor: 버튼이 TextView인 문제 수정

* refactor: 사용되지 않는 data binding variable 제거

* refactor: 함수명 수정

* refactor: 다이얼로그, dateTimePickerBinding 전역으로 선언

* refactor: dateTimePicker 클릭 이벤트를 추상화 해 xml에서 처리하도록 변경

* refactor: ktFormat
  • Loading branch information
songpink authored and ChooSeoyeon committed Oct 11, 2024
1 parent 4cbd3b1 commit 47d0bea
Show file tree
Hide file tree
Showing 14 changed files with 365 additions and 25 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- name: Create google-services
run: echo "${{ secrets.GOOGLE_SERVICES_JSON }}" > ./app/google-services.json

- name: Create local.properties
env:
BASE_URL: ${{ secrets.BASE_URL }}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
package com.zzang.chongdae.presentation.view.write

import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResultListener
import androidx.fragment.app.viewModels
import com.zzang.chongdae.ChongdaeApp
import com.zzang.chongdae.R
import com.zzang.chongdae.databinding.DialogDateTimePickerBinding
import com.zzang.chongdae.databinding.FragmentOfferingWriteBinding
import com.zzang.chongdae.presentation.view.MainActivity
import com.zzang.chongdae.presentation.view.address.AddressFinderDialog
import java.util.Calendar

class OfferingWriteFragment : Fragment() {
private var _binding: FragmentOfferingWriteBinding? = null
private val binding get() = _binding!!
class OfferingWriteFragment : Fragment(), OnOfferingWriteClickListener {
private var _fragmentBinding: FragmentOfferingWriteBinding? = null
private val fragmentBinding get() = _fragmentBinding!!

private var _dateTimePickerBinding: DialogDateTimePickerBinding? = null
private val dateTimePickerBinding get() = _dateTimePickerBinding!!
private var toast: Toast? = null
private val dialog: Dialog by lazy { Dialog(requireActivity()) }

private val viewModel: OfferingWriteViewModel by viewModels {
OfferingWriteViewModel.getFactory(
Expand All @@ -30,7 +40,7 @@ class OfferingWriteFragment : Fragment() {
savedInstanceState: Bundle?,
): View {
initBinding(inflater, container)
return binding.root
return fragmentBinding.root
}

override fun onViewCreated(
Expand All @@ -40,15 +50,106 @@ class OfferingWriteFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)
(activity as MainActivity).hideBottomNavigation()
observeInvalidInputEvent()
observeFinishEvent()
selectDeadline()
searchPlace()
}

private fun searchPlace() {
fragmentBinding.tvPlaceValue.setOnClickListener {
AddressFinderDialog().show(parentFragmentManager, this.tag)
}
setFragmentResultListener(AddressFinderDialog.ADDRESS_KEY) { _, bundle ->
fragmentBinding.tvPlaceValue.text = bundle.getString(AddressFinderDialog.BUNDLE_ADDRESS_KEY)
}
}

private fun selectDeadline() {
viewModel.deadlineChoiceEvent.observe(viewLifecycleOwner) {
dialog.setContentView(dateTimePickerBinding.root)
dialog.show()
setDateTimeText(dateTimePickerBinding)
}
}

override fun onDateTimeSubmitButtonClick() {
viewModel.updateDeadline(
dateTimePickerBinding.tvDate.text.toString(),
dateTimePickerBinding.tvTime.text.toString(),
)
dialog.dismiss()
}

override fun onDateTimeCancelButtonClick() {
dialog.dismiss()
}

private fun setDateTimeText(dateTimeBinding: DialogDateTimePickerBinding) {
val calendar = Calendar.getInstance()
updateDate(calendar, dateTimeBinding)
updateTime(calendar, dateTimeBinding)
}

private fun updateTime(
calendar: Calendar,
dateTimeBinding: DialogDateTimePickerBinding,
) {
val hourOfDay = calendar.get(Calendar.HOUR_OF_DAY)
val minute = calendar.get(Calendar.MINUTE)
updateTimeTextView(dateTimeBinding.tvTime, hourOfDay, minute)
dateTimeBinding.pickerTime.setOnTimeChangedListener { _, hourOfDay, minute ->
updateTimeTextView(dateTimeBinding.tvTime, hourOfDay, minute)
}
}

private fun updateDate(
calendar: Calendar,
dateTimeBinding: DialogDateTimePickerBinding,
) {
val year = calendar.get(Calendar.YEAR)
val month = calendar.get(Calendar.MONTH)
val day = calendar.get(Calendar.DAY_OF_MONTH)
updateDateTextView(dateTimeBinding.tvDate, year, month, day)
dateTimeBinding.pickerDate.setOnDateChangedListener { _, year, monthOfYear, dayOfMonth ->
updateDateTextView(dateTimeBinding.tvDate, year, monthOfYear, dayOfMonth)
}
}

private fun updateDateTextView(
textView: TextView,
year: Int,
monthOfYear: Int,
dayOfMonth: Int,
) {
textView.text =
getString(R.string.write_selected_date).format(
year,
monthOfYear + 1,
dayOfMonth,
)
}

private fun updateTimeTextView(
textView: TextView,
hourOfDay: Int,
minute: Int,
) {
val amPm = if (hourOfDay < 12) getString(R.string.all_am) else getString(R.string.all_pm)
val hour = if (hourOfDay % 12 == 0) 12 else hourOfDay % 12
textView.text = getString(R.string.write_selected_time, amPm, hour, minute)
}

private fun initBinding(
inflater: LayoutInflater,
container: ViewGroup?,
) {
_binding = FragmentOfferingWriteBinding.inflate(inflater, container, false)
binding.vm = viewModel
binding.lifecycleOwner = viewLifecycleOwner
_fragmentBinding = FragmentOfferingWriteBinding.inflate(inflater, container, false)
fragmentBinding.vm = viewModel
fragmentBinding.lifecycleOwner = viewLifecycleOwner

_dateTimePickerBinding = DialogDateTimePickerBinding.inflate(inflater, container, false)
dateTimePickerBinding.vm = viewModel
dateTimePickerBinding.onClickListener = this
}

private fun observeInvalidInputEvent() {
Expand All @@ -63,6 +164,13 @@ class OfferingWriteFragment : Fragment() {
}
}

private fun observeFinishEvent() {
viewModel.finishEvent.observe(viewLifecycleOwner) {
showToast(R.string.write_success_writing)
parentFragmentManager.popBackStack()
}
}

private fun showToast(
@StringRes message: Int,
) {
Expand All @@ -78,6 +186,6 @@ class OfferingWriteFragment : Fragment() {

override fun onDestroy() {
super.onDestroy()
_binding = null
_fragmentBinding = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import com.zzang.chongdae.domain.repository.OfferingsRepository
import com.zzang.chongdae.presentation.util.MutableSingleLiveData
import com.zzang.chongdae.presentation.util.SingleLiveData
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.Locale

class OfferingWriteViewModel(
private val offeringsRepository: OfferingsRepository,
Expand All @@ -42,6 +44,8 @@ class OfferingWriteViewModel(

val deadline: MutableLiveData<String> = MutableLiveData("")

private val deadlineValue: MutableLiveData<String> = MutableLiveData("")

val description: MutableLiveData<String> = MutableLiveData("")

private val _submitButtonEnabled: MediatorLiveData<Boolean> = MediatorLiveData(false)
Expand All @@ -56,18 +60,24 @@ class OfferingWriteViewModel(
private val _invalidEachPriceEvent: MutableSingleLiveData<Boolean> = MutableSingleLiveData()
val invalidEachPriceEvent: SingleLiveData<Boolean> get() = _invalidEachPriceEvent

private val _finishEvent: MutableSingleLiveData<Boolean> = MutableSingleLiveData()
val finishEvent: SingleLiveData<Boolean> get() = _finishEvent

private val _splitPrice: MediatorLiveData<Int> = MediatorLiveData(ERROR_INTEGER_FORMAT)
val splitPrice: LiveData<Int> get() = _splitPrice

private val _discountRate: MediatorLiveData<Float> = MediatorLiveData(ERROR_FLOAT_FORMAT)
val discountRate: LiveData<Float> get() = _discountRate

private val _deadlineChoiceEvent: MutableSingleLiveData<Boolean> = MutableSingleLiveData()
val deadlineChoiceEvent: SingleLiveData<Boolean> get() = _deadlineChoiceEvent

val splitPriceVisibility: LiveData<Boolean>
get() = _splitPrice.map { it >= 0 }

val discountRateVisibility: LiveData<Boolean>
get() = _discountRate.map { it >= 0 }

val discountRate: LiveData<Float> get() = _discountRate

init {
_submitButtonEnabled.apply {
addSource(title) { updateSubmitButtonEnabled() }
Expand Down Expand Up @@ -140,6 +150,23 @@ class OfferingWriteViewModel(
this.totalCount.value = totalCount.number.toString()
}

fun makeDeadlineChoiceEvent() {
_deadlineChoiceEvent.setValue(true)
}

fun updateDeadline(
date: String,
time: String,
) {
val dateTime = "$date $time"
val inputFormat = SimpleDateFormat(INPUT_DATE_TIME_FORMAT, Locale.KOREAN)
val outputFormat = SimpleDateFormat(OUTPUT_DATE_TIME_FORMAT, Locale.getDefault())

val parsedDateTime = inputFormat.parse(dateTime)
deadlineValue.value = parsedDateTime?.let { outputFormat.format(it) }
deadline.value = dateTime
}

// memberId는 임시값을 보내고 있음!
fun postOffering() {
val memberId = BuildConfig.TOKEN.toLong()
Expand All @@ -148,7 +175,7 @@ class OfferingWriteViewModel(
val totalPrice = totalPrice.value ?: return
val meetingAddress = meetingAddress.value ?: return
val meetingAddressDetail = meetingAddressDetail.value ?: return
val deadline = deadline.value ?: return
val deadline = deadlineValue.value ?: return
val description = description.value ?: return

viewModelScope.launch {
Expand Down Expand Up @@ -180,6 +207,7 @@ class OfferingWriteViewModel(
description = description,
),
).onSuccess {
makeFinishEvent()
Log.d("alsong", "success")
}.onFailure {
Log.e("alsong", it.message.toString())
Expand Down Expand Up @@ -220,11 +248,17 @@ class OfferingWriteViewModel(
_invalidEachPriceEvent.setValue(true)
}

private fun makeFinishEvent() {
_finishEvent.setValue(true)
}

companion object {
private const val ERROR_INTEGER_FORMAT = -1
private const val ERROR_FLOAT_FORMAT = -1f
private const val MINIMUM_TOTAL_COUNT = 2
private const val MAXIMUM_TOTAL_COUNT = 10_000
private const val INPUT_DATE_TIME_FORMAT = "yyyy년 M월 d일 a h시 m분"
private const val OUTPUT_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"

@Suppress("UNCHECKED_CAST")
fun getFactory(offeringsRepository: OfferingsRepository) =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.zzang.chongdae.presentation.view.write

interface OnOfferingWriteClickListener {
fun onDateTimeSubmitButtonClick()

fun onDateTimeCancelButtonClick()
}
6 changes: 6 additions & 0 deletions android/app/src/main/res/drawable/bg_date_time_picker.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="24dp" />
<solid android:color="@color/white"/>
</shape>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="12dp" />
<solid android:color="#8FEDEDED"/>
</shape>
13 changes: 13 additions & 0 deletions android/app/src/main/res/drawable/btn_left_arrow.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="8dp"
android:height="14dp"
android:viewportWidth="8"
android:viewportHeight="14">
<path
android:pathData="M7,13L1,7L7,1"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#7B7B7B"
android:strokeLineCap="round"/>
</vector>
13 changes: 13 additions & 0 deletions android/app/src/main/res/drawable/btn_right_arrow.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="8dp"
android:height="14dp"
android:viewportWidth="8"
android:viewportHeight="14">
<path
android:pathData="M1,13L7,7L1,1"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#7B7B7B"
android:strokeLineCap="round"/>
</vector>
6 changes: 6 additions & 0 deletions android/app/src/main/res/drawable/btn_rounded_gray.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="6dp" />
<solid android:color="@color/gray_300"/>
</shape>
6 changes: 3 additions & 3 deletions android/app/src/main/res/layout/activity_offering_detail.xml
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@
app:layout_constraintTop_toTopOf="parent" />

<ImageView
android:id="@+id/iv_divide_line"
android:id="@+id/view_divide_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
Expand All @@ -144,8 +144,8 @@
android:layout_marginStart="10dp"
android:src="@drawable/ic_detail_remove"
app:isVisible="@{vm.isRepresentative()}"
app:layout_constraintStart_toEndOf="@id/iv_divide_line"
app:layout_constraintTop_toTopOf="@id/iv_divide_line" />
app:layout_constraintStart_toEndOf="@id/view_divide_line"
app:layout_constraintTop_toTopOf="@id/view_divide_line" />

<TextView
android:id="@+id/tv_product_link_comment"
Expand Down
Loading

0 comments on commit 47d0bea

Please sign in to comment.