Skip to content

Commit

Permalink
feat: 이미지 업로드 중일 때 로딩 상태 설정 (#317)
Browse files Browse the repository at this point in the history
* feat: 공모 글 작성 ui state 구현

* feat: 로딩 progressbar 생성

* feat: UI 상태에 따른 토스트 메시지 처리

* refactor: 잘못된 입력에 대한 에러 처리 변경
  • Loading branch information
chaehyuns authored and ChooSeoyeon committed Oct 11, 2024
1 parent f2e296a commit 94d4504
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class OfferingWriteFragment : Fragment(), OnOfferingWriteClickListener {

private var _dateTimePickerBinding: DialogDateTimePickerBinding? = null
private val dateTimePickerBinding get() = _dateTimePickerBinding!!

private var toast: Toast? = null
private val dialog: Dialog by lazy { Dialog(requireActivity()) }
private lateinit var permissionManager: PermissionManager
Expand Down Expand Up @@ -73,13 +74,17 @@ class OfferingWriteFragment : Fragment(), OnOfferingWriteClickListener {
) {
super.onViewCreated(view, savedInstanceState)
(activity as MainActivity).hideBottomNavigation()
observeInvalidInputEvent()
observeFinishEvent()
observeImageUploadEvent()
setUpObserve()
selectDeadline()
searchPlace()
}

private fun setUpObserve() {
observeFinishEvent()
observeImageUploadEvent()
observeUIState()
}

private fun initializePhotoPicker() {
pickMediaLauncher =
registerForActivityResult(PickVisualMedia()) { uri: Uri? ->
Expand Down Expand Up @@ -121,6 +126,23 @@ class OfferingWriteFragment : Fragment(), OnOfferingWriteClickListener {
}
}

private fun observeUIState() {
viewModel.writeUIState.observe(viewLifecycleOwner) { state ->
when (state) {
is WriteUIState.Error -> {
showToast(state.message)
}
is WriteUIState.Empty -> {
showToast(state.message)
}
is WriteUIState.InvalidInput -> {
showToast(state.message)
}
else -> {}
}
}
}

private fun onPermissionsGranted() {
showToast(R.string.all_permission_granted)
launchPhotoPicker()
Expand Down Expand Up @@ -228,24 +250,6 @@ class OfferingWriteFragment : Fragment(), OnOfferingWriteClickListener {
dateTimePickerBinding.onClickListener = this
}

private fun observeInvalidInputEvent() {
viewModel.invalidTotalCountEvent.observe(viewLifecycleOwner) {
showToast(R.string.write_invalid_total_count)
}
viewModel.invalidTotalPriceEvent.observe(viewLifecycleOwner) {
showToast(R.string.write_invalid_total_price)
}
viewModel.invalidOriginPriceEvent.observe(viewLifecycleOwner) {
showToast(R.string.write_invalid_each_price)
}
viewModel.originPriceCheaperThanSplitPriceEvent.observe(viewLifecycleOwner) {
showToast(R.string.write_origin_price_cheaper_than_total_price)
}
viewModel.errorEvent.observe(viewLifecycleOwner) {
showToast(it)
}
}

private fun observeFinishEvent() {
viewModel.finishEvent.observe(viewLifecycleOwner) {
firebaseAnalyticsManager.logSelectContentEvent(
Expand All @@ -259,13 +263,13 @@ class OfferingWriteFragment : Fragment(), OnOfferingWriteClickListener {
}

private fun showToast(
@StringRes message: Int,
@StringRes messageId: Int,
) {
toast?.cancel()
toast =
Toast.makeText(
requireActivity(),
message,
getString(messageId),
Toast.LENGTH_SHORT,
)
toast?.show()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,27 +51,12 @@ class OfferingWriteViewModel(

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

private val _errorEvent: MutableSingleLiveData<Int> = MutableSingleLiveData()
val errorEvent: SingleLiveData<Int> get() = _errorEvent

private val _submitButtonEnabled: MediatorLiveData<Boolean> = MediatorLiveData(false)
val submitButtonEnabled: LiveData<Boolean> get() = _submitButtonEnabled

private val _extractButtonEnabled: MediatorLiveData<Boolean> = MediatorLiveData(false)
val extractButtonEnabled: LiveData<Boolean> get() = _extractButtonEnabled

private val _invalidTotalCountEvent: MutableSingleLiveData<Boolean> = MutableSingleLiveData()
val invalidTotalCountEvent: SingleLiveData<Boolean> get() = _invalidTotalCountEvent

private val _invalidTotalPriceEvent: MutableSingleLiveData<Boolean> = MutableSingleLiveData()
val invalidTotalPriceEvent: SingleLiveData<Boolean> get() = _invalidTotalPriceEvent

private val _invalidOriginPriceEvent: MutableSingleLiveData<Boolean> = MutableSingleLiveData()
val invalidOriginPriceEvent: SingleLiveData<Boolean> get() = _invalidOriginPriceEvent

private val _originPriceCheaperThanSplitPriceEvent: MutableSingleLiveData<Boolean> = MutableSingleLiveData()
val originPriceCheaperThanSplitPriceEvent: SingleLiveData<Boolean> get() = _originPriceCheaperThanSplitPriceEvent

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

Expand All @@ -93,6 +78,11 @@ class OfferingWriteViewModel(
private val _imageUploadEvent = MutableLiveData<Unit>()
val imageUploadEvent: LiveData<Unit> get() = _imageUploadEvent

private val _writeUIState = MutableLiveData<WriteUIState>(WriteUIState.Initial)
val writeUIState: LiveData<WriteUIState> get() = _writeUIState

val isLoading: LiveData<Boolean> = _writeUIState.map { it is WriteUIState.Loading }

init {
_submitButtonEnabled.apply {
addSource(title) { updateSubmitButtonEnabled() }
Expand Down Expand Up @@ -137,26 +127,29 @@ class OfferingWriteViewModel(

fun uploadImageFile(multipartBody: MultipartBody.Part) {
viewModelScope.launch {
_writeUIState.value = WriteUIState.Loading
offeringRepository.saveProductImageS3(multipartBody).onSuccess {
_writeUIState.value = WriteUIState.Success(it.imageUrl)
thumbnailUrl.value = it.imageUrl
}.onFailure {
Log.e("error", it.message.toString())
_errorEvent.setValue(R.string.all_error_image_upload)
_writeUIState.value = WriteUIState.Error(R.string.error_invalid_product_url, it.message.toString())
}
}
}

fun postProductImageOg() {
viewModelScope.launch {
_writeUIState.value = WriteUIState.Loading
offeringRepository.saveProductImageOg(productUrl.value ?: "").onSuccess {
if (it.imageUrl.isNullOrBlank()) {
_errorEvent.setValue(R.string.error_empty_product_url)
if (it.imageUrl.isBlank()) {
_writeUIState.value = WriteUIState.Empty(R.string.error_empty_product_url)
return@launch
}
thumbnailUrl.value = HTTPS + it.imageUrl
}.onFailure {
Log.e("error", it.message.toString())
_errorEvent.setValue(R.string.error_invalid_product_url)
_writeUIState.value = WriteUIState.Error(R.string.error_invalid_product_url, it.message.toString())
}
}
}
Expand Down Expand Up @@ -224,7 +217,6 @@ class OfferingWriteViewModel(
deadline.value = dateTime
}

// memberId는 임시값을 보내고 있음!
fun postOffering() {
val memberId = BuildConfig.TOKEN.toLong()
val title = title.value ?: return
Expand All @@ -245,7 +237,7 @@ class OfferingWriteViewModel(
makeOriginPriceInvalidEvent()
return
}
if (isOriginPriceCheaperThanSplitPriceEvent() == true) return
if (isOriginPriceCheaperThanSplitPriceEvent()) return

viewModelScope.launch {
offeringRepository.saveOffering(
Expand All @@ -268,6 +260,7 @@ class OfferingWriteViewModel(
makeFinishEvent()
}.onFailure {
Log.e("error", it.message.toString())
_writeUIState.value = WriteUIState.Error(R.string.write_error_writing, it.message.toString())
}
}
}
Expand All @@ -286,7 +279,7 @@ class OfferingWriteViewModel(
private fun makeTotalCountInvalidEvent(totalCount: String): Int? {
val totalCountConverted = totalCount.trim().toIntOrNull() ?: ERROR_INTEGER_FORMAT
if (totalCountConverted < MINIMUM_TOTAL_COUNT || totalCountConverted > MAXIMUM_TOTAL_COUNT) {
_invalidTotalCountEvent.setValue(true)
_writeUIState.value = WriteUIState.InvalidInput(R.string.write_invalid_total_count)
return null
}
return totalCountConverted
Expand All @@ -295,21 +288,21 @@ class OfferingWriteViewModel(
private fun makeTotalPriceInvalidEvent(totalPrice: String): Int? {
val totalPriceConverted = totalPrice.trim().toIntOrNull() ?: ERROR_INTEGER_FORMAT
if (totalPriceConverted < 0) {
_invalidTotalPriceEvent.setValue(true)
_writeUIState.value = WriteUIState.InvalidInput(R.string.write_invalid_total_price)
return null
}
return totalPriceConverted
}

private fun makeOriginPriceInvalidEvent() {
_invalidOriginPriceEvent.setValue(true)
_writeUIState.value = WriteUIState.InvalidInput(R.string.write_invalid_each_price)
}

private fun isOriginPriceCheaperThanSplitPriceEvent(): Boolean {
if (originPrice.value.isNullOrBlank()) return false
val discountRateValue = discountRate.value ?: ERROR_FLOAT_FORMAT
if (discountRateValue <= 0f) {
_originPriceCheaperThanSplitPriceEvent.setValue(true)
_writeUIState.value = WriteUIState.InvalidInput(R.string.write_origin_price_cheaper_than_total_price)
return true
}
return false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.zzang.chongdae.presentation.view.write

import androidx.annotation.StringRes

sealed class WriteUIState {
data class Empty(
@StringRes val message: Int,
) : WriteUIState()

data object Initial : WriteUIState()

data object Loading : WriteUIState()

data class InvalidInput(
@StringRes val message: Int,
) : WriteUIState()

data class Success(val url: String) : WriteUIState()

data class Error(
@StringRes val message: Int,
val errorMessage: String,
) : WriteUIState()
}
16 changes: 13 additions & 3 deletions android/app/src/main/res/layout/fragment_offering_write.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/size_26">

<TextView
android:id="@+id/tv_information_message"
android:layout_width="wrap_content"
Expand Down Expand Up @@ -129,8 +129,8 @@

<ImageView
android:id="@+id/iv_delete_image"
android:layout_width="@dimen/icon_size_30"
android:layout_height="@dimen/icon_size_30"
android:layout_width="@dimen/icon_size_24"
android:layout_height="@dimen/icon_size_24"
android:onClick="@{() -> vm.clearProductImage()}"
android:src="@drawable/btn_offering_write_delete_image"
app:isVisible="@{vm.deleteImageVisible}"
Expand Down Expand Up @@ -477,6 +477,16 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_contents" />

<ProgressBar
android:id="@+id/progressBar"
android:layout_width="@dimen/size_20"
android:layout_height="@dimen/size_20"
app:isVisible="@{vm.isLoading}"
app:layout_constraintBottom_toBottomOf="@+id/iv_upload_photo"
app:layout_constraintEnd_toEndOf="@+id/iv_upload_photo"
app:layout_constraintStart_toStartOf="@+id/iv_upload_photo"
app:layout_constraintTop_toTopOf="@+id/iv_upload_photo" />

</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</layout>
1 change: 1 addition & 0 deletions android/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -125,5 +125,6 @@
<string name="write_selected_date">%d년 %d월 %d일</string>
<string name="write_selected_time">%s %d시 %d분</string>
<string name="write_success_writing">공모가 게시되었어요!</string>
<string name="write_error_writing">공모 게시에 실패했어요🥲</string>
<string name="update_state_dialog_message">공동구매 <font color="#F15642">진행 상황</font>을 <font color="#F15642">변경</font>하시겠습니까?</string>
</resources>

0 comments on commit 94d4504

Please sign in to comment.