diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index ee3bf5fb3..f6ef4d685 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -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 }} diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteFragment.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteFragment.kt index 1ead88edb..3228e3f26 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteFragment.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteFragment.kt @@ -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( @@ -30,7 +40,7 @@ class OfferingWriteFragment : Fragment() { savedInstanceState: Bundle?, ): View { initBinding(inflater, container) - return binding.root + return fragmentBinding.root } override fun onViewCreated( @@ -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() { @@ -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, ) { @@ -78,6 +186,6 @@ class OfferingWriteFragment : Fragment() { override fun onDestroy() { super.onDestroy() - _binding = null + _fragmentBinding = null } } diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteViewModel.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteViewModel.kt index 7e368c79c..d2d3e9804 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteViewModel.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteViewModel.kt @@ -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, @@ -42,6 +44,8 @@ class OfferingWriteViewModel( val deadline: MutableLiveData = MutableLiveData("") + private val deadlineValue: MutableLiveData = MutableLiveData("") + val description: MutableLiveData = MutableLiveData("") private val _submitButtonEnabled: MediatorLiveData = MediatorLiveData(false) @@ -56,18 +60,24 @@ class OfferingWriteViewModel( private val _invalidEachPriceEvent: MutableSingleLiveData = MutableSingleLiveData() val invalidEachPriceEvent: SingleLiveData get() = _invalidEachPriceEvent + private val _finishEvent: MutableSingleLiveData = MutableSingleLiveData() + val finishEvent: SingleLiveData get() = _finishEvent + private val _splitPrice: MediatorLiveData = MediatorLiveData(ERROR_INTEGER_FORMAT) val splitPrice: LiveData get() = _splitPrice private val _discountRate: MediatorLiveData = MediatorLiveData(ERROR_FLOAT_FORMAT) + val discountRate: LiveData get() = _discountRate + + private val _deadlineChoiceEvent: MutableSingleLiveData = MutableSingleLiveData() + val deadlineChoiceEvent: SingleLiveData get() = _deadlineChoiceEvent + val splitPriceVisibility: LiveData get() = _splitPrice.map { it >= 0 } val discountRateVisibility: LiveData get() = _discountRate.map { it >= 0 } - val discountRate: LiveData get() = _discountRate - init { _submitButtonEnabled.apply { addSource(title) { updateSubmitButtonEnabled() } @@ -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() @@ -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 { @@ -180,6 +207,7 @@ class OfferingWriteViewModel( description = description, ), ).onSuccess { + makeFinishEvent() Log.d("alsong", "success") }.onFailure { Log.e("alsong", it.message.toString()) @@ -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) = diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OnOfferingWriteClickListener.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OnOfferingWriteClickListener.kt new file mode 100644 index 000000000..56fe0e55b --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OnOfferingWriteClickListener.kt @@ -0,0 +1,7 @@ +package com.zzang.chongdae.presentation.view.write + +interface OnOfferingWriteClickListener { + fun onDateTimeSubmitButtonClick() + + fun onDateTimeCancelButtonClick() +} diff --git a/android/app/src/main/res/drawable/bg_date_time_picker.xml b/android/app/src/main/res/drawable/bg_date_time_picker.xml new file mode 100644 index 000000000..a02929177 --- /dev/null +++ b/android/app/src/main/res/drawable/bg_date_time_picker.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/bg_date_time_picker_selected.xml b/android/app/src/main/res/drawable/bg_date_time_picker_selected.xml new file mode 100644 index 000000000..07e351fef --- /dev/null +++ b/android/app/src/main/res/drawable/bg_date_time_picker_selected.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/btn_left_arrow.xml b/android/app/src/main/res/drawable/btn_left_arrow.xml new file mode 100644 index 000000000..cabecdc24 --- /dev/null +++ b/android/app/src/main/res/drawable/btn_left_arrow.xml @@ -0,0 +1,13 @@ + + + diff --git a/android/app/src/main/res/drawable/btn_right_arrow.xml b/android/app/src/main/res/drawable/btn_right_arrow.xml new file mode 100644 index 000000000..1080f75ba --- /dev/null +++ b/android/app/src/main/res/drawable/btn_right_arrow.xml @@ -0,0 +1,13 @@ + + + diff --git a/android/app/src/main/res/drawable/btn_rounded_gray.xml b/android/app/src/main/res/drawable/btn_rounded_gray.xml new file mode 100644 index 000000000..cde21b032 --- /dev/null +++ b/android/app/src/main/res/drawable/btn_rounded_gray.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_offering_detail.xml b/android/app/src/main/res/layout/activity_offering_detail.xml index d3cb891b8..261454b30 100644 --- a/android/app/src/main/res/layout/activity_offering_detail.xml +++ b/android/app/src/main/res/layout/activity_offering_detail.xml @@ -128,7 +128,7 @@ app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintStart_toEndOf="@id/view_divide_line" + app:layout_constraintTop_toTopOf="@id/view_divide_line" /> + + + + + + + + + + + + + + + + + + + + + + + + + + +