From a53918918dba714a69cb5e92bc95e5d9b908264c Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Thu, 18 Jul 2024 13:51:05 +0900 Subject: [PATCH 01/49] =?UTF-8?q?[fix/#33]=20=EB=B8=94=EB=9F=AD=EB=B3=84?= =?UTF-8?q?=20=EC=A2=8C=EC=84=9D=20=EC=97=B4/=EB=B2=88=20=EB=B2=94?= =?UTF-8?q?=EC=9C=84=20=EC=A1=B0=ED=9A=8C=20API=20=EC=97=B0=EA=B2=B0=20?= =?UTF-8?q?=EB=B0=8F=20=ED=8A=B9=EC=A0=95=20=EA=B5=AC=EC=97=AD=20=EC=97=B4?= =?UTF-8?q?=EB=B3=84=20UI=20=EB=B6=84=EA=B8=B0=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 4 +- .../data/datasource/SeatReviewDataSource.kt | 6 +- .../remote/SeatReviewDataSourceImpl.kt | 8 +-- ...eSeatMaxDto.kt => ResponseSeatRangeDto.kt} | 12 ++-- .../data/remote/SeatReviewService.kt | 6 +- .../repository/SeatReviewRepositoryImpl.kt | 12 ++-- .../{SeatMaxModel.kt => SeatRangeModel.kt} | 2 +- .../domain/repository/SeatReviewRepository.kt | 6 +- .../seatReview/ReviewViewModel.kt | 31 ++++++++- .../seatReview/dialog/SelectSeatDialog.kt | 67 +++++++++++++++++-- .../fragment_select_seat_bottom_sheet.xml | 6 +- "\353\254\264\354\240\234" | 2 +- 12 files changed, 125 insertions(+), 37 deletions(-) rename data/src/main/java/com/depromeet/data/model/response/seatReview/{ResponseSeatMaxDto.kt => ResponseSeatRangeDto.kt} (76%) rename domain/src/main/java/com/depromeet/domain/entity/response/seatReview/{SeatMaxModel.kt => SeatRangeModel.kt} (90%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f7bc7420..e2850254 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -63,12 +63,12 @@ android:screenOrientation="portrait" /> diff --git a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt index 3eae47fd..d5011e0f 100644 --- a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt +++ b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt @@ -2,7 +2,7 @@ package com.depromeet.data.datasource import com.depromeet.data.model.request.RequestSeatReviewDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto -import com.depromeet.data.model.response.seatReview.ResponseSeatMaxDto +import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto @@ -18,10 +18,10 @@ interface SeatReviewDataSource { sectionId: Int, ): List - suspend fun getSeatMaxData( + suspend fun getSeatRangeData( stadiumId: Int, sectionId: Int, - ): ResponseSeatMaxDto + ): List suspend fun postSeatReviewData( requestSeatReviewDto: RequestSeatReviewDto, diff --git a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt index 32053480..f7afa536 100644 --- a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt +++ b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt @@ -3,7 +3,7 @@ package com.depromeet.data.datasource.remote import com.depromeet.data.datasource.SeatReviewDataSource import com.depromeet.data.model.request.RequestSeatReviewDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto -import com.depromeet.data.model.response.seatReview.ResponseSeatMaxDto +import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto import com.depromeet.data.remote.SeatReviewService @@ -29,11 +29,11 @@ class SeatReviewDataSourceImpl @Inject constructor( return seatReviewService.getSeatBlock(stadiumId, sectionId) } - override suspend fun getSeatMaxData( + override suspend fun getSeatRangeData( stadiumId: Int, sectionId: Int, - ): ResponseSeatMaxDto { - return seatReviewService.getSeatMax(stadiumId, sectionId) + ): List { + return seatReviewService.getSeatRange(stadiumId, sectionId) } override suspend fun postSeatReviewData(requestSeatReviewDto: RequestSeatReviewDto) { diff --git a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatMaxDto.kt b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatRangeDto.kt similarity index 76% rename from data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatMaxDto.kt rename to data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatRangeDto.kt index 989609f1..83e6c23a 100644 --- a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatMaxDto.kt +++ b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatRangeDto.kt @@ -1,11 +1,11 @@ package com.depromeet.data.model.response.seatReview -import com.depromeet.domain.entity.response.seatReview.SeatMaxModel +import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class ResponseSeatMaxDto( +data class ResponseSeatRangeDto( @SerialName("id") val id: Int, @SerialName("code") @@ -24,8 +24,8 @@ data class ResponseSeatMaxDto( @SerialName("maxSeatNum") val maxSeatNum: Int, ) { - fun toRowInfo(): SeatMaxModel.RowInfo { - return SeatMaxModel.RowInfo( + fun toRowInfo(): SeatRangeModel.RowInfo { + return SeatRangeModel.RowInfo( id = id, number = number, minSeatNum = minSeatNum, @@ -34,8 +34,8 @@ data class ResponseSeatMaxDto( } } - fun toSeatMax(): SeatMaxModel { - return SeatMaxModel( + fun toSeatRange(): SeatRangeModel { + return SeatRangeModel( id = id, code = code, rowInfo = rowInfo.map { it.toRowInfo() }, diff --git a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt index 43280bb0..351dec8a 100644 --- a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt +++ b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt @@ -2,7 +2,7 @@ package com.depromeet.data.remote import com.depromeet.data.model.request.RequestSeatReviewDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto -import com.depromeet.data.model.response.seatReview.ResponseSeatMaxDto +import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto import retrofit2.http.Body @@ -26,10 +26,10 @@ interface SeatReviewService { ): List @GET("/api/v1/stadiums/{stadiumId}/sections/{sectionId}/blocks/rows") - suspend fun getSeatMax( + suspend fun getSeatRange( @Path("stadiumId") stadiumId: Int, @Path("sectionId") sectionId: Int, - ): ResponseSeatMaxDto + ): List @POST("/api/v1/reviews") suspend fun postSeatReview( diff --git a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt index 6b0ab8a0..735ffd72 100644 --- a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt +++ b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt @@ -4,7 +4,7 @@ import com.depromeet.data.datasource.SeatReviewDataSource import com.depromeet.data.model.request.toSeatReview import com.depromeet.domain.entity.request.SeatReviewModel import com.depromeet.domain.entity.response.seatReview.SeatBlockModel -import com.depromeet.domain.entity.response.seatReview.SeatMaxModel +import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import com.depromeet.domain.entity.response.seatReview.StadiumNameModel import com.depromeet.domain.entity.response.seatReview.StadiumSectionModel import com.depromeet.domain.repository.SeatReviewRepository @@ -41,15 +41,13 @@ class SeatReviewRepositoryImpl @Inject constructor( } } - override suspend fun getSeatMax( + override suspend fun getSeatRange( stadiumId: Int, sectionId: Int, - ): Result { + ): Result> { return runCatching { - seatReviewDataSource.getSeatMaxData( - stadiumId, - sectionId, - ).toSeatMax() + val response = seatReviewDataSource.getSeatRangeData(stadiumId, sectionId) + response.map { it.toSeatRange() } } } diff --git a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatMaxModel.kt b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatRangeModel.kt similarity index 90% rename from domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatMaxModel.kt rename to domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatRangeModel.kt index ebc467e6..15faa4af 100644 --- a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatMaxModel.kt +++ b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatRangeModel.kt @@ -1,6 +1,6 @@ package com.depromeet.domain.entity.response.seatReview -data class SeatMaxModel( +data class SeatRangeModel( val id: Int, val code: String, val rowInfo: List, diff --git a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt index d60f11e8..dd9961dc 100644 --- a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt +++ b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt @@ -2,7 +2,7 @@ package com.depromeet.domain.repository import com.depromeet.domain.entity.request.SeatReviewModel import com.depromeet.domain.entity.response.seatReview.SeatBlockModel -import com.depromeet.domain.entity.response.seatReview.SeatMaxModel +import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import com.depromeet.domain.entity.response.seatReview.StadiumNameModel import com.depromeet.domain.entity.response.seatReview.StadiumSectionModel @@ -18,10 +18,10 @@ interface SeatReviewRepository { sectionId: Int, ): Result> - suspend fun getSeatMax( + suspend fun getSeatRange( stadiumId: Int, sectionId: Int, - ): Result + ): Result> suspend fun postSeatReview( seatReviewInfo: SeatReviewModel, diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index 12a431f2..1f84da9e 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.depromeet.core.state.UiState import com.depromeet.domain.entity.response.seatReview.SeatBlockModel +import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import com.depromeet.domain.entity.response.seatReview.StadiumNameModel import com.depromeet.domain.entity.response.seatReview.StadiumSectionModel import com.depromeet.domain.repository.SeatReviewRepository @@ -75,6 +76,9 @@ class ReviewViewModel @Inject constructor( private val _seatBlockState = MutableStateFlow>>(UiState.Empty) val seatBlockState: StateFlow>> = _seatBlockState + private val _seatRangeState = MutableStateFlow>>(UiState.Empty) + val seatRangeState: StateFlow>> = _seatRangeState + fun setSelectedStadiumId(stadiumId: Int) { _selectedStadiumId.value = stadiumId } @@ -142,7 +146,6 @@ class ReviewViewModel @Inject constructor( } } } - fun getSeatBlock(stadiumId: Int, sectionId: Int) { viewModelScope.launch { _seatBlockState.value = UiState.Loading @@ -188,4 +191,30 @@ class ReviewViewModel @Inject constructor( } } } + + fun getSeatRange(stadiumId: Int, sectionId: Int) { + viewModelScope.launch { + _seatRangeState.value = UiState.Loading + seatReviewRepository.getSeatRange( + stadiumId, + sectionId, + ).onSuccess { range -> + Timber.d("GET RANGE SUCCESS : $range") + if (range.isEmpty()) { + _seatRangeState.value = UiState.Empty + } else { + _seatRangeState.value = UiState.Success(range) + } + } + .onFailure { t -> + if (t is HttpException) { + Timber.e("GET RANGE FAILURE : $t") + _seatRangeState.value = UiState.Failure(t.code().toString()) + } + } + } + } + } + + diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index 6a6c4909..5f27b714 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -20,6 +20,7 @@ import coil.load import com.depromeet.core.base.BindingBottomSheetDialog import com.depromeet.core.state.UiState import com.depromeet.domain.entity.response.seatReview.SeatBlockModel +import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import com.depromeet.presentation.R import com.depromeet.presentation.databinding.FragmentSelectSeatBottomSheetBinding import com.depromeet.presentation.extension.setOnSingleClickListener @@ -68,11 +69,15 @@ class SelectSeatDialog : BindingBottomSheetDialog { toast("이미지 오류") } + is UiState.Failure -> { + toast("오류가 발생했습니다") + } + is UiState.Loading -> {} is UiState.Empty -> { toast("오류가 발생했습니다") } + else -> {} } } @@ -81,8 +86,14 @@ class SelectSeatDialog : BindingBottomSheetDialog when (state) { - is UiState.Success -> { observeSuccessSeatBlock(state.data) } - is UiState.Failure -> { toast("오류가 발생했습니다") } + is UiState.Success -> { + observeSuccessSeatBlock(state.data) + } + + is UiState.Failure -> { + toast("오류가 발생했습니다") + } + is UiState.Loading -> {} is UiState.Empty -> {} else -> {} @@ -90,9 +101,56 @@ class SelectSeatDialog : BindingBottomSheetDialog + when (state) { + is UiState.Success -> { + // TODO : 특정 구역의 열이 하나 일 때 UI 분기 처리 + state.data.forEach { range -> + observeSeatRangeColumnUI(range.rowInfo) + } + } + + // TODO : Code(블록)에 포함된 number가 없을 때 + // TODO : -> et_column 빨간색 / tv_none_column_warning - visible / 텍스트는 그대로 + + // TODO : 선택한 code(블록)에서 number(열) 입력 시 number에 포함된 minSeatNum - maxSeatNum 에 포함 안될 시 et_column,et_number 빨강 / tv_none_column_warning - visible & 존재하지 않는 번이에요 / + // TODO : Code(블록)에 포함된 number가 없을 때 + + // TODO : 만약 둘 다 성립 X -> et_column, et_number 둘다 빨강 / 텍스트 존재하지 않는 열과 번이에요 + + is UiState.Failure -> { + toast("오류가 발생했습니다") + } + is UiState.Loading -> {} + is UiState.Empty -> {} + else -> {} + } + } + } + + private fun observeSeatRangeColumnUI(rowInfoList: List) { + if (rowInfoList.size == 1) { + with(binding) { + etColumn.visibility = INVISIBLE + tvColumn.visibility = INVISIBLE + etNumber.visibility = INVISIBLE + etNonColumnNumber.visibility = VISIBLE + } + } else { + with(binding) { + etColumn.visibility = VISIBLE + tvColumn.visibility = VISIBLE + etNumber.visibility = VISIBLE + etNonColumnNumber.visibility = INVISIBLE + } + } + } + private fun observeSuccessSeatBlock(blockItems: List) { val blockCodes = blockItems.map { it.code } - val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, blockCodes) + val adapter = + ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, blockCodes) adapter.setDropDownViewResource(R.layout.custom_spinner_dropdown_item) with(binding.spinnerBlock) { @@ -122,6 +180,7 @@ class SelectSeatDialog : BindingBottomSheetDialog + + Date: Thu, 18 Jul 2024 16:48:34 +0900 Subject: [PATCH 02/49] =?UTF-8?q?[feat/#33]=20=EC=97=B4/=EB=B2=88=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=B2=98=EB=A6=AC=20UI=20=EB=B6=84?= =?UTF-8?q?=EA=B8=B0=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/SeatReviewRepositoryImpl.kt | 2 +- .../seatReview/dialog/SelectSeatDialog.kt | 74 ++++++++++++++++--- .../fragment_select_seat_bottom_sheet.xml | 6 +- 3 files changed, 66 insertions(+), 16 deletions(-) diff --git a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt index 735ffd72..b2f21f7f 100644 --- a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt +++ b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt @@ -44,7 +44,7 @@ class SeatReviewRepositoryImpl @Inject constructor( override suspend fun getSeatRange( stadiumId: Int, sectionId: Int, - ): Result> { + ): Result> { return runCatching { val response = seatReviewDataSource.getSeatRangeData(stadiumId, sectionId) response.map { it.toSeatRange() } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index 5f27b714..cf788c9e 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -48,6 +48,7 @@ class SelectSeatDialog : BindingBottomSheetDialog when (state) { is UiState.Success -> { - // TODO : 특정 구역의 열이 하나 일 때 UI 분기 처리 state.data.forEach { range -> - observeSeatRangeColumnUI(range.rowInfo) + updateIsExistedColumnUI(range.rowInfo) + updateColumnNUmberUI(range) } } - // TODO : Code(블록)에 포함된 number가 없을 때 - // TODO : -> et_column 빨간색 / tv_none_column_warning - visible / 텍스트는 그대로 - - // TODO : 선택한 code(블록)에서 number(열) 입력 시 number에 포함된 minSeatNum - maxSeatNum 에 포함 안될 시 et_column,et_number 빨강 / tv_none_column_warning - visible & 존재하지 않는 번이에요 / - // TODO : Code(블록)에 포함된 number가 없을 때 - - // TODO : 만약 둘 다 성립 X -> et_column, et_number 둘다 빨강 / 텍스트 존재하지 않는 열과 번이에요 - is UiState.Failure -> { toast("오류가 발생했습니다") } + is UiState.Loading -> {} is UiState.Empty -> {} else -> {} @@ -129,7 +123,32 @@ class SelectSeatDialog : BindingBottomSheetDialog) { + private fun updateColumnNUmberUI(range: SeatRangeModel) { + val selectedNumber = viewModel.selectedNumber.value.toIntOrNull() + val selectedBlock = viewModel.selectedBlock.value + val selectedColumn = viewModel.selectedColumn.value + + if (range.code == selectedBlock) { + val matchingRowInfo = range.rowInfo.find { it.number.toString() == selectedColumn } + if (matchingRowInfo == null) { + updateColumnWarning("존재하지 않는 열이에요") + } else { + if (selectedNumber != null && (selectedNumber < matchingRowInfo.minSeatNum || selectedNumber > matchingRowInfo.maxSeatNum)) { + updateNumberWarning("존재하지 않는 번이에요") + } else { + updateBackWarnings() + } + } + } else { + if (selectedNumber == null || range.rowInfo.none { it.minSeatNum <= selectedNumber && it.maxSeatNum >= selectedNumber }) { + updateBothWarnings("존재하지 않는 열과 번이에요") + } else { + updateBackWarnings() + } + } + } + + private fun updateIsExistedColumnUI(rowInfoList: List) { if (rowInfoList.size == 1) { with(binding) { etColumn.visibility = INVISIBLE @@ -255,4 +274,37 @@ class SelectSeatDialog : BindingBottomSheetDialog - - Date: Thu, 18 Jul 2024 22:36:17 +0900 Subject: [PATCH 03/49] =?UTF-8?q?[feat/#33]=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=EC=9A=A9=20presigned=20url=20=EC=83=9D=EC=84=B1=20API=20data,?= =?UTF-8?q?=20domain=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasource/SeatReviewDataSource.kt | 7 +++++++ .../remote/SeatReviewDataSourceImpl.kt | 9 +++++++++ .../data/model/request/RequestUploadUrlDto.kt | 15 +++++++++++++++ .../response/seatReview/ResponseUploadUrlDto.kt | 16 ++++++++++++++++ .../depromeet/data/remote/SeatReviewService.kt | 8 ++++++++ .../data/repository/SeatReviewRepositoryImpl.kt | 15 +++++++++++++++ .../entity/request/RequestUploadUrlModel.kt | 5 +++++ .../seatReview/ResponseUploadUrlModel.kt | 5 +++++ .../domain/repository/SeatReviewRepository.kt | 7 +++++++ 9 files changed, 87 insertions(+) create mode 100644 data/src/main/java/com/depromeet/data/model/request/RequestUploadUrlDto.kt create mode 100644 data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseUploadUrlDto.kt create mode 100644 domain/src/main/java/com/depromeet/domain/entity/request/RequestUploadUrlModel.kt create mode 100644 domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponseUploadUrlModel.kt diff --git a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt index d5011e0f..968f2efd 100644 --- a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt +++ b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt @@ -1,10 +1,12 @@ package com.depromeet.data.datasource import com.depromeet.data.model.request.RequestSeatReviewDto +import com.depromeet.data.model.request.RequestUploadUrlDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto +import com.depromeet.data.model.response.seatReview.ResponseUploadUrlDto interface SeatReviewDataSource { suspend fun getStadiumNameData(): List @@ -26,4 +28,9 @@ interface SeatReviewDataSource { suspend fun postSeatReviewData( requestSeatReviewDto: RequestSeatReviewDto, ) + + suspend fun postUploadUrlData( + memberId: Int, + requestUploadUrlDto: RequestUploadUrlDto, + ): ResponseUploadUrlDto } diff --git a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt index f7afa536..e35ecac8 100644 --- a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt +++ b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt @@ -2,10 +2,12 @@ package com.depromeet.data.datasource.remote import com.depromeet.data.datasource.SeatReviewDataSource import com.depromeet.data.model.request.RequestSeatReviewDto +import com.depromeet.data.model.request.RequestUploadUrlDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto +import com.depromeet.data.model.response.seatReview.ResponseUploadUrlDto import com.depromeet.data.remote.SeatReviewService import javax.inject.Inject @@ -39,4 +41,11 @@ class SeatReviewDataSourceImpl @Inject constructor( override suspend fun postSeatReviewData(requestSeatReviewDto: RequestSeatReviewDto) { return seatReviewService.postSeatReview(requestSeatReviewDto) } + + override suspend fun postUploadUrlData( + memberId: Int, + requestUploadUrlDto: RequestUploadUrlDto, + ): ResponseUploadUrlDto { + return seatReviewService.postUploadUrl(memberId, requestUploadUrlDto) + } } diff --git a/data/src/main/java/com/depromeet/data/model/request/RequestUploadUrlDto.kt b/data/src/main/java/com/depromeet/data/model/request/RequestUploadUrlDto.kt new file mode 100644 index 00000000..06a7b385 --- /dev/null +++ b/data/src/main/java/com/depromeet/data/model/request/RequestUploadUrlDto.kt @@ -0,0 +1,15 @@ +package com.depromeet.data.model.request + +import com.depromeet.domain.entity.request.RequestUploadUrlModel +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RequestUploadUrlDto( + @SerialName("fileExtension") + val fileExtension: String, +) + +fun RequestUploadUrlModel.toRequestUploadUrl(): RequestUploadUrlDto { + return RequestUploadUrlDto(fileExtension) +} diff --git a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseUploadUrlDto.kt b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseUploadUrlDto.kt new file mode 100644 index 00000000..d98a4e68 --- /dev/null +++ b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseUploadUrlDto.kt @@ -0,0 +1,16 @@ +package com.depromeet.data.model.response.seatReview + +import com.depromeet.domain.entity.response.seatReview.RecommendRequestModel +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseUploadUrlDto( + @SerialName("presignedUrl") + val presignedUrl: String, +) { + + fun toResponseUploadUrl(): RecommendRequestModel { + return RecommendRequestModel(presignedUrl) + } +} diff --git a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt index 351dec8a..d6ce4b6a 100644 --- a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt +++ b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt @@ -1,10 +1,12 @@ package com.depromeet.data.remote import com.depromeet.data.model.request.RequestSeatReviewDto +import com.depromeet.data.model.request.RequestUploadUrlDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto +import com.depromeet.data.model.response.seatReview.ResponseUploadUrlDto import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST @@ -35,4 +37,10 @@ interface SeatReviewService { suspend fun postSeatReview( @Body requestPostSignupDto: RequestSeatReviewDto, ) + + @POST("/api/v1/members/{memberId}/reviews/images") + suspend fun postUploadUrl( + @Path("memberId") memberId: Int, + @Body requestUploadUrlDto: RequestUploadUrlDto, + ): ResponseUploadUrlDto } diff --git a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt index b2f21f7f..c843e61a 100644 --- a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt +++ b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt @@ -1,8 +1,11 @@ package com.depromeet.data.repository import com.depromeet.data.datasource.SeatReviewDataSource +import com.depromeet.data.model.request.toRequestUploadUrl import com.depromeet.data.model.request.toSeatReview +import com.depromeet.domain.entity.request.RequestUploadUrlModel import com.depromeet.domain.entity.request.SeatReviewModel +import com.depromeet.domain.entity.response.seatReview.RecommendRequestModel import com.depromeet.domain.entity.response.seatReview.SeatBlockModel import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import com.depromeet.domain.entity.response.seatReview.StadiumNameModel @@ -60,4 +63,16 @@ class SeatReviewRepositoryImpl @Inject constructor( ) } } + + override suspend fun postUploadUrl( + memberId: Int, + requestUploadUrlModel: RequestUploadUrlModel, + ): Result { + return runCatching { + seatReviewDataSource.postUploadUrlData( + memberId, + requestUploadUrlModel.toRequestUploadUrl(), + ).toResponseUploadUrl() + } + } } diff --git a/domain/src/main/java/com/depromeet/domain/entity/request/RequestUploadUrlModel.kt b/domain/src/main/java/com/depromeet/domain/entity/request/RequestUploadUrlModel.kt new file mode 100644 index 00000000..fcf49fcb --- /dev/null +++ b/domain/src/main/java/com/depromeet/domain/entity/request/RequestUploadUrlModel.kt @@ -0,0 +1,5 @@ +package com.depromeet.domain.entity.request + +data class RequestUploadUrlModel( + val fileExtension: String, +) diff --git a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponseUploadUrlModel.kt b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponseUploadUrlModel.kt new file mode 100644 index 00000000..3c8b889d --- /dev/null +++ b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponseUploadUrlModel.kt @@ -0,0 +1,5 @@ +package com.depromeet.domain.entity.response.seatReview + +data class RecommendRequestModel( + val presignedUrl: String, +) diff --git a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt index dd9961dc..c24887e7 100644 --- a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt +++ b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt @@ -1,6 +1,8 @@ package com.depromeet.domain.repository +import com.depromeet.domain.entity.request.RequestUploadUrlModel import com.depromeet.domain.entity.request.SeatReviewModel +import com.depromeet.domain.entity.response.seatReview.RecommendRequestModel import com.depromeet.domain.entity.response.seatReview.SeatBlockModel import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import com.depromeet.domain.entity.response.seatReview.StadiumNameModel @@ -26,4 +28,9 @@ interface SeatReviewRepository { suspend fun postSeatReview( seatReviewInfo: SeatReviewModel, ): Result + + suspend fun postUploadUrl( + memberId: Int, + requestUploadUrlModel: RequestUploadUrlModel, + ): Result } From e37fe7756f0da53ec917843a9dae9a28a648b442 Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Fri, 19 Jul 2024 00:05:19 +0900 Subject: [PATCH 04/49] =?UTF-8?q?[feat/#33]=20svg=20=ED=8C=8C=EC=9D=BC=20c?= =?UTF-8?q?oil=20=ED=99=95=EC=9E=A5=20=ED=95=A8=EC=88=98=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../seatReview/dialog/SelectSeatDialog.kt | 4 ++-- .../com/depromeet/presentation/util/Utils.kt | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index cf788c9e..4d213fb2 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -27,6 +27,7 @@ import com.depromeet.presentation.extension.setOnSingleClickListener import com.depromeet.presentation.extension.toast import com.depromeet.presentation.seatReview.ReviewViewModel import com.depromeet.presentation.seatReview.adapter.SectionListAdapter +import com.depromeet.presentation.util.Utils.loadImageFromUrl import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -66,8 +67,7 @@ class SelectSeatDialog : BindingBottomSheetDialog { adapter.submitList(state.data.sectionList) - // TODO : SVG IMAGE LOAD - binding.ivSeatAgain.load(state.data.seatChart) + binding.ivSeatAgain.loadImageFromUrl(state.data.seatChart) } is UiState.Failure -> { diff --git a/presentation/src/main/java/com/depromeet/presentation/util/Utils.kt b/presentation/src/main/java/com/depromeet/presentation/util/Utils.kt index 83f5dc4a..0b614106 100644 --- a/presentation/src/main/java/com/depromeet/presentation/util/Utils.kt +++ b/presentation/src/main/java/com/depromeet/presentation/util/Utils.kt @@ -2,6 +2,10 @@ package com.depromeet.presentation.util import android.content.Context import android.content.Intent +import android.widget.ImageView +import coil.ImageLoader +import coil.decode.SvgDecoder +import coil.request.ImageRequest object Utils { fun restartApp(context: Context, toastMsg: String?) { @@ -14,4 +18,21 @@ object Utils { } Runtime.getRuntime().exit(0) } + + fun ImageView.loadImageFromUrl(url: String) { + val imageLoader = ImageLoader.Builder(this.context) + .components { + add(SvgDecoder.Factory()) + } + .build() + + val request = ImageRequest.Builder(this.context) + .crossfade(true) + .crossfade(2) + .data(url) + .target(this) + .build() + + imageLoader.enqueue(request) + } } From 5917caf487a0451cffdae1076576b687d2f22f17 Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Fri, 19 Jul 2024 00:10:43 +0900 Subject: [PATCH 05/49] =?UTF-8?q?[fix/#33]=20=EC=A7=81=EA=B4=80=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0=20=EB=93=B1=EB=A1=9D=20API=20endPoint,=20dto?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=EC=82=AC=ED=95=AD=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/request/RequestSeatReviewDto.kt | 24 +++++++------------ .../data/remote/SeatReviewService.kt | 2 +- .../domain/entity/request/SeatReviewModel.kt | 8 +++---- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt b/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt index d1cbbbc6..ec0e02d4 100644 --- a/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt +++ b/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt @@ -6,18 +6,14 @@ import kotlinx.serialization.Serializable @Serializable data class RequestSeatReviewDto( - @SerialName("stadiumId") - val stadiumId: Int, - @SerialName("blockId") - val blockId: Int, - @SerialName("rowId") - val rowId: Int, - @SerialName("seatNumber") - val seatNumber: Int, + @SerialName("memberId") + val memberId: Int, + @SerialName("seatId") + val seatId: Int, @SerialName("images") val images: List, - @SerialName("date") - val date: String, + @SerialName("dateTime") + val dateTime: String, @SerialName("good") val good: List, @SerialName("bad") @@ -27,12 +23,10 @@ data class RequestSeatReviewDto( ) fun SeatReviewModel.toSeatReview() = RequestSeatReviewDto( - stadiumId = stadiumId, - blockId = blockId, - rowId = rowId, - seatNumber = seatNumber, + memberId = memberId, + seatId = seatId, images = images, - date = date, + dateTime = dateTime, good = good, bad = bad, content = content, diff --git a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt index d6ce4b6a..dc683d0f 100644 --- a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt +++ b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt @@ -33,7 +33,7 @@ interface SeatReviewService { @Path("sectionId") sectionId: Int, ): List - @POST("/api/v1/reviews") + @POST("/api/v1/seats/{seatId}/members/{memberId}/reviews") suspend fun postSeatReview( @Body requestPostSignupDto: RequestSeatReviewDto, ) diff --git a/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt b/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt index ad50cdba..23aa51f2 100644 --- a/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt +++ b/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt @@ -1,12 +1,10 @@ package com.depromeet.domain.entity.request data class SeatReviewModel( - val stadiumId: Int, - val blockId: Int, - val rowId: Int, - val seatNumber: Int, + val memberId: Int, + val seatId: Int, val images: List, - val date: String, + val dateTime: String, val good: List, val bad: List, val content: String, From 9c03716f41f41f66705d7862daacaa968d8d0b70 Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Fri, 19 Jul 2024 16:22:54 +0900 Subject: [PATCH 06/49] =?UTF-8?q?[fix/#33]=20image=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=20svg=20->=20png=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../seatReview/dialog/SelectSeatDialog.kt | 2 +- .../com/depromeet/presentation/util/Utils.kt | 17 ----------------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index 4d213fb2..56eb305c 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -67,7 +67,7 @@ class SelectSeatDialog : BindingBottomSheetDialog { adapter.submitList(state.data.sectionList) - binding.ivSeatAgain.loadImageFromUrl(state.data.seatChart) + binding.ivSeatAgain.load(state.data.seatChart) } is UiState.Failure -> { diff --git a/presentation/src/main/java/com/depromeet/presentation/util/Utils.kt b/presentation/src/main/java/com/depromeet/presentation/util/Utils.kt index 0b614106..b9bdb996 100644 --- a/presentation/src/main/java/com/depromeet/presentation/util/Utils.kt +++ b/presentation/src/main/java/com/depromeet/presentation/util/Utils.kt @@ -18,21 +18,4 @@ object Utils { } Runtime.getRuntime().exit(0) } - - fun ImageView.loadImageFromUrl(url: String) { - val imageLoader = ImageLoader.Builder(this.context) - .components { - add(SvgDecoder.Factory()) - } - .build() - - val request = ImageRequest.Builder(this.context) - .crossfade(true) - .crossfade(2) - .data(url) - .target(this) - .build() - - imageLoader.enqueue(request) - } } From 571695dafc65b0841274ee7be54a85d3b167bca5 Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Sat, 20 Jul 2024 00:04:46 +0900 Subject: [PATCH 07/49] =?UTF-8?q?[feat/#33]=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20url=20=EC=83=9D=EC=84=B1=20API=20?= =?UTF-8?q?data,=20domain=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasource/SeatReviewDataSource.kt | 14 ++++++---- .../remote/SeatReviewDataSourceImpl.kt | 22 +++++++++++---- .../model/request/RequestPreSignedUrlDto.kt | 10 +++++++ .../data/model/request/RequestUploadUrlDto.kt | 15 ---------- .../seatReview/ResponsePreSignedUrlDto.kt | 16 +++++++++++ .../seatReview/ResponseUploadUrlDto.kt | 16 ----------- .../data/remote/SeatReviewService.kt | 19 +++++++++---- .../repository/SeatReviewRepositoryImpl.kt | 28 +++++++++++++------ .../seatReview/ResponsePresignedUrlModel.kt | 5 ++++ .../seatReview/ResponseUploadUrlModel.kt | 5 ---- .../domain/repository/SeatReviewRepository.kt | 14 ++++++---- .../seatReview/dialog/SelectSeatDialog.kt | 3 +- .../com/depromeet/presentation/util/Utils.kt | 4 --- 13 files changed, 100 insertions(+), 71 deletions(-) create mode 100644 data/src/main/java/com/depromeet/data/model/request/RequestPreSignedUrlDto.kt delete mode 100644 data/src/main/java/com/depromeet/data/model/request/RequestUploadUrlDto.kt create mode 100644 data/src/main/java/com/depromeet/data/model/response/seatReview/ResponsePreSignedUrlDto.kt delete mode 100644 data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseUploadUrlDto.kt create mode 100644 domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponsePresignedUrlModel.kt delete mode 100644 domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponseUploadUrlModel.kt diff --git a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt index 968f2efd..d94ded4a 100644 --- a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt +++ b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt @@ -1,12 +1,11 @@ package com.depromeet.data.datasource import com.depromeet.data.model.request.RequestSeatReviewDto -import com.depromeet.data.model.request.RequestUploadUrlDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto -import com.depromeet.data.model.response.seatReview.ResponseUploadUrlDto +import com.depromeet.data.model.response.seatReview.ResponsePreSignedUrlDto interface SeatReviewDataSource { suspend fun getStadiumNameData(): List @@ -29,8 +28,13 @@ interface SeatReviewDataSource { requestSeatReviewDto: RequestSeatReviewDto, ) - suspend fun postUploadUrlData( + suspend fun postImagePreSignedData( + fileExtension: String, memberId: Int, - requestUploadUrlDto: RequestUploadUrlDto, - ): ResponseUploadUrlDto + ): ResponsePreSignedUrlDto + + suspend fun putReviewImageData( + presignedUrl: String, + image: ByteArray, + ) } diff --git a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt index e35ecac8..9a99a4dd 100644 --- a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt +++ b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt @@ -2,13 +2,15 @@ package com.depromeet.data.datasource.remote import com.depromeet.data.datasource.SeatReviewDataSource import com.depromeet.data.model.request.RequestSeatReviewDto -import com.depromeet.data.model.request.RequestUploadUrlDto +import com.depromeet.data.model.request.RequestPreSignedUrlDto +import com.depromeet.data.model.response.seatReview.ResponsePreSignedUrlDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto -import com.depromeet.data.model.response.seatReview.ResponseUploadUrlDto import com.depromeet.data.remote.SeatReviewService +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody.Companion.toRequestBody import javax.inject.Inject class SeatReviewDataSourceImpl @Inject constructor( @@ -42,10 +44,18 @@ class SeatReviewDataSourceImpl @Inject constructor( return seatReviewService.postSeatReview(requestSeatReviewDto) } - override suspend fun postUploadUrlData( + override suspend fun postImagePreSignedData( + fileExtension: String, memberId: Int, - requestUploadUrlDto: RequestUploadUrlDto, - ): ResponseUploadUrlDto { - return seatReviewService.postUploadUrl(memberId, requestUploadUrlDto) + ): ResponsePreSignedUrlDto { + return seatReviewService.postImagePreSignedUrl( + RequestPreSignedUrlDto(fileExtension), + memberId, + ) + } + + override suspend fun putReviewImageData(presignedUrl: String, image: ByteArray) { + val mediaType = "image/*".toMediaTypeOrNull() + return seatReviewService.putProfileImage(presignedUrl, image.toRequestBody(mediaType)) } } diff --git a/data/src/main/java/com/depromeet/data/model/request/RequestPreSignedUrlDto.kt b/data/src/main/java/com/depromeet/data/model/request/RequestPreSignedUrlDto.kt new file mode 100644 index 00000000..3ab5c0e2 --- /dev/null +++ b/data/src/main/java/com/depromeet/data/model/request/RequestPreSignedUrlDto.kt @@ -0,0 +1,10 @@ +package com.depromeet.data.model.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RequestPreSignedUrlDto( + @SerialName("fileExtension") + val fileExtension: String, +) diff --git a/data/src/main/java/com/depromeet/data/model/request/RequestUploadUrlDto.kt b/data/src/main/java/com/depromeet/data/model/request/RequestUploadUrlDto.kt deleted file mode 100644 index 06a7b385..00000000 --- a/data/src/main/java/com/depromeet/data/model/request/RequestUploadUrlDto.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.depromeet.data.model.request - -import com.depromeet.domain.entity.request.RequestUploadUrlModel -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class RequestUploadUrlDto( - @SerialName("fileExtension") - val fileExtension: String, -) - -fun RequestUploadUrlModel.toRequestUploadUrl(): RequestUploadUrlDto { - return RequestUploadUrlDto(fileExtension) -} diff --git a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponsePreSignedUrlDto.kt b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponsePreSignedUrlDto.kt new file mode 100644 index 00000000..add748d5 --- /dev/null +++ b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponsePreSignedUrlDto.kt @@ -0,0 +1,16 @@ +package com.depromeet.data.model.response.seatReview + +import com.depromeet.domain.entity.response.seatReview.ResponsePresignedUrlModel +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponsePreSignedUrlDto( + @SerialName("presignedUrl") + val presignedUrl: String, +) { + + fun toResponsePreSignedUrl(): ResponsePresignedUrlModel { + return ResponsePresignedUrlModel(presignedUrl) + } +} diff --git a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseUploadUrlDto.kt b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseUploadUrlDto.kt deleted file mode 100644 index d98a4e68..00000000 --- a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseUploadUrlDto.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.depromeet.data.model.response.seatReview - -import com.depromeet.domain.entity.response.seatReview.RecommendRequestModel -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class ResponseUploadUrlDto( - @SerialName("presignedUrl") - val presignedUrl: String, -) { - - fun toResponseUploadUrl(): RecommendRequestModel { - return RecommendRequestModel(presignedUrl) - } -} diff --git a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt index dc683d0f..0308ec40 100644 --- a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt +++ b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt @@ -1,16 +1,19 @@ package com.depromeet.data.remote import com.depromeet.data.model.request.RequestSeatReviewDto -import com.depromeet.data.model.request.RequestUploadUrlDto +import com.depromeet.data.model.request.RequestPreSignedUrlDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto -import com.depromeet.data.model.response.seatReview.ResponseUploadUrlDto +import com.depromeet.data.model.response.seatReview.ResponsePreSignedUrlDto +import okhttp3.RequestBody import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST +import retrofit2.http.PUT import retrofit2.http.Path +import retrofit2.http.Url interface SeatReviewService { @GET("/api/v1/stadiums/names") @@ -39,8 +42,14 @@ interface SeatReviewService { ) @POST("/api/v1/members/{memberId}/reviews/images") - suspend fun postUploadUrl( + suspend fun postImagePreSignedUrl( + @Body body: RequestPreSignedUrlDto, @Path("memberId") memberId: Int, - @Body requestUploadUrlDto: RequestUploadUrlDto, - ): ResponseUploadUrlDto + ): ResponsePreSignedUrlDto + + @PUT + suspend fun putProfileImage( + @Url preSignedUrl: String, + @Body image: RequestBody, + ) } diff --git a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt index c843e61a..18c91ae5 100644 --- a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt +++ b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt @@ -1,11 +1,9 @@ package com.depromeet.data.repository import com.depromeet.data.datasource.SeatReviewDataSource -import com.depromeet.data.model.request.toRequestUploadUrl import com.depromeet.data.model.request.toSeatReview -import com.depromeet.domain.entity.request.RequestUploadUrlModel import com.depromeet.domain.entity.request.SeatReviewModel -import com.depromeet.domain.entity.response.seatReview.RecommendRequestModel +import com.depromeet.domain.entity.response.seatReview.ResponsePresignedUrlModel import com.depromeet.domain.entity.response.seatReview.SeatBlockModel import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import com.depromeet.domain.entity.response.seatReview.StadiumNameModel @@ -64,15 +62,27 @@ class SeatReviewRepositoryImpl @Inject constructor( } } - override suspend fun postUploadUrl( + override suspend fun postReviewImagePresigned( + fileExtension: String, memberId: Int, - requestUploadUrlModel: RequestUploadUrlModel, - ): Result { + ): Result { return runCatching { - seatReviewDataSource.postUploadUrlData( + seatReviewDataSource.postImagePreSignedData( + fileExtension, memberId, - requestUploadUrlModel.toRequestUploadUrl(), - ).toResponseUploadUrl() + ).toResponsePreSignedUrl() + } + } + + override suspend fun putImagePreSignedUrl( + presignedUrl: String, + image: ByteArray, + ): Result { + return runCatching { + seatReviewDataSource.putReviewImageData( + presignedUrl, + image, + ) } } } diff --git a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponsePresignedUrlModel.kt b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponsePresignedUrlModel.kt new file mode 100644 index 00000000..3a09d548 --- /dev/null +++ b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponsePresignedUrlModel.kt @@ -0,0 +1,5 @@ +package com.depromeet.domain.entity.response.seatReview + +data class ResponsePresignedUrlModel( + val presignedUrl: String = "", +) diff --git a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponseUploadUrlModel.kt b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponseUploadUrlModel.kt deleted file mode 100644 index 3c8b889d..00000000 --- a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponseUploadUrlModel.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.depromeet.domain.entity.response.seatReview - -data class RecommendRequestModel( - val presignedUrl: String, -) diff --git a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt index c24887e7..21e40d31 100644 --- a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt +++ b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt @@ -1,8 +1,7 @@ package com.depromeet.domain.repository -import com.depromeet.domain.entity.request.RequestUploadUrlModel import com.depromeet.domain.entity.request.SeatReviewModel -import com.depromeet.domain.entity.response.seatReview.RecommendRequestModel +import com.depromeet.domain.entity.response.seatReview.ResponsePresignedUrlModel import com.depromeet.domain.entity.response.seatReview.SeatBlockModel import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import com.depromeet.domain.entity.response.seatReview.StadiumNameModel @@ -29,8 +28,13 @@ interface SeatReviewRepository { seatReviewInfo: SeatReviewModel, ): Result - suspend fun postUploadUrl( + suspend fun postReviewImagePresigned( + fileExtension: String, memberId: Int, - requestUploadUrlModel: RequestUploadUrlModel, - ): Result + ): Result + + suspend fun putImagePreSignedUrl( + presignedUrl: String, + image: ByteArray, + ): Result } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index 56eb305c..1468ba58 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -27,7 +27,6 @@ import com.depromeet.presentation.extension.setOnSingleClickListener import com.depromeet.presentation.extension.toast import com.depromeet.presentation.seatReview.ReviewViewModel import com.depromeet.presentation.seatReview.adapter.SectionListAdapter -import com.depromeet.presentation.util.Utils.loadImageFromUrl import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -123,6 +122,8 @@ class SelectSeatDialog : BindingBottomSheetDialog Date: Sat, 20 Jul 2024 01:02:49 +0900 Subject: [PATCH 08/49] =?UTF-8?q?[feat/#33]=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20url=20=EC=83=9D=EC=84=B1=20API=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=20=ED=86=B5=EC=8B=A0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasource/SeatReviewDataSource.kt | 2 +- .../remote/SeatReviewDataSourceImpl.kt | 2 +- .../data/remote/SeatReviewService.kt | 4 +- .../presentation/seatReview/ReviewActivity.kt | 35 ++++++++++++++- .../seatReview/ReviewViewModel.kt | 43 ++++++++++++++++++- 5 files changed, 78 insertions(+), 8 deletions(-) diff --git a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt index d94ded4a..a0ea79b3 100644 --- a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt +++ b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt @@ -1,11 +1,11 @@ package com.depromeet.data.datasource import com.depromeet.data.model.request.RequestSeatReviewDto +import com.depromeet.data.model.response.seatReview.ResponsePreSignedUrlDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto -import com.depromeet.data.model.response.seatReview.ResponsePreSignedUrlDto interface SeatReviewDataSource { suspend fun getStadiumNameData(): List diff --git a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt index 9a99a4dd..2be96346 100644 --- a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt +++ b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt @@ -1,8 +1,8 @@ package com.depromeet.data.datasource.remote import com.depromeet.data.datasource.SeatReviewDataSource -import com.depromeet.data.model.request.RequestSeatReviewDto import com.depromeet.data.model.request.RequestPreSignedUrlDto +import com.depromeet.data.model.request.RequestSeatReviewDto import com.depromeet.data.model.response.seatReview.ResponsePreSignedUrlDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto diff --git a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt index 0308ec40..bbb8594e 100644 --- a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt +++ b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt @@ -1,12 +1,12 @@ package com.depromeet.data.remote -import com.depromeet.data.model.request.RequestSeatReviewDto import com.depromeet.data.model.request.RequestPreSignedUrlDto +import com.depromeet.data.model.request.RequestSeatReviewDto +import com.depromeet.data.model.response.seatReview.ResponsePreSignedUrlDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto -import com.depromeet.data.model.response.seatReview.ResponsePreSignedUrlDto import okhttp3.RequestBody import retrofit2.http.Body import retrofit2.http.GET diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index 770c7251..976bb2f0 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -6,8 +6,10 @@ import android.os.Bundle import android.view.View import android.view.View.INVISIBLE import android.view.View.VISIBLE +import android.webkit.MimeTypeMap import android.widget.FrameLayout import android.widget.ImageView +import android.widget.Toast import androidx.activity.viewModels import androidx.core.content.ContextCompat import androidx.core.view.isVisible @@ -25,6 +27,7 @@ import com.depromeet.presentation.seatReview.dialog.ImageUploadDialog import com.depromeet.presentation.seatReview.dialog.ReviewMySeatDialog import com.depromeet.presentation.seatReview.dialog.SelectSeatDialog import dagger.hilt.android.AndroidEntryPoint +import java.io.File import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale @@ -206,7 +209,7 @@ class ReviewActivity : BaseActivity({ val block = viewModel.selectedBlock.value val column = viewModel.selectedColumn.value val number = viewModel.selectedNumber.value - if (listOf(seatName, block, column, number).any { it.isNullOrEmpty()}) { + if (listOf(seatName, block, column, number).any { it.isNullOrEmpty() }) { binding.layoutSeatInfo.visibility = INVISIBLE } else { binding.layoutSeatInfo.visibility = VISIBLE @@ -265,7 +268,35 @@ class ReviewActivity : BaseActivity({ private fun navigateToReviewDoneActivity() { binding.tvUploadBtn.setOnSingleClickListener { - Intent(this, ReviewDoneActivity::class.java).apply { startActivity(this) } + selectedImageUris.forEach { imageUriString -> + val imageUri = Uri.parse(imageUriString) + val imageFile = File(imageUri.path!!) + val fileExtension = MimeTypeMap.getFileExtensionFromUrl(imageUri.toString()) + + // presigned URL 요청 + //TODO : MemberID 받기 + viewModel.requestPresignedUrl(fileExtension, 1) + + // presigned URL 응답을 관찰 후 업로드 후 이동 + viewModel.getPreSignedUrl.asLiveData().observe(this) { state -> + when (state) { + is UiState.Success -> { + val presignedUrl = state.data.presignedUrl + val imageData = imageFile.readBytes() + // 이미지 업로드 + viewModel.uploadImageToPreSignedUrl(presignedUrl, imageData) + Intent(this, ReviewDoneActivity::class.java).apply { startActivity(this) } + } + + is UiState.Failure -> { + Toast.makeText(this, "Presigned URL 요청 실패: $state", Toast.LENGTH_SHORT) + .show() + } + + else -> {} + } + } + } } } } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index 1f84da9e..4cf4693e 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -3,6 +3,7 @@ package com.depromeet.presentation.seatReview import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.depromeet.core.state.UiState +import com.depromeet.domain.entity.response.seatReview.ResponsePresignedUrlModel import com.depromeet.domain.entity.response.seatReview.SeatBlockModel import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import com.depromeet.domain.entity.response.seatReview.StadiumNameModel @@ -79,6 +80,10 @@ class ReviewViewModel @Inject constructor( private val _seatRangeState = MutableStateFlow>>(UiState.Empty) val seatRangeState: StateFlow>> = _seatRangeState + private val _getPreSignedUrl = + MutableStateFlow>(UiState.Loading) + val getPreSignedUrl = _getPreSignedUrl.asStateFlow() + fun setSelectedStadiumId(stadiumId: Int) { _selectedStadiumId.value = stadiumId } @@ -215,6 +220,40 @@ class ReviewViewModel @Inject constructor( } } -} - + // presigned URL 요청 + fun requestPresignedUrl(fileExtension: String, memberId: Int) { + viewModelScope.launch { + _getPreSignedUrl.value = UiState.Loading + seatReviewRepository.postReviewImagePresigned(fileExtension, memberId) + .onSuccess { response -> + Timber.e("REQUEST PRESIGNED URL SUCCESS : $response") + _getPreSignedUrl.value = UiState.Success(response) + } + .onFailure { t -> + Timber.e("REQUEST PRESIGNED URL FAILURE : $t") + if (t is HttpException) { + _getPreSignedUrl.value = UiState.Failure(t.code().toString()) + } else { + _getPreSignedUrl.value = UiState.Failure(t.message ?: "Unknown error") + } + } + } + } + // 이미지 업로드 + fun uploadImageToPreSignedUrl(presignedUrl: String, image: ByteArray) { + viewModelScope.launch { + val result = seatReviewRepository.putImagePreSignedUrl(presignedUrl, image) + result.onSuccess { + Timber.d("UPLOAD IMAGE SUCCESS") + }.onFailure { t -> + Timber.e("UPLOAD IMAGE FAILURE : $t") + if (t is HttpException) { + Timber.e("HTTP error code: ${t.code()}") + } else { + Timber.e("General error: ${t.message ?: "Unknown error"}") + } + } + } + } +} From 7afa08adb89c7374eda503770cdda662de15aec6 Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Sat, 20 Jul 2024 01:21:10 +0900 Subject: [PATCH 09/49] =?UTF-8?q?[feat/#33]=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20API=20domain,=20data=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasource/SeatReviewDataSource.kt | 10 +++++---- .../remote/SeatReviewDataSourceImpl.kt | 17 +++++++++----- .../model/request/RequestSeatReviewDto.kt | 6 ----- .../data/remote/SeatReviewService.kt | 12 +++++----- .../repository/SeatReviewRepositoryImpl.kt | 22 ++++++++++--------- .../domain/entity/request/SeatReviewModel.kt | 2 -- .../domain/repository/SeatReviewRepository.kt | 10 +++++---- 7 files changed, 43 insertions(+), 36 deletions(-) diff --git a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt index a0ea79b3..a038d698 100644 --- a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt +++ b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt @@ -24,10 +24,6 @@ interface SeatReviewDataSource { sectionId: Int, ): List - suspend fun postSeatReviewData( - requestSeatReviewDto: RequestSeatReviewDto, - ) - suspend fun postImagePreSignedData( fileExtension: String, memberId: Int, @@ -37,4 +33,10 @@ interface SeatReviewDataSource { presignedUrl: String, image: ByteArray, ) + + suspend fun postSeatReviewData( + memberId: Int, + seatId: Int, + requestSeatReviewDto: RequestSeatReviewDto, + ) } diff --git a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt index 2be96346..9250f71b 100644 --- a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt +++ b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt @@ -40,10 +40,6 @@ class SeatReviewDataSourceImpl @Inject constructor( return seatReviewService.getSeatRange(stadiumId, sectionId) } - override suspend fun postSeatReviewData(requestSeatReviewDto: RequestSeatReviewDto) { - return seatReviewService.postSeatReview(requestSeatReviewDto) - } - override suspend fun postImagePreSignedData( fileExtension: String, memberId: Int, @@ -54,8 +50,19 @@ class SeatReviewDataSourceImpl @Inject constructor( ) } - override suspend fun putReviewImageData(presignedUrl: String, image: ByteArray) { + override suspend fun putReviewImageData( + presignedUrl: String, + image: ByteArray, + ) { val mediaType = "image/*".toMediaTypeOrNull() return seatReviewService.putProfileImage(presignedUrl, image.toRequestBody(mediaType)) } + + override suspend fun postSeatReviewData( + memberId: Int, + seatId: Int, + requestSeatReviewDto: RequestSeatReviewDto + ) { + return seatReviewService.postSeatReview(requestSeatReviewDto) + } } diff --git a/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt b/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt index ec0e02d4..a482443b 100644 --- a/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt +++ b/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt @@ -6,10 +6,6 @@ import kotlinx.serialization.Serializable @Serializable data class RequestSeatReviewDto( - @SerialName("memberId") - val memberId: Int, - @SerialName("seatId") - val seatId: Int, @SerialName("images") val images: List, @SerialName("dateTime") @@ -23,8 +19,6 @@ data class RequestSeatReviewDto( ) fun SeatReviewModel.toSeatReview() = RequestSeatReviewDto( - memberId = memberId, - seatId = seatId, images = images, dateTime = dateTime, good = good, diff --git a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt index bbb8594e..88c4afaf 100644 --- a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt +++ b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt @@ -36,11 +36,6 @@ interface SeatReviewService { @Path("sectionId") sectionId: Int, ): List - @POST("/api/v1/seats/{seatId}/members/{memberId}/reviews") - suspend fun postSeatReview( - @Body requestPostSignupDto: RequestSeatReviewDto, - ) - @POST("/api/v1/members/{memberId}/reviews/images") suspend fun postImagePreSignedUrl( @Body body: RequestPreSignedUrlDto, @@ -52,4 +47,11 @@ interface SeatReviewService { @Url preSignedUrl: String, @Body image: RequestBody, ) + + @POST("/api/v1/seats/{seatId}/members/{memberId}/reviews") + suspend fun postSeatReview( + @Path("memberId") memberId: Int, + @Path("seatId") seatId: Int, + @Body requestPostSignupDto: RequestSeatReviewDto, + ) } diff --git a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt index 18c91ae5..30bf29ed 100644 --- a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt +++ b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt @@ -52,16 +52,6 @@ class SeatReviewRepositoryImpl @Inject constructor( } } - override suspend fun postSeatReview( - seatReviewInfo: SeatReviewModel, - ): Result { - return runCatching { - seatReviewDataSource.postSeatReviewData( - seatReviewInfo.toSeatReview(), - ) - } - } - override suspend fun postReviewImagePresigned( fileExtension: String, memberId: Int, @@ -85,4 +75,16 @@ class SeatReviewRepositoryImpl @Inject constructor( ) } } + + override suspend fun postSeatReview( + memberId: Int, + seatId: Int, + seatReviewInfo: SeatReviewModel + ): Result { + return runCatching { + seatReviewDataSource.postSeatReviewData( + memberId,seatId,seatReviewInfo.toSeatReview() + ) + } + } } diff --git a/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt b/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt index 23aa51f2..2c1b6ef4 100644 --- a/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt +++ b/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt @@ -1,8 +1,6 @@ package com.depromeet.domain.entity.request data class SeatReviewModel( - val memberId: Int, - val seatId: Int, val images: List, val dateTime: String, val good: List, diff --git a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt index 21e40d31..046c65d1 100644 --- a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt +++ b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt @@ -24,10 +24,6 @@ interface SeatReviewRepository { sectionId: Int, ): Result> - suspend fun postSeatReview( - seatReviewInfo: SeatReviewModel, - ): Result - suspend fun postReviewImagePresigned( fileExtension: String, memberId: Int, @@ -37,4 +33,10 @@ interface SeatReviewRepository { presignedUrl: String, image: ByteArray, ): Result + + suspend fun postSeatReview( + memberId: Int, + seatId: Int, + seatReviewInfo: SeatReviewModel, + ): Result } From eac5dbe661f756c00c9664bf4aacf43d74274b1d Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Sat, 20 Jul 2024 01:32:06 +0900 Subject: [PATCH 10/49] =?UTF-8?q?[feat/#33]=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20API=20=EC=84=9C=EB=B2=84=20=ED=86=B5?= =?UTF-8?q?=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../remote/SeatReviewDataSourceImpl.kt | 8 +++-- .../model/request/RequestSeatReviewDto.kt | 2 +- .../repository/SeatReviewRepositoryImpl.kt | 6 ++-- .../presentation/seatReview/ReviewActivity.kt | 2 +- .../seatReview/ReviewViewModel.kt | 32 +++++++++++++++++++ 5 files changed, 44 insertions(+), 6 deletions(-) diff --git a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt index 9250f71b..d56a9b5a 100644 --- a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt +++ b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt @@ -61,8 +61,12 @@ class SeatReviewDataSourceImpl @Inject constructor( override suspend fun postSeatReviewData( memberId: Int, seatId: Int, - requestSeatReviewDto: RequestSeatReviewDto + requestSeatReviewDto: RequestSeatReviewDto, ) { - return seatReviewService.postSeatReview(requestSeatReviewDto) + return seatReviewService.postSeatReview( + memberId, + seatId, + requestSeatReviewDto, + ) } } diff --git a/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt b/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt index a482443b..b2ddb10c 100644 --- a/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt +++ b/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt @@ -15,7 +15,7 @@ data class RequestSeatReviewDto( @SerialName("bad") val bad: List, @SerialName("content") - val content: String, + val content: String?, ) fun SeatReviewModel.toSeatReview() = RequestSeatReviewDto( diff --git a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt index 30bf29ed..663c9554 100644 --- a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt +++ b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt @@ -79,11 +79,13 @@ class SeatReviewRepositoryImpl @Inject constructor( override suspend fun postSeatReview( memberId: Int, seatId: Int, - seatReviewInfo: SeatReviewModel + seatReviewInfo: SeatReviewModel, ): Result { return runCatching { seatReviewDataSource.postSeatReviewData( - memberId,seatId,seatReviewInfo.toSeatReview() + memberId, + seatId, + seatReviewInfo.toSeatReview(), ) } } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index 976bb2f0..662f6def 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -37,7 +37,7 @@ class ReviewActivity : BaseActivity({ ActivityReviewBinding.inflate(it) }) { companion object { - private const val DATE_FORMAT = "yy.MM.dd" + private const val DATE_FORMAT = "yyyy.MM.dd" private const val FRAGMENT_RESULT_KEY = "requestKey" private const val SELECTED_IMAGES = "selected_images" private const val MAX_SELECTED_IMAGES = 3 diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index 4cf4693e..f9133591 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -3,6 +3,7 @@ package com.depromeet.presentation.seatReview import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.depromeet.core.state.UiState +import com.depromeet.domain.entity.request.SeatReviewModel import com.depromeet.domain.entity.response.seatReview.ResponsePresignedUrlModel import com.depromeet.domain.entity.response.seatReview.SeatBlockModel import com.depromeet.domain.entity.response.seatReview.SeatRangeModel @@ -84,6 +85,9 @@ class ReviewViewModel @Inject constructor( MutableStateFlow>(UiState.Loading) val getPreSignedUrl = _getPreSignedUrl.asStateFlow() + private val _postReviewState = MutableStateFlow>(UiState.Empty) + val postReviewState: StateFlow> = _postReviewState.asStateFlow() + fun setSelectedStadiumId(stadiumId: Int) { _selectedStadiumId.value = stadiumId } @@ -151,6 +155,7 @@ class ReviewViewModel @Inject constructor( } } } + fun getSeatBlock(stadiumId: Int, sectionId: Int) { viewModelScope.launch { _seatBlockState.value = UiState.Loading @@ -256,4 +261,31 @@ class ReviewViewModel @Inject constructor( } } } + + fun postSeatReview(memberId: Int) { + viewModelScope.launch { + val seatReviewModel = SeatReviewModel( + images = _selectedImages.value, + dateTime = _selectedDate.value, + good = _selectedGoodReview.value, + bad = _selectedBadReview.value, + content = _detailReviewText.value, + ) + + _postReviewState.value = UiState.Loading + seatReviewRepository.postSeatReview(memberId, _selectedStadiumId.value, seatReviewModel) + .onSuccess { + _postReviewState.value = UiState.Success(Unit) + Timber.d("POST REVIEW SUCCESS") + } + .onFailure { t -> + Timber.e("POST REVIEW FAILURE : $t") + if (t is HttpException) { + _postReviewState.value = UiState.Failure(t.code().toString()) + } else { + _postReviewState.value = UiState.Failure(t.message ?: "Unknown error") + } + } + } + } } From 3a7123aea84602ce0e1fdedc55ae2673731e3593 Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Sun, 21 Jul 2024 02:46:40 +0900 Subject: [PATCH 11/49] =?UTF-8?q?[feat/#33]=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EC=A0=80=EC=9E=A5=20=EB=B0=8F=20=EC=95=BC?= =?UTF-8?q?=EA=B5=AC=EC=9E=A5=20=EC=9D=B4=EB=A6=84=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20dto=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + .../com/depromeet/spot/di/RetrofitModule.kt | 25 ++++++++++++++++--- .../seatReview/ResponseStadiumNameDto.kt | 3 +++ .../response/seatReview/StadiumNameModel.kt | 1 + .../presentation/seatReview/ReviewActivity.kt | 6 ++--- .../seatReview/ReviewViewModel.kt | 15 ++++++++--- .../seatReview/dialog/SelectSeatDialog.kt | 18 +++++++------ 7 files changed, 50 insertions(+), 19 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 41a10445..4f096f05 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -91,6 +91,7 @@ dependencies { } implementation(MaterialDesignDependencies.materialDesign) + implementation("com.google.firebase:firebase-database-ktx:21.0.0") TestDependencies.run { testImplementation(jUnit) diff --git a/app/src/main/java/com/depromeet/spot/di/RetrofitModule.kt b/app/src/main/java/com/depromeet/spot/di/RetrofitModule.kt index e744e389..3b913604 100644 --- a/app/src/main/java/com/depromeet/spot/di/RetrofitModule.kt +++ b/app/src/main/java/com/depromeet/spot/di/RetrofitModule.kt @@ -26,6 +26,8 @@ annotation class WebSvg @InstallIn(SingletonComponent::class) object RetrofitModule { private const val APPLICATION_JSON = "application/json" + private const val ACCESS_TOKEN = + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoiUk9MRV9VU0VSIiwic3ViIjoiMyIsImlhdCI6MTc1MjkzNTM3MCwiZXhwIjoxNzUyOTM1MzcwfQ.fRdMnNz-bDzriVDQh1vJelJ3hLw1pSrzAQLcXKonB4I" @Provides @Singleton @@ -41,15 +43,30 @@ object RetrofitModule { @Provides @Singleton - fun provideHttpLoggingInterceptor(): Interceptor = HttpLoggingInterceptor().apply { + fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY } + @Provides + @Singleton + fun provideAuthInterceptor(): Interceptor { + return Interceptor { chain -> + val request = chain.request().newBuilder() + .addHeader("Authorization", "Bearer $ACCESS_TOKEN") + .build() + chain.proceed(request) + } + } + @Provides @Singleton fun provideOkHttpClient( - loggingInterceptor: Interceptor, - ): OkHttpClient = OkHttpClient.Builder().addInterceptor(loggingInterceptor).build() + loggingInterceptor: HttpLoggingInterceptor, + authInterceptor: Interceptor, + ): OkHttpClient = OkHttpClient.Builder() + .addInterceptor(loggingInterceptor) + .addInterceptor(authInterceptor) + .build() @Provides @Singleton @@ -64,7 +81,7 @@ object RetrofitModule { @Singleton fun provideWebSvgRetrofit( client: OkHttpClient, - factory: Factory + factory: Factory, ): Retrofit = Retrofit.Builder().baseUrl(SVG_BASE_URL).client(client).addConverterFactory(factory).build() } diff --git a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseStadiumNameDto.kt b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseStadiumNameDto.kt index 34dfeef3..d363e0b1 100644 --- a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseStadiumNameDto.kt +++ b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseStadiumNameDto.kt @@ -10,11 +10,14 @@ data class ResponseStadiumNameDto( val id: Int, @SerialName("name") val name: String, + @SerialName("isActive") + val isActive: Boolean, ) { fun toStadiumName(): StadiumNameModel { return StadiumNameModel( id = id, name = name, + isActive = isActive, ) } } diff --git a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/StadiumNameModel.kt b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/StadiumNameModel.kt index b821a521..c58e3769 100644 --- a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/StadiumNameModel.kt +++ b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/StadiumNameModel.kt @@ -3,4 +3,5 @@ package com.depromeet.domain.entity.response.seatReview data class StadiumNameModel( var id: Int, val name: String, + val isActive: Boolean ) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index 662f6def..137008c2 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -133,7 +133,7 @@ class ReviewActivity : BaseActivity({ if (firstStadium != null) { binding.tvStadiumName.text = firstStadium.name viewModel.getStadiumSection(firstStadium.id) - viewModel.setSelectedStadiumId(firstStadium.id) + viewModel.updateSelectedStadiumId(firstStadium.id) } observeReviewViewModel() } @@ -274,10 +274,10 @@ class ReviewActivity : BaseActivity({ val fileExtension = MimeTypeMap.getFileExtensionFromUrl(imageUri.toString()) // presigned URL 요청 - //TODO : MemberID 받기 + // TODO : MemberID 받기 viewModel.requestPresignedUrl(fileExtension, 1) - // presigned URL 응답을 관찰 후 업로드 후 이동 + // presigned URL 응답 관찰 -> 업로드 -> ReviewDoneActivity 이동 viewModel.getPreSignedUrl.asLiveData().observe(this) { state -> when (state) { is UiState.Success -> { diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index f9133591..1c9452d0 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -72,6 +72,9 @@ class ReviewViewModel @Inject constructor( private val _selectedStadiumId = MutableStateFlow(0) val selectedStadiumId: StateFlow = _selectedStadiumId.asStateFlow() + private val _selectedSectionId = MutableStateFlow(0) + val selectedSectionId: StateFlow = _selectedSectionId.asStateFlow() + private val _stadiumSectionState = MutableStateFlow>(UiState.Empty) val stadiumSectionState: StateFlow> = _stadiumSectionState @@ -88,10 +91,14 @@ class ReviewViewModel @Inject constructor( private val _postReviewState = MutableStateFlow>(UiState.Empty) val postReviewState: StateFlow> = _postReviewState.asStateFlow() - fun setSelectedStadiumId(stadiumId: Int) { + fun updateSelectedStadiumId(stadiumId: Int) { _selectedStadiumId.value = stadiumId } + fun updateSelectedSectionId(sectionId: Int) { + _selectedSectionId.value = sectionId + } + fun updateSelectedDate(date: String) { _selectedDate.value = date } @@ -148,6 +155,7 @@ class ReviewViewModel @Inject constructor( Timber.e("GET NAME FAILURE : ${t.message}", t) if (t is HttpException) { Timber.e("HTTP error code: ${t.code()}") + Timber.e("HTTP error response: ${t.response()?.errorBody()?.string()}") _stadiumNameState.value = UiState.Failure(t.code().toString()) } else { Timber.e("General error: ${t.message ?: "Unknown error"}") @@ -161,7 +169,7 @@ class ReviewViewModel @Inject constructor( _seatBlockState.value = UiState.Loading seatReviewRepository.getSeatBlock(stadiumId, sectionId) .onSuccess { blocks -> - Timber.e("GET BLOCK FAILURE : $blocks") + Timber.d("GET BLOCK SUCCESS : $blocks") if (blocks.isEmpty()) { _seatBlockState.value = UiState.Empty } else { @@ -212,8 +220,7 @@ class ReviewViewModel @Inject constructor( Timber.d("GET RANGE SUCCESS : $range") if (range.isEmpty()) { _seatRangeState.value = UiState.Empty - } else { - _seatRangeState.value = UiState.Success(range) + return@launch } } .onFailure { t -> diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index 1468ba58..037d5bbf 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -107,14 +107,12 @@ class SelectSeatDialog : BindingBottomSheetDialog { state.data.forEach { range -> updateIsExistedColumnUI(range.rowInfo) - updateColumnNUmberUI(range) + updateColumnNumberUI(range) } } - is UiState.Failure -> { toast("오류가 발생했습니다") } - is UiState.Loading -> {} is UiState.Empty -> {} else -> {} @@ -122,26 +120,30 @@ class SelectSeatDialog : BindingBottomSheetDialog matchingRowInfo.maxSeatNum)) { + if (selectedNumber < matchingRowInfo.minSeatNum.toString() || selectedNumber > matchingRowInfo.maxSeatNum.toString()) { updateNumberWarning("존재하지 않는 번이에요") } else { updateBackWarnings() } } } else { - if (selectedNumber == null || range.rowInfo.none { it.minSeatNum <= selectedNumber && it.maxSeatNum >= selectedNumber }) { + if (range.rowInfo.none { it.minSeatNum.toString() <= selectedNumber && it.maxSeatNum.toString() >= selectedNumber }) { updateBothWarnings("존재하지 않는 열과 번이에요") } else { updateBackWarnings() From e71520e63e64f2ac9f398265df23108165d050d6 Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Sun, 21 Jul 2024 14:53:37 +0900 Subject: [PATCH 12/49] =?UTF-8?q?[feat/#33]=20=EC=97=B4=EB=B2=88=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/seatReview/ReviewActivity.kt | 2 +- .../seatReview/ReviewViewModel.kt | 60 ++--- .../seatReview/dialog/SelectSeatDialog.kt | 215 +++++++++++------- 3 files changed, 165 insertions(+), 112 deletions(-) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index 137008c2..740bb6ae 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -275,7 +275,7 @@ class ReviewActivity : BaseActivity({ // presigned URL 요청 // TODO : MemberID 받기 - viewModel.requestPresignedUrl(fileExtension, 1) + viewModel.requestPreSignedUrl(fileExtension, 1) // presigned URL 응답 관찰 -> 업로드 -> ReviewDoneActivity 이동 viewModel.getPreSignedUrl.asLiveData().observe(this) { state -> diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index 1c9452d0..8835c8ad 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -1,5 +1,6 @@ package com.depromeet.presentation.seatReview +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.depromeet.core.state.UiState @@ -164,27 +165,6 @@ class ReviewViewModel @Inject constructor( } } - fun getSeatBlock(stadiumId: Int, sectionId: Int) { - viewModelScope.launch { - _seatBlockState.value = UiState.Loading - seatReviewRepository.getSeatBlock(stadiumId, sectionId) - .onSuccess { blocks -> - Timber.d("GET BLOCK SUCCESS : $blocks") - if (blocks.isEmpty()) { - _seatBlockState.value = UiState.Empty - } else { - _seatBlockState.value = UiState.Success(blocks) - } - } - .onFailure { t -> - if (t is HttpException) { - Timber.e("GET BLOCK FAILURE : $t") - _seatBlockState.value = UiState.Failure(t.code().toString()) - } - } - } - } - fun getStadiumSection(stadiumId: Int) { viewModelScope.launch { _stadiumSectionState.value = UiState.Loading @@ -210,6 +190,27 @@ class ReviewViewModel @Inject constructor( } } + fun getSeatBlock(stadiumId: Int, sectionId: Int) { + viewModelScope.launch { + _seatBlockState.value = UiState.Loading + seatReviewRepository.getSeatBlock(stadiumId, sectionId) + .onSuccess { blocks -> + Timber.d("GET BLOCK SUCCESS : $blocks") + if (blocks.isEmpty()) { + _seatBlockState.value = UiState.Empty + } else { + _seatBlockState.value = UiState.Success(blocks) + } + } + .onFailure { t -> + if (t is HttpException) { + Timber.e("GET BLOCK FAILURE : $t") + _seatBlockState.value = UiState.Failure(t.code().toString()) + } + } + } + } + fun getSeatRange(stadiumId: Int, sectionId: Int) { viewModelScope.launch { _seatRangeState.value = UiState.Loading @@ -220,20 +221,21 @@ class ReviewViewModel @Inject constructor( Timber.d("GET RANGE SUCCESS : $range") if (range.isEmpty()) { _seatRangeState.value = UiState.Empty - return@launch + } else { + _seatRangeState.value = UiState.Success(range) } - } - .onFailure { t -> - if (t is HttpException) { - Timber.e("GET RANGE FAILURE : $t") - _seatRangeState.value = UiState.Failure(t.code().toString()) - } + }.onFailure { t -> + if (t is HttpException) { + Timber.e("GET RANGE FAILURE : $t") + _seatRangeState.value = UiState.Failure(t.code().toString()) } + } } } + // presigned URL 요청 - fun requestPresignedUrl(fileExtension: String, memberId: Int) { + fun requestPreSignedUrl(fileExtension: String, memberId: Int) { viewModelScope.launch { _getPreSignedUrl.value = UiState.Loading seatReviewRepository.postReviewImagePresigned(fileExtension, memberId) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index 037d5bbf..bb159073 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -7,7 +7,6 @@ import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE import android.view.ViewTreeObserver -import android.widget.Adapter import android.widget.AdapterView import android.widget.ArrayAdapter import androidx.core.content.ContextCompat @@ -45,22 +44,15 @@ class SelectSeatDialog : BindingBottomSheetDialog when (state) { @@ -83,7 +75,7 @@ class SelectSeatDialog : BindingBottomSheetDialog when (state) { is UiState.Success -> { @@ -110,43 +102,133 @@ class SelectSeatDialog : BindingBottomSheetDialog { toast("오류가 발생했습니다") } - is UiState.Loading -> {} - is UiState.Empty -> {} + + is UiState.Loading -> { + } + + is UiState.Empty -> { + } + else -> {} } } } - // TODO : 에러 처리 다시 + private fun observeReviewViewModel() { + viewModel.selectedSeatZone.asLiveData().observe(this) { adapter.notifyDataSetChanged() } + viewModel.selectedBlock.asLiveData().observe(this) { block -> + updateCompleteBtnState() + } + viewModel.selectedColumn.asLiveData().observe(this) { column -> + updateCompleteBtnState() + } - private fun updateColumnNumberUI(range: SeatRangeModel) { - val selectedBlock = viewModel.selectedBlock.value - val selectedColumn = viewModel.selectedColumn.value - val selectedNumber = viewModel.selectedNumber.value + viewModel.selectedNumber.asLiveData().observe(this) { number -> + updateCompleteBtnState() + } + } - if (selectedColumn.isNullOrEmpty() || selectedNumber.isNullOrEmpty()) { - return + private fun setupEditTextListeners() { + binding.etColumn.addTextChangedListener { text: Editable? -> + val newColumn = text.toString() + viewModel.setSelectedColumn(newColumn) + viewModel.seatRangeState.value?.let { state -> + if (state is UiState.Success) { + state.data.forEach { range -> + updateColumnNumberUI(range) + } + } + } } - if (range.code == selectedBlock) { - val matchingRowInfo = range.rowInfo.find { it.number.toString() == selectedColumn } - if (matchingRowInfo == null) { - updateColumnWarning("존재하지 않는 열이에요") - } else { - if (selectedNumber < matchingRowInfo.minSeatNum.toString() || selectedNumber > matchingRowInfo.maxSeatNum.toString()) { - updateNumberWarning("존재하지 않는 번이에요") - } else { - updateBackWarnings() + binding.etNumber.addTextChangedListener { text: Editable? -> + val newNumber = text.toString() + viewModel.setSelectedNumber(newNumber) + viewModel.seatRangeState.value?.let { state -> + if (state is UiState.Success) { + state.data.forEach { range -> + updateColumnNumberUI(range) + } } } - } else { - if (range.rowInfo.none { it.minSeatNum.toString() <= selectedNumber && it.maxSeatNum.toString() >= selectedNumber }) { - updateBothWarnings("존재하지 않는 열과 번이에요") + } + } + + // TODO : 추후 코드 개선 예정 (ㅠㅠ) + private fun updateColumnNumberUI(range: SeatRangeModel) { + if (viewModel.selectedColumn.value.isEmpty() && viewModel.selectedNumber.value.isEmpty()) { + binding.etColumn.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) + binding.etNumber.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) + binding.tvNoneColumnWarning.visibility = GONE + } + if (range.code == viewModel.selectedBlock.value) { + val matchingRowInfo = + range.rowInfo.find { it.number.toString() == viewModel.selectedColumn.value } + if (matchingRowInfo == null && viewModel.selectedColumn.value.isNotEmpty()) { + with(binding) { + etColumn.setBackgroundResource(R.drawable.rect_gray50_fill_red1_line_12) + etNumber.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) + tvNoneColumnWarning.text = "존재하지 않는 열이에요" + tvNoneColumnWarning.visibility = VISIBLE + binding.tvCompleteBtn.setBackgroundResource(R.drawable.rect_gray200_fill_6) + binding.tvCompleteBtn.setTextColor( + ContextCompat.getColor( + requireContext(), + android.R.color.white, + ), + ) + } + if (viewModel.selectedNumber.value.isNotEmpty()) { + // 열과 번호 모두 오류인 경우 + with(binding) { + etColumn.setBackgroundResource(R.drawable.rect_gray50_fill_red1_line_12) + etNumber.setBackgroundResource(R.drawable.rect_gray50_fill_red1_line_12) + tvNoneColumnWarning.text = "존재하지 않는 열과 번이에요" + binding.tvCompleteBtn.setBackgroundResource(R.drawable.rect_gray200_fill_6) + binding.tvCompleteBtn.setTextColor( + ContextCompat.getColor( + requireContext(), + android.R.color.white, + ), + ) + } + } } else { - updateBackWarnings() + if (matchingRowInfo != null && viewModel.selectedNumber.value.isNotEmpty()) { + if (viewModel.selectedNumber.value < matchingRowInfo.minSeatNum.toString() || viewModel.selectedNumber.value > matchingRowInfo.maxSeatNum.toString()) { + with(binding) { + etColumn.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) + etNumber.setBackgroundResource(R.drawable.rect_gray50_fill_red1_line_12) + tvNoneColumnWarning.text = "존재하지 않는 번이에요" + tvNoneColumnWarning.visibility = VISIBLE + binding.tvCompleteBtn.setBackgroundResource(R.drawable.rect_gray200_fill_6) + binding.tvCompleteBtn.setTextColor( + ContextCompat.getColor( + requireContext(), + android.R.color.white, + ), + ) + } + } else { + with(binding) { + etColumn.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) + etNumber.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) + tvNoneColumnWarning.visibility = GONE + binding.tvCompleteBtn.isEnabled + binding.tvCompleteBtn.setBackgroundResource(R.drawable.rect_gray900_fill_6) + binding.tvCompleteBtn.setTextColor( + ContextCompat.getColor( + requireContext(), + android.R.color.white, + ), + ) + } + } + } } } } @@ -170,14 +252,19 @@ class SelectSeatDialog : BindingBottomSheetDialog) { - val blockCodes = blockItems.map { it.code } + val blockCodes = mutableListOf().apply { + add("") + addAll(blockItems.map { it.code }) + } + val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, blockCodes) adapter.setDropDownViewResource(R.layout.custom_spinner_dropdown_item) with(binding.spinnerBlock) { this.adapter = adapter - this.setSelection(Adapter.NO_SELECTION, false) + this.setSelection(0, false) + this.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected( parent: AdapterView<*>?, @@ -185,8 +272,16 @@ class SelectSeatDialog : BindingBottomSheetDialog?) { @@ -202,22 +297,11 @@ class SelectSeatDialog : BindingBottomSheetDialog - viewModel.setSelectedColumn(text.toString()) - } - - binding.etNumber.addTextChangedListener { text: Editable? -> - viewModel.setSelectedNumber(text.toString()) - } - } - private fun setupTransactionSelectSeat() { with(binding) { layoutSeatAgain.setOnSingleClickListener { @@ -277,37 +361,4 @@ class SelectSeatDialog : BindingBottomSheetDialog Date: Sun, 21 Jul 2024 15:38:01 +0900 Subject: [PATCH 13/49] =?UTF-8?q?[feat/#33]=20preSignedUrl=20URL=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20API=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/seatReview/ReviewActivity.kt | 72 +++++++++++++------ .../seatReview/ReviewViewModel.kt | 13 ++-- .../seatReview/dialog/SelectSeatDialog.kt | 36 +--------- 3 files changed, 59 insertions(+), 62 deletions(-) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index 740bb6ae..2d229427 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -1,5 +1,6 @@ package com.depromeet.presentation.seatReview +import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle @@ -27,7 +28,8 @@ import com.depromeet.presentation.seatReview.dialog.ImageUploadDialog import com.depromeet.presentation.seatReview.dialog.ReviewMySeatDialog import com.depromeet.presentation.seatReview.dialog.SelectSeatDialog import dagger.hilt.android.AndroidEntryPoint -import java.io.File +import java.io.FileNotFoundException +import java.io.InputStream import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale @@ -232,7 +234,6 @@ class ReviewActivity : BaseActivity({ } } for (index in selectedImageUris.size until selectedImage.size) { - val image = selectedImage[index] val layout = selectedImageLayout[index] layout.isVisible = false removeButtons[index].isVisible = false @@ -266,35 +267,60 @@ class ReviewActivity : BaseActivity({ } } + private fun getFileExtension(context: Context, uri: Uri): String { + val mimeType = context.contentResolver.getType(uri) + return mimeType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(it) } ?: "" + } + + private fun getFileInputStream(context: Context, uri: Uri): InputStream? { + return try { + context.contentResolver.openInputStream(uri) + } catch (e: FileNotFoundException) { + e.printStackTrace() + null + } + } + + private fun readImageData(context: Context, uri: Uri): ByteArray? { + val inputStream = getFileInputStream(context, uri) + return inputStream?.use { it.readBytes() } + } + private fun navigateToReviewDoneActivity() { binding.tvUploadBtn.setOnSingleClickListener { selectedImageUris.forEach { imageUriString -> val imageUri = Uri.parse(imageUriString) - val imageFile = File(imageUri.path!!) - val fileExtension = MimeTypeMap.getFileExtensionFromUrl(imageUri.toString()) + val fileExtension = getFileExtension(this, imageUri) + val imageData = readImageData(this, imageUri) + if (imageData != null) { + // TODO : MemberID 수정 + viewModel.requestPreSignedUrl(fileExtension, 1) + viewModel.getPreSignedUrl.asLiveData().observe(this) { state -> + when (state) { + is UiState.Success -> { + val presignedUrl = state.data.presignedUrl + viewModel.uploadImageToPreSignedUrl(presignedUrl, imageData) + Intent(this, ReviewDoneActivity::class.java).apply { + startActivity( + this, + ) + } + } - // presigned URL 요청 - // TODO : MemberID 받기 - viewModel.requestPreSignedUrl(fileExtension, 1) + is UiState.Failure -> { + Toast.makeText( + this, + "Presigned URL 요청 실패: $state", + Toast.LENGTH_SHORT, + ) + .show() + } - // presigned URL 응답 관찰 -> 업로드 -> ReviewDoneActivity 이동 - viewModel.getPreSignedUrl.asLiveData().observe(this) { state -> - when (state) { - is UiState.Success -> { - val presignedUrl = state.data.presignedUrl - val imageData = imageFile.readBytes() - // 이미지 업로드 - viewModel.uploadImageToPreSignedUrl(presignedUrl, imageData) - Intent(this, ReviewDoneActivity::class.java).apply { startActivity(this) } + else -> {} } - - is UiState.Failure -> { - Toast.makeText(this, "Presigned URL 요청 실패: $state", Toast.LENGTH_SHORT) - .show() - } - - else -> {} } + } else { + Toast.makeText(this, "파일을 읽을 수 없습니다.", Toast.LENGTH_SHORT).show() } } } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index 8835c8ad..baa03c3e 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -1,6 +1,5 @@ package com.depromeet.presentation.seatReview -import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.depromeet.core.state.UiState @@ -233,23 +232,25 @@ class ReviewViewModel @Inject constructor( } } - // presigned URL 요청 fun requestPreSignedUrl(fileExtension: String, memberId: Int) { viewModelScope.launch { _getPreSignedUrl.value = UiState.Loading seatReviewRepository.postReviewImagePresigned(fileExtension, memberId) .onSuccess { response -> - Timber.e("REQUEST PRESIGNED URL SUCCESS : $response") + Timber.d("REQUEST PRESIGNED URL SUCCESS : $response") _getPreSignedUrl.value = UiState.Success(response) } .onFailure { t -> Timber.e("REQUEST PRESIGNED URL FAILURE : $t") - if (t is HttpException) { - _getPreSignedUrl.value = UiState.Failure(t.code().toString()) + val errorMessage = if (t is HttpException) { + val errorBody = t.response()?.errorBody()?.string() + "HTTP Error ${t.code()}: ${errorBody ?: "Unknown error"}" } else { - _getPreSignedUrl.value = UiState.Failure(t.message ?: "Unknown error") + t.message ?: "Unknown error" } + + _getPreSignedUrl.value = UiState.Failure(errorMessage) } } } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index bb159073..5d369615 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -44,7 +44,6 @@ class SelectSeatDialog : BindingBottomSheetDialog - updateCompleteBtnState() - } - viewModel.selectedColumn.asLiveData().observe(this) { column -> - updateCompleteBtnState() - } - - viewModel.selectedNumber.asLiveData().observe(this) { number -> - updateCompleteBtnState() - } - } - private fun setupEditTextListeners() { binding.etColumn.addTextChangedListener { text: Editable? -> val newColumn = text.toString() @@ -218,7 +203,7 @@ class SelectSeatDialog : BindingBottomSheetDialog Date: Sun, 21 Jul 2024 16:37:23 +0900 Subject: [PATCH 14/49] =?UTF-8?q?[fix/#33]=20=EC=A7=81=EA=B4=80=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0=20=EB=93=B1=EB=A1=9D=20API=20request=20membe?= =?UTF-8?q?rId=20=ED=95=84=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasource/SeatReviewDataSource.kt | 1 - .../datasource/remote/SeatReviewDataSourceImpl.kt | 2 -- .../depromeet/data/remote/SeatReviewService.kt | 1 - .../domain/repository/SeatReviewRepository.kt | 1 - .../presentation/seatReview/ReviewActivity.kt | 11 +++-------- .../presentation/seatReview/ReviewViewModel.kt | 15 +++++++++++---- 6 files changed, 14 insertions(+), 17 deletions(-) diff --git a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt index a038d698..6d936614 100644 --- a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt +++ b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt @@ -35,7 +35,6 @@ interface SeatReviewDataSource { ) suspend fun postSeatReviewData( - memberId: Int, seatId: Int, requestSeatReviewDto: RequestSeatReviewDto, ) diff --git a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt index d56a9b5a..42ed1f70 100644 --- a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt +++ b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt @@ -59,12 +59,10 @@ class SeatReviewDataSourceImpl @Inject constructor( } override suspend fun postSeatReviewData( - memberId: Int, seatId: Int, requestSeatReviewDto: RequestSeatReviewDto, ) { return seatReviewService.postSeatReview( - memberId, seatId, requestSeatReviewDto, ) diff --git a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt index 88c4afaf..b9d3ea79 100644 --- a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt +++ b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt @@ -50,7 +50,6 @@ interface SeatReviewService { @POST("/api/v1/seats/{seatId}/members/{memberId}/reviews") suspend fun postSeatReview( - @Path("memberId") memberId: Int, @Path("seatId") seatId: Int, @Body requestPostSignupDto: RequestSeatReviewDto, ) diff --git a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt index 046c65d1..c1fa49c7 100644 --- a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt +++ b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt @@ -35,7 +35,6 @@ interface SeatReviewRepository { ): Result suspend fun postSeatReview( - memberId: Int, seatId: Int, seatReviewInfo: SeatReviewModel, ): Result diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index 2d229427..2c3f220a 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -10,7 +10,6 @@ import android.view.View.VISIBLE import android.webkit.MimeTypeMap import android.widget.FrameLayout import android.widget.ImageView -import android.widget.Toast import androidx.activity.viewModels import androidx.core.content.ContextCompat import androidx.core.view.isVisible @@ -294,6 +293,7 @@ class ReviewActivity : BaseActivity({ val imageData = readImageData(this, imageUri) if (imageData != null) { // TODO : MemberID 수정 + // TODO : postSeatReview() 호출 -> 이미지 업로드 완료 viewModel.requestPreSignedUrl(fileExtension, 1) viewModel.getPreSignedUrl.asLiveData().observe(this) { state -> when (state) { @@ -308,19 +308,14 @@ class ReviewActivity : BaseActivity({ } is UiState.Failure -> { - Toast.makeText( - this, - "Presigned URL 요청 실패: $state", - Toast.LENGTH_SHORT, - ) - .show() + toast("Presigned URL 요청 실패: $state") } else -> {} } } } else { - Toast.makeText(this, "파일을 읽을 수 없습니다.", Toast.LENGTH_SHORT).show() + toast("파일을 읽을 수 없습니다.") } } } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index baa03c3e..1d5d7832 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -11,6 +11,7 @@ import com.depromeet.domain.entity.response.seatReview.StadiumNameModel import com.depromeet.domain.entity.response.seatReview.StadiumSectionModel import com.depromeet.domain.repository.SeatReviewRepository import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -256,11 +257,16 @@ class ReviewViewModel @Inject constructor( } // 이미지 업로드 - fun uploadImageToPreSignedUrl(presignedUrl: String, image: ByteArray) { + fun uploadImageToPreSignedUrl( + presignedUrl: String, + image: ByteArray, + ): CompletableDeferred { + val deferred = CompletableDeferred() viewModelScope.launch { val result = seatReviewRepository.putImagePreSignedUrl(presignedUrl, image) result.onSuccess { Timber.d("UPLOAD IMAGE SUCCESS") + deferred.complete(true) }.onFailure { t -> Timber.e("UPLOAD IMAGE FAILURE : $t") if (t is HttpException) { @@ -268,11 +274,13 @@ class ReviewViewModel @Inject constructor( } else { Timber.e("General error: ${t.message ?: "Unknown error"}") } + deferred.complete(false) } } + return deferred } - fun postSeatReview(memberId: Int) { + fun postSeatReview() { viewModelScope.launch { val seatReviewModel = SeatReviewModel( images = _selectedImages.value, @@ -281,9 +289,8 @@ class ReviewViewModel @Inject constructor( bad = _selectedBadReview.value, content = _detailReviewText.value, ) - _postReviewState.value = UiState.Loading - seatReviewRepository.postSeatReview(memberId, _selectedStadiumId.value, seatReviewModel) + seatReviewRepository.postSeatReview(_selectedStadiumId.value, seatReviewModel) .onSuccess { _postReviewState.value = UiState.Success(Unit) Timber.d("POST REVIEW SUCCESS") From 6a78632067afc7ef0b3a426080995423d479ac4f Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Sun, 21 Jul 2024 20:21:16 +0900 Subject: [PATCH 15/49] =?UTF-8?q?[fix/#33]=20=EC=97=B4/=EB=B2=88=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=B2=98=EB=A6=AC=20dto=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../seatReview/ResponseSeatRangeDto.kt | 9 +-- .../repository/SeatReviewRepositoryImpl.kt | 2 - .../response/seatReview/SeatRangeModel.kt | 3 +- .../seatReview/dialog/SelectSeatDialog.kt | 68 ++++++++++--------- 4 files changed, 39 insertions(+), 43 deletions(-) diff --git a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatRangeDto.kt b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatRangeDto.kt index 83e6c23a..07fabd11 100644 --- a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatRangeDto.kt +++ b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatRangeDto.kt @@ -19,17 +19,14 @@ data class ResponseSeatRangeDto( val id: Int, @SerialName("number") val number: Int, - @SerialName("minSeatNum") - val minSeatNum: Int, - @SerialName("maxSeatNum") - val maxSeatNum: Int, + @SerialName("seatNumList") + val seatNumList: List, ) { fun toRowInfo(): SeatRangeModel.RowInfo { return SeatRangeModel.RowInfo( id = id, number = number, - minSeatNum = minSeatNum, - maxSeatNum = maxSeatNum, + seatNumList = seatNumList, ) } } diff --git a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt index 663c9554..63cc9dca 100644 --- a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt +++ b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt @@ -77,13 +77,11 @@ class SeatReviewRepositoryImpl @Inject constructor( } override suspend fun postSeatReview( - memberId: Int, seatId: Int, seatReviewInfo: SeatReviewModel, ): Result { return runCatching { seatReviewDataSource.postSeatReviewData( - memberId, seatId, seatReviewInfo.toSeatReview(), ) diff --git a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatRangeModel.kt b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatRangeModel.kt index 15faa4af..92c47270 100644 --- a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatRangeModel.kt +++ b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatRangeModel.kt @@ -8,7 +8,6 @@ data class SeatRangeModel( data class RowInfo( val id: Int, val number: Int, - val minSeatNum: Int, - val maxSeatNum: Int, + val seatNumList: List, ) } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index 5d369615..40ae8788 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -2,6 +2,7 @@ package com.depromeet.presentation.seatReview.dialog import android.os.Bundle import android.text.Editable +import android.util.Log import android.view.View import android.view.View.GONE import android.view.View.INVISIBLE @@ -143,7 +144,7 @@ class SelectSeatDialog : BindingBottomSheetDialog matchingRowInfo.maxSeatNum.toString()) { - with(binding) { - etColumn.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) - etNumber.setBackgroundResource(R.drawable.rect_gray50_fill_red1_line_12) - tvNoneColumnWarning.text = "존재하지 않는 번이에요" - tvNoneColumnWarning.visibility = VISIBLE - binding.tvCompleteBtn.setBackgroundResource(R.drawable.rect_gray200_fill_6) - binding.tvCompleteBtn.setTextColor( - ContextCompat.getColor( - requireContext(), - android.R.color.white, - ), - ) - } - } else { - with(binding) { - etColumn.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) - etNumber.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) - tvNoneColumnWarning.visibility = GONE - binding.tvCompleteBtn.isEnabled = true - binding.tvCompleteBtn.setBackgroundResource(R.drawable.rect_gray900_fill_6) - binding.tvCompleteBtn.setTextColor( - ContextCompat.getColor( - requireContext(), - android.R.color.white, - ), - ) - } + } else if (matchingRowInfo != null && viewModel.selectedNumber.value.isNotEmpty() && viewModel.selectedNumber.value.isNotBlank()) { + if (!matchingRowInfo.seatNumList.contains(viewModel.selectedNumber.value.toInt())) { + with(binding) { + etColumn.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) + etNumber.setBackgroundResource(R.drawable.rect_gray50_fill_red1_line_12) + tvNoneColumnWarning.text = "존재하지 않는 번이에요" + tvNoneColumnWarning.visibility = VISIBLE + binding.tvCompleteBtn.setBackgroundResource(R.drawable.rect_gray200_fill_6) + binding.tvCompleteBtn.setTextColor( + ContextCompat.getColor( + requireContext(), + android.R.color.white, + ), + ) + binding.tvCompleteBtn.isEnabled = false + } + } else { + with(binding) { + etColumn.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) + etNumber.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) + tvNoneColumnWarning.visibility = GONE + binding.tvCompleteBtn.isEnabled = true + binding.tvCompleteBtn.setBackgroundResource(R.drawable.rect_gray900_fill_6) + binding.tvCompleteBtn.setTextColor( + ContextCompat.getColor( + requireContext(), + android.R.color.white, + ), + ) } } } @@ -331,4 +333,4 @@ class SelectSeatDialog : BindingBottomSheetDialog Date: Sun, 21 Jul 2024 20:27:26 +0900 Subject: [PATCH 16/49] =?UTF-8?q?[fix/#33]=20=EC=8B=9C=EC=95=BC=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0=20=EA=B0=9C=EC=88=98=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../seatReview/dialog/ReviewMySeatDialog.kt | 24 ++++++++++++++----- .../seatReview/dialog/SelectSeatDialog.kt | 1 - 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/ReviewMySeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/ReviewMySeatDialog.kt index 8c4a3228..258d041c 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/ReviewMySeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/ReviewMySeatDialog.kt @@ -58,10 +58,16 @@ class ReviewMySeatDialog : BindingBottomSheetDialog Date: Sun, 21 Jul 2024 20:31:42 +0900 Subject: [PATCH 17/49] =?UTF-8?q?[fix/#33]=20=EC=A2=8C=EC=84=9D=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=20=EC=97=B4,=20=EB=B2=88=20maxlength=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/res/layout/fragment_select_seat_bottom_sheet.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/presentation/src/main/res/layout/fragment_select_seat_bottom_sheet.xml b/presentation/src/main/res/layout/fragment_select_seat_bottom_sheet.xml index 17fe1796..54285382 100644 --- a/presentation/src/main/res/layout/fragment_select_seat_bottom_sheet.xml +++ b/presentation/src/main/res/layout/fragment_select_seat_bottom_sheet.xml @@ -237,6 +237,7 @@ android:paddingVertical="13dp" android:paddingStart="16dp" android:hint="ex)12" + android:maxLength="4" android:paddingEnd="64dp" android:textColor="#121212" app:layout_constraintTop_toTopOf="parent" @@ -263,6 +264,7 @@ android:layout_height="wrap_content" android:background="@drawable/rect_gray50_fill_gray200_line_12" android:hint="ex)12" + android:maxLength="4" android:layout_marginEnd="4dp" android:paddingVertical="13dp" android:paddingStart="16dp" From 0e9348838bf13ae4faf952f82b6c4603f3430a47 Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Sun, 21 Jul 2024 21:22:30 +0900 Subject: [PATCH 18/49] =?UTF-8?q?[fix/#33]=20=EC=9E=84=EC=8B=9C=20token=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=EC=85=89=ED=84=B0=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/depromeet/spot/di/RetrofitModule.kt | 23 +++---------------- .../presentation/seatReview/ReviewActivity.kt | 6 ++--- .../src/main/res/layout/activity_review.xml | 2 +- 3 files changed, 7 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/depromeet/spot/di/RetrofitModule.kt b/app/src/main/java/com/depromeet/spot/di/RetrofitModule.kt index 3b913604..8eba5e68 100644 --- a/app/src/main/java/com/depromeet/spot/di/RetrofitModule.kt +++ b/app/src/main/java/com/depromeet/spot/di/RetrofitModule.kt @@ -26,8 +26,6 @@ annotation class WebSvg @InstallIn(SingletonComponent::class) object RetrofitModule { private const val APPLICATION_JSON = "application/json" - private const val ACCESS_TOKEN = - "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoiUk9MRV9VU0VSIiwic3ViIjoiMyIsImlhdCI6MTc1MjkzNTM3MCwiZXhwIjoxNzUyOTM1MzcwfQ.fRdMnNz-bDzriVDQh1vJelJ3hLw1pSrzAQLcXKonB4I" @Provides @Singleton @@ -43,30 +41,15 @@ object RetrofitModule { @Provides @Singleton - fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor = HttpLoggingInterceptor().apply { + fun provideHttpLoggingInterceptor(): Interceptor = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY } - @Provides - @Singleton - fun provideAuthInterceptor(): Interceptor { - return Interceptor { chain -> - val request = chain.request().newBuilder() - .addHeader("Authorization", "Bearer $ACCESS_TOKEN") - .build() - chain.proceed(request) - } - } - @Provides @Singleton fun provideOkHttpClient( - loggingInterceptor: HttpLoggingInterceptor, - authInterceptor: Interceptor, - ): OkHttpClient = OkHttpClient.Builder() - .addInterceptor(loggingInterceptor) - .addInterceptor(authInterceptor) - .build() + loggingInterceptor: Interceptor, + ): OkHttpClient = OkHttpClient.Builder().addInterceptor(loggingInterceptor).build() @Provides @Singleton diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index 2c3f220a..bc4beade 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -292,14 +292,14 @@ class ReviewActivity : BaseActivity({ val fileExtension = getFileExtension(this, imageUri) val imageData = readImageData(this, imageUri) if (imageData != null) { - // TODO : MemberID 수정 - // TODO : postSeatReview() 호출 -> 이미지 업로드 완료 + // TODO : viewModel.uploadImageToPreSignedUrl 사진 업로드 서버 통신 + // TODO : postSeatReview() 시야 등록 후기 request -> ReviewDoneActivity 이동 viewModel.requestPreSignedUrl(fileExtension, 1) viewModel.getPreSignedUrl.asLiveData().observe(this) { state -> when (state) { is UiState.Success -> { val presignedUrl = state.data.presignedUrl - viewModel.uploadImageToPreSignedUrl(presignedUrl, imageData) + // viewModel.uploadImageToPreSignedUrl(presignedUrl, imageData) Intent(this, ReviewDoneActivity::class.java).apply { startActivity( this, diff --git a/presentation/src/main/res/layout/activity_review.xml b/presentation/src/main/res/layout/activity_review.xml index c450e073..2417d54a 100644 --- a/presentation/src/main/res/layout/activity_review.xml +++ b/presentation/src/main/res/layout/activity_review.xml @@ -15,7 +15,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> - Date: Sun, 21 Jul 2024 21:43:52 +0900 Subject: [PATCH 19/49] [fix/#33] manifest exported true -> false --- app/src/main/AndroidManifest.xml | 4 ++-- "\353\254\264\354\240\234" | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2e3b7003..80829c2a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -73,12 +73,12 @@ android:screenOrientation="portrait" /> Date: Mon, 22 Jul 2024 00:25:28 +0900 Subject: [PATCH 20/49] =?UTF-8?q?[fix/#33]=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20dependency=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 - buildSrc/build/kotlin/buildSrcjar-classes.txt | 2 +- .../compileKotlin/cacheable/last-build.bin | Bin 18 -> 18 bytes .../local-state/build-history.bin | Bin 31 -> 31 bytes buildSrc/build/libs/buildSrc.jar | Bin 12253 -> 12391 bytes 5 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a7fe5641..53e91fb7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -94,7 +94,6 @@ dependencies { } implementation(MaterialDesignDependencies.materialDesign) - implementation("com.google.firebase:firebase-database-ktx:21.0.0") TestDependencies.run { testImplementation(jUnit) diff --git a/buildSrc/build/kotlin/buildSrcjar-classes.txt b/buildSrc/build/kotlin/buildSrcjar-classes.txt index c98318a7..d7dd3381 100644 --- a/buildSrc/build/kotlin/buildSrcjar-classes.txt +++ b/buildSrc/build/kotlin/buildSrcjar-classes.txt @@ -1 +1 @@ -/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/AndroidXDependencies.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/ClassPathPlugins.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/ComposeDependency.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/Constants.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/DependenciesKt.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/FirebaseDependencies.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/KaptDependencies.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/KotlinDependencies.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/MaterialDesignDependencies.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/TestDependencies.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/ThirdPartyDependencies.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/Versions.class \ No newline at end of file +/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/AndroidXDependencies.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/ClassPathPlugins.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/ComposeDependency.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/Constants.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/DependenciesKt.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/FirebaseDependencies.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/KaptDependencies.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/KotlinDependencies.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/MaterialDesignDependencies.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/TestDependencies.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/ThirdPartyDependencies.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/Versions.class \ No newline at end of file diff --git a/buildSrc/build/kotlin/compileKotlin/cacheable/last-build.bin b/buildSrc/build/kotlin/compileKotlin/cacheable/last-build.bin index 8ded29aaf285f252b08cee91fc2af6cf135559a1..4b7f20cc502b86ca394680d9df8d93134930c869 100644 GIT binary patch literal 18 YcmZ4UmVvdLhk=1{!qxNkoeV$#065|W9{>OV literal 18 YcmZ4UmVvdLhk=1{!Zw?GTN!`=062REH~;_u diff --git a/buildSrc/build/kotlin/compileKotlin/local-state/build-history.bin b/buildSrc/build/kotlin/compileKotlin/local-state/build-history.bin index b4355cc7c5393cae3179503bd71ce8bdb8111504..ebb3f9b34b9a512525430cf5e2e916d8bdf53142 100644 GIT binary patch literal 31 dcmZ4UmVvcgk^ur385kHRTs?2!3FR{|003n11uXyo literal 31 dcmZ4UmVvcgk^ur385kHRY_qwy70PE|003nN1x5e> diff --git a/buildSrc/build/libs/buildSrc.jar b/buildSrc/build/libs/buildSrc.jar index bfe6d490c775f4d3e281d48f17c5221a7cf1cd1d..6d91849a8b0902b0066d03587c2408d6d286097c 100644 GIT binary patch delta 7176 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 delta 7052 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 D6t?w- From ab90c5849b973dd266ce6a71b3a930ce8bbcbae2 Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Thu, 18 Jul 2024 13:51:05 +0900 Subject: [PATCH 21/49] =?UTF-8?q?[fix/#33]=20=EB=B8=94=EB=9F=AD=EB=B3=84?= =?UTF-8?q?=20=EC=A2=8C=EC=84=9D=20=EC=97=B4/=EB=B2=88=20=EB=B2=94?= =?UTF-8?q?=EC=9C=84=20=EC=A1=B0=ED=9A=8C=20API=20=EC=97=B0=EA=B2=B0=20?= =?UTF-8?q?=EB=B0=8F=20=ED=8A=B9=EC=A0=95=20=EA=B5=AC=EC=97=AD=20=EC=97=B4?= =?UTF-8?q?=EB=B3=84=20UI=20=EB=B6=84=EA=B8=B0=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 4 +- .../data/datasource/SeatReviewDataSource.kt | 6 +- .../remote/SeatReviewDataSourceImpl.kt | 8 +-- ...eSeatMaxDto.kt => ResponseSeatRangeDto.kt} | 12 ++-- .../data/remote/SeatReviewService.kt | 6 +- .../repository/SeatReviewRepositoryImpl.kt | 12 ++-- .../{SeatMaxModel.kt => SeatRangeModel.kt} | 2 +- .../domain/repository/SeatReviewRepository.kt | 6 +- .../seatReview/ReviewViewModel.kt | 31 ++++++++- .../seatReview/dialog/SelectSeatDialog.kt | 67 +++++++++++++++++-- .../fragment_select_seat_bottom_sheet.xml | 6 +- "\353\254\264\354\240\234" | 2 +- 12 files changed, 125 insertions(+), 37 deletions(-) rename data/src/main/java/com/depromeet/data/model/response/seatReview/{ResponseSeatMaxDto.kt => ResponseSeatRangeDto.kt} (76%) rename domain/src/main/java/com/depromeet/domain/entity/response/seatReview/{SeatMaxModel.kt => SeatRangeModel.kt} (90%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 80829c2a..2e3b7003 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -73,12 +73,12 @@ android:screenOrientation="portrait" /> - suspend fun getSeatMaxData( + suspend fun getSeatRangeData( stadiumId: Int, sectionId: Int, - ): ResponseSeatMaxDto + ): List suspend fun postSeatReviewData( requestSeatReviewDto: RequestSeatReviewDto, diff --git a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt index 32053480..f7afa536 100644 --- a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt +++ b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt @@ -3,7 +3,7 @@ package com.depromeet.data.datasource.remote import com.depromeet.data.datasource.SeatReviewDataSource import com.depromeet.data.model.request.RequestSeatReviewDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto -import com.depromeet.data.model.response.seatReview.ResponseSeatMaxDto +import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto import com.depromeet.data.remote.SeatReviewService @@ -29,11 +29,11 @@ class SeatReviewDataSourceImpl @Inject constructor( return seatReviewService.getSeatBlock(stadiumId, sectionId) } - override suspend fun getSeatMaxData( + override suspend fun getSeatRangeData( stadiumId: Int, sectionId: Int, - ): ResponseSeatMaxDto { - return seatReviewService.getSeatMax(stadiumId, sectionId) + ): List { + return seatReviewService.getSeatRange(stadiumId, sectionId) } override suspend fun postSeatReviewData(requestSeatReviewDto: RequestSeatReviewDto) { diff --git a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatMaxDto.kt b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatRangeDto.kt similarity index 76% rename from data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatMaxDto.kt rename to data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatRangeDto.kt index 989609f1..83e6c23a 100644 --- a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatMaxDto.kt +++ b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatRangeDto.kt @@ -1,11 +1,11 @@ package com.depromeet.data.model.response.seatReview -import com.depromeet.domain.entity.response.seatReview.SeatMaxModel +import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class ResponseSeatMaxDto( +data class ResponseSeatRangeDto( @SerialName("id") val id: Int, @SerialName("code") @@ -24,8 +24,8 @@ data class ResponseSeatMaxDto( @SerialName("maxSeatNum") val maxSeatNum: Int, ) { - fun toRowInfo(): SeatMaxModel.RowInfo { - return SeatMaxModel.RowInfo( + fun toRowInfo(): SeatRangeModel.RowInfo { + return SeatRangeModel.RowInfo( id = id, number = number, minSeatNum = minSeatNum, @@ -34,8 +34,8 @@ data class ResponseSeatMaxDto( } } - fun toSeatMax(): SeatMaxModel { - return SeatMaxModel( + fun toSeatRange(): SeatRangeModel { + return SeatRangeModel( id = id, code = code, rowInfo = rowInfo.map { it.toRowInfo() }, diff --git a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt index 43280bb0..351dec8a 100644 --- a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt +++ b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt @@ -2,7 +2,7 @@ package com.depromeet.data.remote import com.depromeet.data.model.request.RequestSeatReviewDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto -import com.depromeet.data.model.response.seatReview.ResponseSeatMaxDto +import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto import retrofit2.http.Body @@ -26,10 +26,10 @@ interface SeatReviewService { ): List @GET("/api/v1/stadiums/{stadiumId}/sections/{sectionId}/blocks/rows") - suspend fun getSeatMax( + suspend fun getSeatRange( @Path("stadiumId") stadiumId: Int, @Path("sectionId") sectionId: Int, - ): ResponseSeatMaxDto + ): List @POST("/api/v1/reviews") suspend fun postSeatReview( diff --git a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt index 6b0ab8a0..735ffd72 100644 --- a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt +++ b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt @@ -4,7 +4,7 @@ import com.depromeet.data.datasource.SeatReviewDataSource import com.depromeet.data.model.request.toSeatReview import com.depromeet.domain.entity.request.SeatReviewModel import com.depromeet.domain.entity.response.seatReview.SeatBlockModel -import com.depromeet.domain.entity.response.seatReview.SeatMaxModel +import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import com.depromeet.domain.entity.response.seatReview.StadiumNameModel import com.depromeet.domain.entity.response.seatReview.StadiumSectionModel import com.depromeet.domain.repository.SeatReviewRepository @@ -41,15 +41,13 @@ class SeatReviewRepositoryImpl @Inject constructor( } } - override suspend fun getSeatMax( + override suspend fun getSeatRange( stadiumId: Int, sectionId: Int, - ): Result { + ): Result> { return runCatching { - seatReviewDataSource.getSeatMaxData( - stadiumId, - sectionId, - ).toSeatMax() + val response = seatReviewDataSource.getSeatRangeData(stadiumId, sectionId) + response.map { it.toSeatRange() } } } diff --git a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatMaxModel.kt b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatRangeModel.kt similarity index 90% rename from domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatMaxModel.kt rename to domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatRangeModel.kt index ebc467e6..15faa4af 100644 --- a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatMaxModel.kt +++ b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatRangeModel.kt @@ -1,6 +1,6 @@ package com.depromeet.domain.entity.response.seatReview -data class SeatMaxModel( +data class SeatRangeModel( val id: Int, val code: String, val rowInfo: List, diff --git a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt index d60f11e8..dd9961dc 100644 --- a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt +++ b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt @@ -2,7 +2,7 @@ package com.depromeet.domain.repository import com.depromeet.domain.entity.request.SeatReviewModel import com.depromeet.domain.entity.response.seatReview.SeatBlockModel -import com.depromeet.domain.entity.response.seatReview.SeatMaxModel +import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import com.depromeet.domain.entity.response.seatReview.StadiumNameModel import com.depromeet.domain.entity.response.seatReview.StadiumSectionModel @@ -18,10 +18,10 @@ interface SeatReviewRepository { sectionId: Int, ): Result> - suspend fun getSeatMax( + suspend fun getSeatRange( stadiumId: Int, sectionId: Int, - ): Result + ): Result> suspend fun postSeatReview( seatReviewInfo: SeatReviewModel, diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index 12a431f2..1f84da9e 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.depromeet.core.state.UiState import com.depromeet.domain.entity.response.seatReview.SeatBlockModel +import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import com.depromeet.domain.entity.response.seatReview.StadiumNameModel import com.depromeet.domain.entity.response.seatReview.StadiumSectionModel import com.depromeet.domain.repository.SeatReviewRepository @@ -75,6 +76,9 @@ class ReviewViewModel @Inject constructor( private val _seatBlockState = MutableStateFlow>>(UiState.Empty) val seatBlockState: StateFlow>> = _seatBlockState + private val _seatRangeState = MutableStateFlow>>(UiState.Empty) + val seatRangeState: StateFlow>> = _seatRangeState + fun setSelectedStadiumId(stadiumId: Int) { _selectedStadiumId.value = stadiumId } @@ -142,7 +146,6 @@ class ReviewViewModel @Inject constructor( } } } - fun getSeatBlock(stadiumId: Int, sectionId: Int) { viewModelScope.launch { _seatBlockState.value = UiState.Loading @@ -188,4 +191,30 @@ class ReviewViewModel @Inject constructor( } } } + + fun getSeatRange(stadiumId: Int, sectionId: Int) { + viewModelScope.launch { + _seatRangeState.value = UiState.Loading + seatReviewRepository.getSeatRange( + stadiumId, + sectionId, + ).onSuccess { range -> + Timber.d("GET RANGE SUCCESS : $range") + if (range.isEmpty()) { + _seatRangeState.value = UiState.Empty + } else { + _seatRangeState.value = UiState.Success(range) + } + } + .onFailure { t -> + if (t is HttpException) { + Timber.e("GET RANGE FAILURE : $t") + _seatRangeState.value = UiState.Failure(t.code().toString()) + } + } + } + } + } + + diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index 6a6c4909..5f27b714 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -20,6 +20,7 @@ import coil.load import com.depromeet.core.base.BindingBottomSheetDialog import com.depromeet.core.state.UiState import com.depromeet.domain.entity.response.seatReview.SeatBlockModel +import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import com.depromeet.presentation.R import com.depromeet.presentation.databinding.FragmentSelectSeatBottomSheetBinding import com.depromeet.presentation.extension.setOnSingleClickListener @@ -68,11 +69,15 @@ class SelectSeatDialog : BindingBottomSheetDialog { toast("이미지 오류") } + is UiState.Failure -> { + toast("오류가 발생했습니다") + } + is UiState.Loading -> {} is UiState.Empty -> { toast("오류가 발생했습니다") } + else -> {} } } @@ -81,8 +86,14 @@ class SelectSeatDialog : BindingBottomSheetDialog when (state) { - is UiState.Success -> { observeSuccessSeatBlock(state.data) } - is UiState.Failure -> { toast("오류가 발생했습니다") } + is UiState.Success -> { + observeSuccessSeatBlock(state.data) + } + + is UiState.Failure -> { + toast("오류가 발생했습니다") + } + is UiState.Loading -> {} is UiState.Empty -> {} else -> {} @@ -90,9 +101,56 @@ class SelectSeatDialog : BindingBottomSheetDialog + when (state) { + is UiState.Success -> { + // TODO : 특정 구역의 열이 하나 일 때 UI 분기 처리 + state.data.forEach { range -> + observeSeatRangeColumnUI(range.rowInfo) + } + } + + // TODO : Code(블록)에 포함된 number가 없을 때 + // TODO : -> et_column 빨간색 / tv_none_column_warning - visible / 텍스트는 그대로 + + // TODO : 선택한 code(블록)에서 number(열) 입력 시 number에 포함된 minSeatNum - maxSeatNum 에 포함 안될 시 et_column,et_number 빨강 / tv_none_column_warning - visible & 존재하지 않는 번이에요 / + // TODO : Code(블록)에 포함된 number가 없을 때 + + // TODO : 만약 둘 다 성립 X -> et_column, et_number 둘다 빨강 / 텍스트 존재하지 않는 열과 번이에요 + + is UiState.Failure -> { + toast("오류가 발생했습니다") + } + is UiState.Loading -> {} + is UiState.Empty -> {} + else -> {} + } + } + } + + private fun observeSeatRangeColumnUI(rowInfoList: List) { + if (rowInfoList.size == 1) { + with(binding) { + etColumn.visibility = INVISIBLE + tvColumn.visibility = INVISIBLE + etNumber.visibility = INVISIBLE + etNonColumnNumber.visibility = VISIBLE + } + } else { + with(binding) { + etColumn.visibility = VISIBLE + tvColumn.visibility = VISIBLE + etNumber.visibility = VISIBLE + etNonColumnNumber.visibility = INVISIBLE + } + } + } + private fun observeSuccessSeatBlock(blockItems: List) { val blockCodes = blockItems.map { it.code } - val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, blockCodes) + val adapter = + ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, blockCodes) adapter.setDropDownViewResource(R.layout.custom_spinner_dropdown_item) with(binding.spinnerBlock) { @@ -122,6 +180,7 @@ class SelectSeatDialog : BindingBottomSheetDialog + + Date: Thu, 18 Jul 2024 16:48:34 +0900 Subject: [PATCH 22/49] =?UTF-8?q?[feat/#33]=20=EC=97=B4/=EB=B2=88=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=B2=98=EB=A6=AC=20UI=20=EB=B6=84?= =?UTF-8?q?=EA=B8=B0=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/SeatReviewRepositoryImpl.kt | 2 +- .../seatReview/dialog/SelectSeatDialog.kt | 74 ++++++++++++++++--- .../fragment_select_seat_bottom_sheet.xml | 6 +- 3 files changed, 66 insertions(+), 16 deletions(-) diff --git a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt index 735ffd72..b2f21f7f 100644 --- a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt +++ b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt @@ -44,7 +44,7 @@ class SeatReviewRepositoryImpl @Inject constructor( override suspend fun getSeatRange( stadiumId: Int, sectionId: Int, - ): Result> { + ): Result> { return runCatching { val response = seatReviewDataSource.getSeatRangeData(stadiumId, sectionId) response.map { it.toSeatRange() } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index 5f27b714..cf788c9e 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -48,6 +48,7 @@ class SelectSeatDialog : BindingBottomSheetDialog when (state) { is UiState.Success -> { - // TODO : 특정 구역의 열이 하나 일 때 UI 분기 처리 state.data.forEach { range -> - observeSeatRangeColumnUI(range.rowInfo) + updateIsExistedColumnUI(range.rowInfo) + updateColumnNUmberUI(range) } } - // TODO : Code(블록)에 포함된 number가 없을 때 - // TODO : -> et_column 빨간색 / tv_none_column_warning - visible / 텍스트는 그대로 - - // TODO : 선택한 code(블록)에서 number(열) 입력 시 number에 포함된 minSeatNum - maxSeatNum 에 포함 안될 시 et_column,et_number 빨강 / tv_none_column_warning - visible & 존재하지 않는 번이에요 / - // TODO : Code(블록)에 포함된 number가 없을 때 - - // TODO : 만약 둘 다 성립 X -> et_column, et_number 둘다 빨강 / 텍스트 존재하지 않는 열과 번이에요 - is UiState.Failure -> { toast("오류가 발생했습니다") } + is UiState.Loading -> {} is UiState.Empty -> {} else -> {} @@ -129,7 +123,32 @@ class SelectSeatDialog : BindingBottomSheetDialog) { + private fun updateColumnNUmberUI(range: SeatRangeModel) { + val selectedNumber = viewModel.selectedNumber.value.toIntOrNull() + val selectedBlock = viewModel.selectedBlock.value + val selectedColumn = viewModel.selectedColumn.value + + if (range.code == selectedBlock) { + val matchingRowInfo = range.rowInfo.find { it.number.toString() == selectedColumn } + if (matchingRowInfo == null) { + updateColumnWarning("존재하지 않는 열이에요") + } else { + if (selectedNumber != null && (selectedNumber < matchingRowInfo.minSeatNum || selectedNumber > matchingRowInfo.maxSeatNum)) { + updateNumberWarning("존재하지 않는 번이에요") + } else { + updateBackWarnings() + } + } + } else { + if (selectedNumber == null || range.rowInfo.none { it.minSeatNum <= selectedNumber && it.maxSeatNum >= selectedNumber }) { + updateBothWarnings("존재하지 않는 열과 번이에요") + } else { + updateBackWarnings() + } + } + } + + private fun updateIsExistedColumnUI(rowInfoList: List) { if (rowInfoList.size == 1) { with(binding) { etColumn.visibility = INVISIBLE @@ -255,4 +274,37 @@ class SelectSeatDialog : BindingBottomSheetDialog - - Date: Thu, 18 Jul 2024 22:36:17 +0900 Subject: [PATCH 23/49] =?UTF-8?q?[feat/#33]=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=EC=9A=A9=20presigned=20url=20=EC=83=9D=EC=84=B1=20API=20data,?= =?UTF-8?q?=20domain=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasource/SeatReviewDataSource.kt | 7 +++++++ .../remote/SeatReviewDataSourceImpl.kt | 9 +++++++++ .../data/model/request/RequestUploadUrlDto.kt | 15 +++++++++++++++ .../response/seatReview/ResponseUploadUrlDto.kt | 16 ++++++++++++++++ .../depromeet/data/remote/SeatReviewService.kt | 8 ++++++++ .../data/repository/SeatReviewRepositoryImpl.kt | 15 +++++++++++++++ .../entity/request/RequestUploadUrlModel.kt | 5 +++++ .../seatReview/ResponseUploadUrlModel.kt | 5 +++++ .../domain/repository/SeatReviewRepository.kt | 7 +++++++ 9 files changed, 87 insertions(+) create mode 100644 data/src/main/java/com/depromeet/data/model/request/RequestUploadUrlDto.kt create mode 100644 data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseUploadUrlDto.kt create mode 100644 domain/src/main/java/com/depromeet/domain/entity/request/RequestUploadUrlModel.kt create mode 100644 domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponseUploadUrlModel.kt diff --git a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt index d5011e0f..968f2efd 100644 --- a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt +++ b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt @@ -1,10 +1,12 @@ package com.depromeet.data.datasource import com.depromeet.data.model.request.RequestSeatReviewDto +import com.depromeet.data.model.request.RequestUploadUrlDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto +import com.depromeet.data.model.response.seatReview.ResponseUploadUrlDto interface SeatReviewDataSource { suspend fun getStadiumNameData(): List @@ -26,4 +28,9 @@ interface SeatReviewDataSource { suspend fun postSeatReviewData( requestSeatReviewDto: RequestSeatReviewDto, ) + + suspend fun postUploadUrlData( + memberId: Int, + requestUploadUrlDto: RequestUploadUrlDto, + ): ResponseUploadUrlDto } diff --git a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt index f7afa536..e35ecac8 100644 --- a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt +++ b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt @@ -2,10 +2,12 @@ package com.depromeet.data.datasource.remote import com.depromeet.data.datasource.SeatReviewDataSource import com.depromeet.data.model.request.RequestSeatReviewDto +import com.depromeet.data.model.request.RequestUploadUrlDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto +import com.depromeet.data.model.response.seatReview.ResponseUploadUrlDto import com.depromeet.data.remote.SeatReviewService import javax.inject.Inject @@ -39,4 +41,11 @@ class SeatReviewDataSourceImpl @Inject constructor( override suspend fun postSeatReviewData(requestSeatReviewDto: RequestSeatReviewDto) { return seatReviewService.postSeatReview(requestSeatReviewDto) } + + override suspend fun postUploadUrlData( + memberId: Int, + requestUploadUrlDto: RequestUploadUrlDto, + ): ResponseUploadUrlDto { + return seatReviewService.postUploadUrl(memberId, requestUploadUrlDto) + } } diff --git a/data/src/main/java/com/depromeet/data/model/request/RequestUploadUrlDto.kt b/data/src/main/java/com/depromeet/data/model/request/RequestUploadUrlDto.kt new file mode 100644 index 00000000..06a7b385 --- /dev/null +++ b/data/src/main/java/com/depromeet/data/model/request/RequestUploadUrlDto.kt @@ -0,0 +1,15 @@ +package com.depromeet.data.model.request + +import com.depromeet.domain.entity.request.RequestUploadUrlModel +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RequestUploadUrlDto( + @SerialName("fileExtension") + val fileExtension: String, +) + +fun RequestUploadUrlModel.toRequestUploadUrl(): RequestUploadUrlDto { + return RequestUploadUrlDto(fileExtension) +} diff --git a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseUploadUrlDto.kt b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseUploadUrlDto.kt new file mode 100644 index 00000000..d98a4e68 --- /dev/null +++ b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseUploadUrlDto.kt @@ -0,0 +1,16 @@ +package com.depromeet.data.model.response.seatReview + +import com.depromeet.domain.entity.response.seatReview.RecommendRequestModel +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseUploadUrlDto( + @SerialName("presignedUrl") + val presignedUrl: String, +) { + + fun toResponseUploadUrl(): RecommendRequestModel { + return RecommendRequestModel(presignedUrl) + } +} diff --git a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt index 351dec8a..d6ce4b6a 100644 --- a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt +++ b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt @@ -1,10 +1,12 @@ package com.depromeet.data.remote import com.depromeet.data.model.request.RequestSeatReviewDto +import com.depromeet.data.model.request.RequestUploadUrlDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto +import com.depromeet.data.model.response.seatReview.ResponseUploadUrlDto import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST @@ -35,4 +37,10 @@ interface SeatReviewService { suspend fun postSeatReview( @Body requestPostSignupDto: RequestSeatReviewDto, ) + + @POST("/api/v1/members/{memberId}/reviews/images") + suspend fun postUploadUrl( + @Path("memberId") memberId: Int, + @Body requestUploadUrlDto: RequestUploadUrlDto, + ): ResponseUploadUrlDto } diff --git a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt index b2f21f7f..c843e61a 100644 --- a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt +++ b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt @@ -1,8 +1,11 @@ package com.depromeet.data.repository import com.depromeet.data.datasource.SeatReviewDataSource +import com.depromeet.data.model.request.toRequestUploadUrl import com.depromeet.data.model.request.toSeatReview +import com.depromeet.domain.entity.request.RequestUploadUrlModel import com.depromeet.domain.entity.request.SeatReviewModel +import com.depromeet.domain.entity.response.seatReview.RecommendRequestModel import com.depromeet.domain.entity.response.seatReview.SeatBlockModel import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import com.depromeet.domain.entity.response.seatReview.StadiumNameModel @@ -60,4 +63,16 @@ class SeatReviewRepositoryImpl @Inject constructor( ) } } + + override suspend fun postUploadUrl( + memberId: Int, + requestUploadUrlModel: RequestUploadUrlModel, + ): Result { + return runCatching { + seatReviewDataSource.postUploadUrlData( + memberId, + requestUploadUrlModel.toRequestUploadUrl(), + ).toResponseUploadUrl() + } + } } diff --git a/domain/src/main/java/com/depromeet/domain/entity/request/RequestUploadUrlModel.kt b/domain/src/main/java/com/depromeet/domain/entity/request/RequestUploadUrlModel.kt new file mode 100644 index 00000000..fcf49fcb --- /dev/null +++ b/domain/src/main/java/com/depromeet/domain/entity/request/RequestUploadUrlModel.kt @@ -0,0 +1,5 @@ +package com.depromeet.domain.entity.request + +data class RequestUploadUrlModel( + val fileExtension: String, +) diff --git a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponseUploadUrlModel.kt b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponseUploadUrlModel.kt new file mode 100644 index 00000000..3c8b889d --- /dev/null +++ b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponseUploadUrlModel.kt @@ -0,0 +1,5 @@ +package com.depromeet.domain.entity.response.seatReview + +data class RecommendRequestModel( + val presignedUrl: String, +) diff --git a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt index dd9961dc..c24887e7 100644 --- a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt +++ b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt @@ -1,6 +1,8 @@ package com.depromeet.domain.repository +import com.depromeet.domain.entity.request.RequestUploadUrlModel import com.depromeet.domain.entity.request.SeatReviewModel +import com.depromeet.domain.entity.response.seatReview.RecommendRequestModel import com.depromeet.domain.entity.response.seatReview.SeatBlockModel import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import com.depromeet.domain.entity.response.seatReview.StadiumNameModel @@ -26,4 +28,9 @@ interface SeatReviewRepository { suspend fun postSeatReview( seatReviewInfo: SeatReviewModel, ): Result + + suspend fun postUploadUrl( + memberId: Int, + requestUploadUrlModel: RequestUploadUrlModel, + ): Result } From 74ae1e542707ba92be5f608aabebcd4e62cc3661 Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Fri, 19 Jul 2024 00:05:19 +0900 Subject: [PATCH 24/49] =?UTF-8?q?[feat/#33]=20svg=20=ED=8C=8C=EC=9D=BC=20c?= =?UTF-8?q?oil=20=ED=99=95=EC=9E=A5=20=ED=95=A8=EC=88=98=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../seatReview/dialog/SelectSeatDialog.kt | 4 ++-- .../com/depromeet/presentation/util/Utils.kt | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index cf788c9e..4d213fb2 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -27,6 +27,7 @@ import com.depromeet.presentation.extension.setOnSingleClickListener import com.depromeet.presentation.extension.toast import com.depromeet.presentation.seatReview.ReviewViewModel import com.depromeet.presentation.seatReview.adapter.SectionListAdapter +import com.depromeet.presentation.util.Utils.loadImageFromUrl import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -66,8 +67,7 @@ class SelectSeatDialog : BindingBottomSheetDialog { adapter.submitList(state.data.sectionList) - // TODO : SVG IMAGE LOAD - binding.ivSeatAgain.load(state.data.seatChart) + binding.ivSeatAgain.loadImageFromUrl(state.data.seatChart) } is UiState.Failure -> { diff --git a/presentation/src/main/java/com/depromeet/presentation/util/Utils.kt b/presentation/src/main/java/com/depromeet/presentation/util/Utils.kt index 83f5dc4a..0b614106 100644 --- a/presentation/src/main/java/com/depromeet/presentation/util/Utils.kt +++ b/presentation/src/main/java/com/depromeet/presentation/util/Utils.kt @@ -2,6 +2,10 @@ package com.depromeet.presentation.util import android.content.Context import android.content.Intent +import android.widget.ImageView +import coil.ImageLoader +import coil.decode.SvgDecoder +import coil.request.ImageRequest object Utils { fun restartApp(context: Context, toastMsg: String?) { @@ -14,4 +18,21 @@ object Utils { } Runtime.getRuntime().exit(0) } + + fun ImageView.loadImageFromUrl(url: String) { + val imageLoader = ImageLoader.Builder(this.context) + .components { + add(SvgDecoder.Factory()) + } + .build() + + val request = ImageRequest.Builder(this.context) + .crossfade(true) + .crossfade(2) + .data(url) + .target(this) + .build() + + imageLoader.enqueue(request) + } } From 5a31f7083bb9ff30b967d2eb34ddff2afd9a133a Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Fri, 19 Jul 2024 00:10:43 +0900 Subject: [PATCH 25/49] =?UTF-8?q?[fix/#33]=20=EC=A7=81=EA=B4=80=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0=20=EB=93=B1=EB=A1=9D=20API=20endPoint,=20dto?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=EC=82=AC=ED=95=AD=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/request/RequestSeatReviewDto.kt | 24 +++++++------------ .../data/remote/SeatReviewService.kt | 2 +- .../domain/entity/request/SeatReviewModel.kt | 8 +++---- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt b/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt index d1cbbbc6..ec0e02d4 100644 --- a/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt +++ b/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt @@ -6,18 +6,14 @@ import kotlinx.serialization.Serializable @Serializable data class RequestSeatReviewDto( - @SerialName("stadiumId") - val stadiumId: Int, - @SerialName("blockId") - val blockId: Int, - @SerialName("rowId") - val rowId: Int, - @SerialName("seatNumber") - val seatNumber: Int, + @SerialName("memberId") + val memberId: Int, + @SerialName("seatId") + val seatId: Int, @SerialName("images") val images: List, - @SerialName("date") - val date: String, + @SerialName("dateTime") + val dateTime: String, @SerialName("good") val good: List, @SerialName("bad") @@ -27,12 +23,10 @@ data class RequestSeatReviewDto( ) fun SeatReviewModel.toSeatReview() = RequestSeatReviewDto( - stadiumId = stadiumId, - blockId = blockId, - rowId = rowId, - seatNumber = seatNumber, + memberId = memberId, + seatId = seatId, images = images, - date = date, + dateTime = dateTime, good = good, bad = bad, content = content, diff --git a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt index d6ce4b6a..dc683d0f 100644 --- a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt +++ b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt @@ -33,7 +33,7 @@ interface SeatReviewService { @Path("sectionId") sectionId: Int, ): List - @POST("/api/v1/reviews") + @POST("/api/v1/seats/{seatId}/members/{memberId}/reviews") suspend fun postSeatReview( @Body requestPostSignupDto: RequestSeatReviewDto, ) diff --git a/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt b/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt index ad50cdba..23aa51f2 100644 --- a/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt +++ b/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt @@ -1,12 +1,10 @@ package com.depromeet.domain.entity.request data class SeatReviewModel( - val stadiumId: Int, - val blockId: Int, - val rowId: Int, - val seatNumber: Int, + val memberId: Int, + val seatId: Int, val images: List, - val date: String, + val dateTime: String, val good: List, val bad: List, val content: String, From 1a905215cb6e86486f140fb6dd4e10207730034b Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Fri, 19 Jul 2024 16:22:54 +0900 Subject: [PATCH 26/49] =?UTF-8?q?[fix/#33]=20image=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=20svg=20->=20png=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../seatReview/dialog/SelectSeatDialog.kt | 2 +- .../com/depromeet/presentation/util/Utils.kt | 17 ----------------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index 4d213fb2..56eb305c 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -67,7 +67,7 @@ class SelectSeatDialog : BindingBottomSheetDialog { adapter.submitList(state.data.sectionList) - binding.ivSeatAgain.loadImageFromUrl(state.data.seatChart) + binding.ivSeatAgain.load(state.data.seatChart) } is UiState.Failure -> { diff --git a/presentation/src/main/java/com/depromeet/presentation/util/Utils.kt b/presentation/src/main/java/com/depromeet/presentation/util/Utils.kt index 0b614106..b9bdb996 100644 --- a/presentation/src/main/java/com/depromeet/presentation/util/Utils.kt +++ b/presentation/src/main/java/com/depromeet/presentation/util/Utils.kt @@ -18,21 +18,4 @@ object Utils { } Runtime.getRuntime().exit(0) } - - fun ImageView.loadImageFromUrl(url: String) { - val imageLoader = ImageLoader.Builder(this.context) - .components { - add(SvgDecoder.Factory()) - } - .build() - - val request = ImageRequest.Builder(this.context) - .crossfade(true) - .crossfade(2) - .data(url) - .target(this) - .build() - - imageLoader.enqueue(request) - } } From f359026ac249ecc46cfbb17b025baac29494e7f8 Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Sat, 20 Jul 2024 00:04:46 +0900 Subject: [PATCH 27/49] =?UTF-8?q?[feat/#33]=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20url=20=EC=83=9D=EC=84=B1=20API=20?= =?UTF-8?q?data,=20domain=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasource/SeatReviewDataSource.kt | 14 ++++++---- .../remote/SeatReviewDataSourceImpl.kt | 22 +++++++++++---- .../model/request/RequestPreSignedUrlDto.kt | 10 +++++++ .../data/model/request/RequestUploadUrlDto.kt | 15 ---------- .../seatReview/ResponsePreSignedUrlDto.kt | 16 +++++++++++ .../seatReview/ResponseUploadUrlDto.kt | 16 ----------- .../data/remote/SeatReviewService.kt | 19 +++++++++---- .../repository/SeatReviewRepositoryImpl.kt | 28 +++++++++++++------ .../seatReview/ResponsePresignedUrlModel.kt | 5 ++++ .../seatReview/ResponseUploadUrlModel.kt | 5 ---- .../domain/repository/SeatReviewRepository.kt | 14 ++++++---- .../seatReview/dialog/SelectSeatDialog.kt | 3 +- .../com/depromeet/presentation/util/Utils.kt | 4 --- 13 files changed, 100 insertions(+), 71 deletions(-) create mode 100644 data/src/main/java/com/depromeet/data/model/request/RequestPreSignedUrlDto.kt delete mode 100644 data/src/main/java/com/depromeet/data/model/request/RequestUploadUrlDto.kt create mode 100644 data/src/main/java/com/depromeet/data/model/response/seatReview/ResponsePreSignedUrlDto.kt delete mode 100644 data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseUploadUrlDto.kt create mode 100644 domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponsePresignedUrlModel.kt delete mode 100644 domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponseUploadUrlModel.kt diff --git a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt index 968f2efd..d94ded4a 100644 --- a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt +++ b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt @@ -1,12 +1,11 @@ package com.depromeet.data.datasource import com.depromeet.data.model.request.RequestSeatReviewDto -import com.depromeet.data.model.request.RequestUploadUrlDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto -import com.depromeet.data.model.response.seatReview.ResponseUploadUrlDto +import com.depromeet.data.model.response.seatReview.ResponsePreSignedUrlDto interface SeatReviewDataSource { suspend fun getStadiumNameData(): List @@ -29,8 +28,13 @@ interface SeatReviewDataSource { requestSeatReviewDto: RequestSeatReviewDto, ) - suspend fun postUploadUrlData( + suspend fun postImagePreSignedData( + fileExtension: String, memberId: Int, - requestUploadUrlDto: RequestUploadUrlDto, - ): ResponseUploadUrlDto + ): ResponsePreSignedUrlDto + + suspend fun putReviewImageData( + presignedUrl: String, + image: ByteArray, + ) } diff --git a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt index e35ecac8..9a99a4dd 100644 --- a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt +++ b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt @@ -2,13 +2,15 @@ package com.depromeet.data.datasource.remote import com.depromeet.data.datasource.SeatReviewDataSource import com.depromeet.data.model.request.RequestSeatReviewDto -import com.depromeet.data.model.request.RequestUploadUrlDto +import com.depromeet.data.model.request.RequestPreSignedUrlDto +import com.depromeet.data.model.response.seatReview.ResponsePreSignedUrlDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto -import com.depromeet.data.model.response.seatReview.ResponseUploadUrlDto import com.depromeet.data.remote.SeatReviewService +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody.Companion.toRequestBody import javax.inject.Inject class SeatReviewDataSourceImpl @Inject constructor( @@ -42,10 +44,18 @@ class SeatReviewDataSourceImpl @Inject constructor( return seatReviewService.postSeatReview(requestSeatReviewDto) } - override suspend fun postUploadUrlData( + override suspend fun postImagePreSignedData( + fileExtension: String, memberId: Int, - requestUploadUrlDto: RequestUploadUrlDto, - ): ResponseUploadUrlDto { - return seatReviewService.postUploadUrl(memberId, requestUploadUrlDto) + ): ResponsePreSignedUrlDto { + return seatReviewService.postImagePreSignedUrl( + RequestPreSignedUrlDto(fileExtension), + memberId, + ) + } + + override suspend fun putReviewImageData(presignedUrl: String, image: ByteArray) { + val mediaType = "image/*".toMediaTypeOrNull() + return seatReviewService.putProfileImage(presignedUrl, image.toRequestBody(mediaType)) } } diff --git a/data/src/main/java/com/depromeet/data/model/request/RequestPreSignedUrlDto.kt b/data/src/main/java/com/depromeet/data/model/request/RequestPreSignedUrlDto.kt new file mode 100644 index 00000000..3ab5c0e2 --- /dev/null +++ b/data/src/main/java/com/depromeet/data/model/request/RequestPreSignedUrlDto.kt @@ -0,0 +1,10 @@ +package com.depromeet.data.model.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RequestPreSignedUrlDto( + @SerialName("fileExtension") + val fileExtension: String, +) diff --git a/data/src/main/java/com/depromeet/data/model/request/RequestUploadUrlDto.kt b/data/src/main/java/com/depromeet/data/model/request/RequestUploadUrlDto.kt deleted file mode 100644 index 06a7b385..00000000 --- a/data/src/main/java/com/depromeet/data/model/request/RequestUploadUrlDto.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.depromeet.data.model.request - -import com.depromeet.domain.entity.request.RequestUploadUrlModel -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class RequestUploadUrlDto( - @SerialName("fileExtension") - val fileExtension: String, -) - -fun RequestUploadUrlModel.toRequestUploadUrl(): RequestUploadUrlDto { - return RequestUploadUrlDto(fileExtension) -} diff --git a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponsePreSignedUrlDto.kt b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponsePreSignedUrlDto.kt new file mode 100644 index 00000000..add748d5 --- /dev/null +++ b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponsePreSignedUrlDto.kt @@ -0,0 +1,16 @@ +package com.depromeet.data.model.response.seatReview + +import com.depromeet.domain.entity.response.seatReview.ResponsePresignedUrlModel +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponsePreSignedUrlDto( + @SerialName("presignedUrl") + val presignedUrl: String, +) { + + fun toResponsePreSignedUrl(): ResponsePresignedUrlModel { + return ResponsePresignedUrlModel(presignedUrl) + } +} diff --git a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseUploadUrlDto.kt b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseUploadUrlDto.kt deleted file mode 100644 index d98a4e68..00000000 --- a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseUploadUrlDto.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.depromeet.data.model.response.seatReview - -import com.depromeet.domain.entity.response.seatReview.RecommendRequestModel -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class ResponseUploadUrlDto( - @SerialName("presignedUrl") - val presignedUrl: String, -) { - - fun toResponseUploadUrl(): RecommendRequestModel { - return RecommendRequestModel(presignedUrl) - } -} diff --git a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt index dc683d0f..0308ec40 100644 --- a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt +++ b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt @@ -1,16 +1,19 @@ package com.depromeet.data.remote import com.depromeet.data.model.request.RequestSeatReviewDto -import com.depromeet.data.model.request.RequestUploadUrlDto +import com.depromeet.data.model.request.RequestPreSignedUrlDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto -import com.depromeet.data.model.response.seatReview.ResponseUploadUrlDto +import com.depromeet.data.model.response.seatReview.ResponsePreSignedUrlDto +import okhttp3.RequestBody import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST +import retrofit2.http.PUT import retrofit2.http.Path +import retrofit2.http.Url interface SeatReviewService { @GET("/api/v1/stadiums/names") @@ -39,8 +42,14 @@ interface SeatReviewService { ) @POST("/api/v1/members/{memberId}/reviews/images") - suspend fun postUploadUrl( + suspend fun postImagePreSignedUrl( + @Body body: RequestPreSignedUrlDto, @Path("memberId") memberId: Int, - @Body requestUploadUrlDto: RequestUploadUrlDto, - ): ResponseUploadUrlDto + ): ResponsePreSignedUrlDto + + @PUT + suspend fun putProfileImage( + @Url preSignedUrl: String, + @Body image: RequestBody, + ) } diff --git a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt index c843e61a..18c91ae5 100644 --- a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt +++ b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt @@ -1,11 +1,9 @@ package com.depromeet.data.repository import com.depromeet.data.datasource.SeatReviewDataSource -import com.depromeet.data.model.request.toRequestUploadUrl import com.depromeet.data.model.request.toSeatReview -import com.depromeet.domain.entity.request.RequestUploadUrlModel import com.depromeet.domain.entity.request.SeatReviewModel -import com.depromeet.domain.entity.response.seatReview.RecommendRequestModel +import com.depromeet.domain.entity.response.seatReview.ResponsePresignedUrlModel import com.depromeet.domain.entity.response.seatReview.SeatBlockModel import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import com.depromeet.domain.entity.response.seatReview.StadiumNameModel @@ -64,15 +62,27 @@ class SeatReviewRepositoryImpl @Inject constructor( } } - override suspend fun postUploadUrl( + override suspend fun postReviewImagePresigned( + fileExtension: String, memberId: Int, - requestUploadUrlModel: RequestUploadUrlModel, - ): Result { + ): Result { return runCatching { - seatReviewDataSource.postUploadUrlData( + seatReviewDataSource.postImagePreSignedData( + fileExtension, memberId, - requestUploadUrlModel.toRequestUploadUrl(), - ).toResponseUploadUrl() + ).toResponsePreSignedUrl() + } + } + + override suspend fun putImagePreSignedUrl( + presignedUrl: String, + image: ByteArray, + ): Result { + return runCatching { + seatReviewDataSource.putReviewImageData( + presignedUrl, + image, + ) } } } diff --git a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponsePresignedUrlModel.kt b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponsePresignedUrlModel.kt new file mode 100644 index 00000000..3a09d548 --- /dev/null +++ b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponsePresignedUrlModel.kt @@ -0,0 +1,5 @@ +package com.depromeet.domain.entity.response.seatReview + +data class ResponsePresignedUrlModel( + val presignedUrl: String = "", +) diff --git a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponseUploadUrlModel.kt b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponseUploadUrlModel.kt deleted file mode 100644 index 3c8b889d..00000000 --- a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/ResponseUploadUrlModel.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.depromeet.domain.entity.response.seatReview - -data class RecommendRequestModel( - val presignedUrl: String, -) diff --git a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt index c24887e7..21e40d31 100644 --- a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt +++ b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt @@ -1,8 +1,7 @@ package com.depromeet.domain.repository -import com.depromeet.domain.entity.request.RequestUploadUrlModel import com.depromeet.domain.entity.request.SeatReviewModel -import com.depromeet.domain.entity.response.seatReview.RecommendRequestModel +import com.depromeet.domain.entity.response.seatReview.ResponsePresignedUrlModel import com.depromeet.domain.entity.response.seatReview.SeatBlockModel import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import com.depromeet.domain.entity.response.seatReview.StadiumNameModel @@ -29,8 +28,13 @@ interface SeatReviewRepository { seatReviewInfo: SeatReviewModel, ): Result - suspend fun postUploadUrl( + suspend fun postReviewImagePresigned( + fileExtension: String, memberId: Int, - requestUploadUrlModel: RequestUploadUrlModel, - ): Result + ): Result + + suspend fun putImagePreSignedUrl( + presignedUrl: String, + image: ByteArray, + ): Result } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index 56eb305c..1468ba58 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -27,7 +27,6 @@ import com.depromeet.presentation.extension.setOnSingleClickListener import com.depromeet.presentation.extension.toast import com.depromeet.presentation.seatReview.ReviewViewModel import com.depromeet.presentation.seatReview.adapter.SectionListAdapter -import com.depromeet.presentation.util.Utils.loadImageFromUrl import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -123,6 +122,8 @@ class SelectSeatDialog : BindingBottomSheetDialog Date: Sat, 20 Jul 2024 01:02:49 +0900 Subject: [PATCH 28/49] =?UTF-8?q?[feat/#33]=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20url=20=EC=83=9D=EC=84=B1=20API=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=20=ED=86=B5=EC=8B=A0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasource/SeatReviewDataSource.kt | 2 +- .../remote/SeatReviewDataSourceImpl.kt | 2 +- .../data/remote/SeatReviewService.kt | 4 +- .../presentation/seatReview/ReviewActivity.kt | 35 ++++++++++++++- .../seatReview/ReviewViewModel.kt | 43 ++++++++++++++++++- 5 files changed, 78 insertions(+), 8 deletions(-) diff --git a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt index d94ded4a..a0ea79b3 100644 --- a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt +++ b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt @@ -1,11 +1,11 @@ package com.depromeet.data.datasource import com.depromeet.data.model.request.RequestSeatReviewDto +import com.depromeet.data.model.response.seatReview.ResponsePreSignedUrlDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto -import com.depromeet.data.model.response.seatReview.ResponsePreSignedUrlDto interface SeatReviewDataSource { suspend fun getStadiumNameData(): List diff --git a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt index 9a99a4dd..2be96346 100644 --- a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt +++ b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt @@ -1,8 +1,8 @@ package com.depromeet.data.datasource.remote import com.depromeet.data.datasource.SeatReviewDataSource -import com.depromeet.data.model.request.RequestSeatReviewDto import com.depromeet.data.model.request.RequestPreSignedUrlDto +import com.depromeet.data.model.request.RequestSeatReviewDto import com.depromeet.data.model.response.seatReview.ResponsePreSignedUrlDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto diff --git a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt index 0308ec40..bbb8594e 100644 --- a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt +++ b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt @@ -1,12 +1,12 @@ package com.depromeet.data.remote -import com.depromeet.data.model.request.RequestSeatReviewDto import com.depromeet.data.model.request.RequestPreSignedUrlDto +import com.depromeet.data.model.request.RequestSeatReviewDto +import com.depromeet.data.model.response.seatReview.ResponsePreSignedUrlDto import com.depromeet.data.model.response.seatReview.ResponseSeatBlockDto import com.depromeet.data.model.response.seatReview.ResponseSeatRangeDto import com.depromeet.data.model.response.seatReview.ResponseStadiumNameDto import com.depromeet.data.model.response.seatReview.ResponseStadiumSectionDto -import com.depromeet.data.model.response.seatReview.ResponsePreSignedUrlDto import okhttp3.RequestBody import retrofit2.http.Body import retrofit2.http.GET diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index 770c7251..976bb2f0 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -6,8 +6,10 @@ import android.os.Bundle import android.view.View import android.view.View.INVISIBLE import android.view.View.VISIBLE +import android.webkit.MimeTypeMap import android.widget.FrameLayout import android.widget.ImageView +import android.widget.Toast import androidx.activity.viewModels import androidx.core.content.ContextCompat import androidx.core.view.isVisible @@ -25,6 +27,7 @@ import com.depromeet.presentation.seatReview.dialog.ImageUploadDialog import com.depromeet.presentation.seatReview.dialog.ReviewMySeatDialog import com.depromeet.presentation.seatReview.dialog.SelectSeatDialog import dagger.hilt.android.AndroidEntryPoint +import java.io.File import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale @@ -206,7 +209,7 @@ class ReviewActivity : BaseActivity({ val block = viewModel.selectedBlock.value val column = viewModel.selectedColumn.value val number = viewModel.selectedNumber.value - if (listOf(seatName, block, column, number).any { it.isNullOrEmpty()}) { + if (listOf(seatName, block, column, number).any { it.isNullOrEmpty() }) { binding.layoutSeatInfo.visibility = INVISIBLE } else { binding.layoutSeatInfo.visibility = VISIBLE @@ -265,7 +268,35 @@ class ReviewActivity : BaseActivity({ private fun navigateToReviewDoneActivity() { binding.tvUploadBtn.setOnSingleClickListener { - Intent(this, ReviewDoneActivity::class.java).apply { startActivity(this) } + selectedImageUris.forEach { imageUriString -> + val imageUri = Uri.parse(imageUriString) + val imageFile = File(imageUri.path!!) + val fileExtension = MimeTypeMap.getFileExtensionFromUrl(imageUri.toString()) + + // presigned URL 요청 + //TODO : MemberID 받기 + viewModel.requestPresignedUrl(fileExtension, 1) + + // presigned URL 응답을 관찰 후 업로드 후 이동 + viewModel.getPreSignedUrl.asLiveData().observe(this) { state -> + when (state) { + is UiState.Success -> { + val presignedUrl = state.data.presignedUrl + val imageData = imageFile.readBytes() + // 이미지 업로드 + viewModel.uploadImageToPreSignedUrl(presignedUrl, imageData) + Intent(this, ReviewDoneActivity::class.java).apply { startActivity(this) } + } + + is UiState.Failure -> { + Toast.makeText(this, "Presigned URL 요청 실패: $state", Toast.LENGTH_SHORT) + .show() + } + + else -> {} + } + } + } } } } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index 1f84da9e..4cf4693e 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -3,6 +3,7 @@ package com.depromeet.presentation.seatReview import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.depromeet.core.state.UiState +import com.depromeet.domain.entity.response.seatReview.ResponsePresignedUrlModel import com.depromeet.domain.entity.response.seatReview.SeatBlockModel import com.depromeet.domain.entity.response.seatReview.SeatRangeModel import com.depromeet.domain.entity.response.seatReview.StadiumNameModel @@ -79,6 +80,10 @@ class ReviewViewModel @Inject constructor( private val _seatRangeState = MutableStateFlow>>(UiState.Empty) val seatRangeState: StateFlow>> = _seatRangeState + private val _getPreSignedUrl = + MutableStateFlow>(UiState.Loading) + val getPreSignedUrl = _getPreSignedUrl.asStateFlow() + fun setSelectedStadiumId(stadiumId: Int) { _selectedStadiumId.value = stadiumId } @@ -215,6 +220,40 @@ class ReviewViewModel @Inject constructor( } } -} - + // presigned URL 요청 + fun requestPresignedUrl(fileExtension: String, memberId: Int) { + viewModelScope.launch { + _getPreSignedUrl.value = UiState.Loading + seatReviewRepository.postReviewImagePresigned(fileExtension, memberId) + .onSuccess { response -> + Timber.e("REQUEST PRESIGNED URL SUCCESS : $response") + _getPreSignedUrl.value = UiState.Success(response) + } + .onFailure { t -> + Timber.e("REQUEST PRESIGNED URL FAILURE : $t") + if (t is HttpException) { + _getPreSignedUrl.value = UiState.Failure(t.code().toString()) + } else { + _getPreSignedUrl.value = UiState.Failure(t.message ?: "Unknown error") + } + } + } + } + // 이미지 업로드 + fun uploadImageToPreSignedUrl(presignedUrl: String, image: ByteArray) { + viewModelScope.launch { + val result = seatReviewRepository.putImagePreSignedUrl(presignedUrl, image) + result.onSuccess { + Timber.d("UPLOAD IMAGE SUCCESS") + }.onFailure { t -> + Timber.e("UPLOAD IMAGE FAILURE : $t") + if (t is HttpException) { + Timber.e("HTTP error code: ${t.code()}") + } else { + Timber.e("General error: ${t.message ?: "Unknown error"}") + } + } + } + } +} From e395ed6f3e9ba78e1ae0996121bccb0efddbc0eb Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Sat, 20 Jul 2024 01:21:10 +0900 Subject: [PATCH 29/49] =?UTF-8?q?[feat/#33]=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20API=20domain,=20data=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasource/SeatReviewDataSource.kt | 10 +++++---- .../remote/SeatReviewDataSourceImpl.kt | 17 +++++++++----- .../model/request/RequestSeatReviewDto.kt | 6 ----- .../data/remote/SeatReviewService.kt | 12 +++++----- .../repository/SeatReviewRepositoryImpl.kt | 22 ++++++++++--------- .../domain/entity/request/SeatReviewModel.kt | 2 -- .../domain/repository/SeatReviewRepository.kt | 10 +++++---- 7 files changed, 43 insertions(+), 36 deletions(-) diff --git a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt index a0ea79b3..a038d698 100644 --- a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt +++ b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt @@ -24,10 +24,6 @@ interface SeatReviewDataSource { sectionId: Int, ): List - suspend fun postSeatReviewData( - requestSeatReviewDto: RequestSeatReviewDto, - ) - suspend fun postImagePreSignedData( fileExtension: String, memberId: Int, @@ -37,4 +33,10 @@ interface SeatReviewDataSource { presignedUrl: String, image: ByteArray, ) + + suspend fun postSeatReviewData( + memberId: Int, + seatId: Int, + requestSeatReviewDto: RequestSeatReviewDto, + ) } diff --git a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt index 2be96346..9250f71b 100644 --- a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt +++ b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt @@ -40,10 +40,6 @@ class SeatReviewDataSourceImpl @Inject constructor( return seatReviewService.getSeatRange(stadiumId, sectionId) } - override suspend fun postSeatReviewData(requestSeatReviewDto: RequestSeatReviewDto) { - return seatReviewService.postSeatReview(requestSeatReviewDto) - } - override suspend fun postImagePreSignedData( fileExtension: String, memberId: Int, @@ -54,8 +50,19 @@ class SeatReviewDataSourceImpl @Inject constructor( ) } - override suspend fun putReviewImageData(presignedUrl: String, image: ByteArray) { + override suspend fun putReviewImageData( + presignedUrl: String, + image: ByteArray, + ) { val mediaType = "image/*".toMediaTypeOrNull() return seatReviewService.putProfileImage(presignedUrl, image.toRequestBody(mediaType)) } + + override suspend fun postSeatReviewData( + memberId: Int, + seatId: Int, + requestSeatReviewDto: RequestSeatReviewDto + ) { + return seatReviewService.postSeatReview(requestSeatReviewDto) + } } diff --git a/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt b/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt index ec0e02d4..a482443b 100644 --- a/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt +++ b/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt @@ -6,10 +6,6 @@ import kotlinx.serialization.Serializable @Serializable data class RequestSeatReviewDto( - @SerialName("memberId") - val memberId: Int, - @SerialName("seatId") - val seatId: Int, @SerialName("images") val images: List, @SerialName("dateTime") @@ -23,8 +19,6 @@ data class RequestSeatReviewDto( ) fun SeatReviewModel.toSeatReview() = RequestSeatReviewDto( - memberId = memberId, - seatId = seatId, images = images, dateTime = dateTime, good = good, diff --git a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt index bbb8594e..88c4afaf 100644 --- a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt +++ b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt @@ -36,11 +36,6 @@ interface SeatReviewService { @Path("sectionId") sectionId: Int, ): List - @POST("/api/v1/seats/{seatId}/members/{memberId}/reviews") - suspend fun postSeatReview( - @Body requestPostSignupDto: RequestSeatReviewDto, - ) - @POST("/api/v1/members/{memberId}/reviews/images") suspend fun postImagePreSignedUrl( @Body body: RequestPreSignedUrlDto, @@ -52,4 +47,11 @@ interface SeatReviewService { @Url preSignedUrl: String, @Body image: RequestBody, ) + + @POST("/api/v1/seats/{seatId}/members/{memberId}/reviews") + suspend fun postSeatReview( + @Path("memberId") memberId: Int, + @Path("seatId") seatId: Int, + @Body requestPostSignupDto: RequestSeatReviewDto, + ) } diff --git a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt index 18c91ae5..30bf29ed 100644 --- a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt +++ b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt @@ -52,16 +52,6 @@ class SeatReviewRepositoryImpl @Inject constructor( } } - override suspend fun postSeatReview( - seatReviewInfo: SeatReviewModel, - ): Result { - return runCatching { - seatReviewDataSource.postSeatReviewData( - seatReviewInfo.toSeatReview(), - ) - } - } - override suspend fun postReviewImagePresigned( fileExtension: String, memberId: Int, @@ -85,4 +75,16 @@ class SeatReviewRepositoryImpl @Inject constructor( ) } } + + override suspend fun postSeatReview( + memberId: Int, + seatId: Int, + seatReviewInfo: SeatReviewModel + ): Result { + return runCatching { + seatReviewDataSource.postSeatReviewData( + memberId,seatId,seatReviewInfo.toSeatReview() + ) + } + } } diff --git a/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt b/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt index 23aa51f2..2c1b6ef4 100644 --- a/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt +++ b/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt @@ -1,8 +1,6 @@ package com.depromeet.domain.entity.request data class SeatReviewModel( - val memberId: Int, - val seatId: Int, val images: List, val dateTime: String, val good: List, diff --git a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt index 21e40d31..046c65d1 100644 --- a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt +++ b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt @@ -24,10 +24,6 @@ interface SeatReviewRepository { sectionId: Int, ): Result> - suspend fun postSeatReview( - seatReviewInfo: SeatReviewModel, - ): Result - suspend fun postReviewImagePresigned( fileExtension: String, memberId: Int, @@ -37,4 +33,10 @@ interface SeatReviewRepository { presignedUrl: String, image: ByteArray, ): Result + + suspend fun postSeatReview( + memberId: Int, + seatId: Int, + seatReviewInfo: SeatReviewModel, + ): Result } From da64205ede265097af14f3548c4b82ae10773e6f Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Sat, 20 Jul 2024 01:32:06 +0900 Subject: [PATCH 30/49] =?UTF-8?q?[feat/#33]=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20API=20=EC=84=9C=EB=B2=84=20=ED=86=B5?= =?UTF-8?q?=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../remote/SeatReviewDataSourceImpl.kt | 8 +++-- .../model/request/RequestSeatReviewDto.kt | 2 +- .../repository/SeatReviewRepositoryImpl.kt | 6 ++-- .../presentation/seatReview/ReviewActivity.kt | 2 +- .../seatReview/ReviewViewModel.kt | 32 +++++++++++++++++++ 5 files changed, 44 insertions(+), 6 deletions(-) diff --git a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt index 9250f71b..d56a9b5a 100644 --- a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt +++ b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt @@ -61,8 +61,12 @@ class SeatReviewDataSourceImpl @Inject constructor( override suspend fun postSeatReviewData( memberId: Int, seatId: Int, - requestSeatReviewDto: RequestSeatReviewDto + requestSeatReviewDto: RequestSeatReviewDto, ) { - return seatReviewService.postSeatReview(requestSeatReviewDto) + return seatReviewService.postSeatReview( + memberId, + seatId, + requestSeatReviewDto, + ) } } diff --git a/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt b/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt index a482443b..b2ddb10c 100644 --- a/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt +++ b/data/src/main/java/com/depromeet/data/model/request/RequestSeatReviewDto.kt @@ -15,7 +15,7 @@ data class RequestSeatReviewDto( @SerialName("bad") val bad: List, @SerialName("content") - val content: String, + val content: String?, ) fun SeatReviewModel.toSeatReview() = RequestSeatReviewDto( diff --git a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt index 30bf29ed..663c9554 100644 --- a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt +++ b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt @@ -79,11 +79,13 @@ class SeatReviewRepositoryImpl @Inject constructor( override suspend fun postSeatReview( memberId: Int, seatId: Int, - seatReviewInfo: SeatReviewModel + seatReviewInfo: SeatReviewModel, ): Result { return runCatching { seatReviewDataSource.postSeatReviewData( - memberId,seatId,seatReviewInfo.toSeatReview() + memberId, + seatId, + seatReviewInfo.toSeatReview(), ) } } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index 976bb2f0..662f6def 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -37,7 +37,7 @@ class ReviewActivity : BaseActivity({ ActivityReviewBinding.inflate(it) }) { companion object { - private const val DATE_FORMAT = "yy.MM.dd" + private const val DATE_FORMAT = "yyyy.MM.dd" private const val FRAGMENT_RESULT_KEY = "requestKey" private const val SELECTED_IMAGES = "selected_images" private const val MAX_SELECTED_IMAGES = 3 diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index 4cf4693e..f9133591 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -3,6 +3,7 @@ package com.depromeet.presentation.seatReview import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.depromeet.core.state.UiState +import com.depromeet.domain.entity.request.SeatReviewModel import com.depromeet.domain.entity.response.seatReview.ResponsePresignedUrlModel import com.depromeet.domain.entity.response.seatReview.SeatBlockModel import com.depromeet.domain.entity.response.seatReview.SeatRangeModel @@ -84,6 +85,9 @@ class ReviewViewModel @Inject constructor( MutableStateFlow>(UiState.Loading) val getPreSignedUrl = _getPreSignedUrl.asStateFlow() + private val _postReviewState = MutableStateFlow>(UiState.Empty) + val postReviewState: StateFlow> = _postReviewState.asStateFlow() + fun setSelectedStadiumId(stadiumId: Int) { _selectedStadiumId.value = stadiumId } @@ -151,6 +155,7 @@ class ReviewViewModel @Inject constructor( } } } + fun getSeatBlock(stadiumId: Int, sectionId: Int) { viewModelScope.launch { _seatBlockState.value = UiState.Loading @@ -256,4 +261,31 @@ class ReviewViewModel @Inject constructor( } } } + + fun postSeatReview(memberId: Int) { + viewModelScope.launch { + val seatReviewModel = SeatReviewModel( + images = _selectedImages.value, + dateTime = _selectedDate.value, + good = _selectedGoodReview.value, + bad = _selectedBadReview.value, + content = _detailReviewText.value, + ) + + _postReviewState.value = UiState.Loading + seatReviewRepository.postSeatReview(memberId, _selectedStadiumId.value, seatReviewModel) + .onSuccess { + _postReviewState.value = UiState.Success(Unit) + Timber.d("POST REVIEW SUCCESS") + } + .onFailure { t -> + Timber.e("POST REVIEW FAILURE : $t") + if (t is HttpException) { + _postReviewState.value = UiState.Failure(t.code().toString()) + } else { + _postReviewState.value = UiState.Failure(t.message ?: "Unknown error") + } + } + } + } } From 0345b461b117bc3b550923c0a4196731abf64291 Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Sun, 21 Jul 2024 02:46:40 +0900 Subject: [PATCH 31/49] =?UTF-8?q?[feat/#33]=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EC=A0=80=EC=9E=A5=20=EB=B0=8F=20=EC=95=BC?= =?UTF-8?q?=EA=B5=AC=EC=9E=A5=20=EC=9D=B4=EB=A6=84=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20dto=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + .../seatReview/ResponseStadiumNameDto.kt | 3 +++ .../response/seatReview/StadiumNameModel.kt | 1 + .../presentation/seatReview/ReviewActivity.kt | 6 +++--- .../presentation/seatReview/ReviewViewModel.kt | 15 +++++++++++---- .../seatReview/dialog/SelectSeatDialog.kt | 18 ++++++++++-------- 6 files changed, 29 insertions(+), 15 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 53e91fb7..a7fe5641 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -94,6 +94,7 @@ dependencies { } implementation(MaterialDesignDependencies.materialDesign) + implementation("com.google.firebase:firebase-database-ktx:21.0.0") TestDependencies.run { testImplementation(jUnit) diff --git a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseStadiumNameDto.kt b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseStadiumNameDto.kt index 34dfeef3..d363e0b1 100644 --- a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseStadiumNameDto.kt +++ b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseStadiumNameDto.kt @@ -10,11 +10,14 @@ data class ResponseStadiumNameDto( val id: Int, @SerialName("name") val name: String, + @SerialName("isActive") + val isActive: Boolean, ) { fun toStadiumName(): StadiumNameModel { return StadiumNameModel( id = id, name = name, + isActive = isActive, ) } } diff --git a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/StadiumNameModel.kt b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/StadiumNameModel.kt index b821a521..c58e3769 100644 --- a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/StadiumNameModel.kt +++ b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/StadiumNameModel.kt @@ -3,4 +3,5 @@ package com.depromeet.domain.entity.response.seatReview data class StadiumNameModel( var id: Int, val name: String, + val isActive: Boolean ) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index 662f6def..137008c2 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -133,7 +133,7 @@ class ReviewActivity : BaseActivity({ if (firstStadium != null) { binding.tvStadiumName.text = firstStadium.name viewModel.getStadiumSection(firstStadium.id) - viewModel.setSelectedStadiumId(firstStadium.id) + viewModel.updateSelectedStadiumId(firstStadium.id) } observeReviewViewModel() } @@ -274,10 +274,10 @@ class ReviewActivity : BaseActivity({ val fileExtension = MimeTypeMap.getFileExtensionFromUrl(imageUri.toString()) // presigned URL 요청 - //TODO : MemberID 받기 + // TODO : MemberID 받기 viewModel.requestPresignedUrl(fileExtension, 1) - // presigned URL 응답을 관찰 후 업로드 후 이동 + // presigned URL 응답 관찰 -> 업로드 -> ReviewDoneActivity 이동 viewModel.getPreSignedUrl.asLiveData().observe(this) { state -> when (state) { is UiState.Success -> { diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index f9133591..1c9452d0 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -72,6 +72,9 @@ class ReviewViewModel @Inject constructor( private val _selectedStadiumId = MutableStateFlow(0) val selectedStadiumId: StateFlow = _selectedStadiumId.asStateFlow() + private val _selectedSectionId = MutableStateFlow(0) + val selectedSectionId: StateFlow = _selectedSectionId.asStateFlow() + private val _stadiumSectionState = MutableStateFlow>(UiState.Empty) val stadiumSectionState: StateFlow> = _stadiumSectionState @@ -88,10 +91,14 @@ class ReviewViewModel @Inject constructor( private val _postReviewState = MutableStateFlow>(UiState.Empty) val postReviewState: StateFlow> = _postReviewState.asStateFlow() - fun setSelectedStadiumId(stadiumId: Int) { + fun updateSelectedStadiumId(stadiumId: Int) { _selectedStadiumId.value = stadiumId } + fun updateSelectedSectionId(sectionId: Int) { + _selectedSectionId.value = sectionId + } + fun updateSelectedDate(date: String) { _selectedDate.value = date } @@ -148,6 +155,7 @@ class ReviewViewModel @Inject constructor( Timber.e("GET NAME FAILURE : ${t.message}", t) if (t is HttpException) { Timber.e("HTTP error code: ${t.code()}") + Timber.e("HTTP error response: ${t.response()?.errorBody()?.string()}") _stadiumNameState.value = UiState.Failure(t.code().toString()) } else { Timber.e("General error: ${t.message ?: "Unknown error"}") @@ -161,7 +169,7 @@ class ReviewViewModel @Inject constructor( _seatBlockState.value = UiState.Loading seatReviewRepository.getSeatBlock(stadiumId, sectionId) .onSuccess { blocks -> - Timber.e("GET BLOCK FAILURE : $blocks") + Timber.d("GET BLOCK SUCCESS : $blocks") if (blocks.isEmpty()) { _seatBlockState.value = UiState.Empty } else { @@ -212,8 +220,7 @@ class ReviewViewModel @Inject constructor( Timber.d("GET RANGE SUCCESS : $range") if (range.isEmpty()) { _seatRangeState.value = UiState.Empty - } else { - _seatRangeState.value = UiState.Success(range) + return@launch } } .onFailure { t -> diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index 1468ba58..037d5bbf 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -107,14 +107,12 @@ class SelectSeatDialog : BindingBottomSheetDialog { state.data.forEach { range -> updateIsExistedColumnUI(range.rowInfo) - updateColumnNUmberUI(range) + updateColumnNumberUI(range) } } - is UiState.Failure -> { toast("오류가 발생했습니다") } - is UiState.Loading -> {} is UiState.Empty -> {} else -> {} @@ -122,26 +120,30 @@ class SelectSeatDialog : BindingBottomSheetDialog matchingRowInfo.maxSeatNum)) { + if (selectedNumber < matchingRowInfo.minSeatNum.toString() || selectedNumber > matchingRowInfo.maxSeatNum.toString()) { updateNumberWarning("존재하지 않는 번이에요") } else { updateBackWarnings() } } } else { - if (selectedNumber == null || range.rowInfo.none { it.minSeatNum <= selectedNumber && it.maxSeatNum >= selectedNumber }) { + if (range.rowInfo.none { it.minSeatNum.toString() <= selectedNumber && it.maxSeatNum.toString() >= selectedNumber }) { updateBothWarnings("존재하지 않는 열과 번이에요") } else { updateBackWarnings() From bb30205513d983f7fb0c1c1f3caa583f18b2b239 Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Sun, 21 Jul 2024 14:53:37 +0900 Subject: [PATCH 32/49] =?UTF-8?q?[feat/#33]=20=EC=97=B4=EB=B2=88=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/seatReview/ReviewActivity.kt | 2 +- .../seatReview/ReviewViewModel.kt | 60 ++--- .../seatReview/dialog/SelectSeatDialog.kt | 215 +++++++++++------- 3 files changed, 165 insertions(+), 112 deletions(-) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index 137008c2..740bb6ae 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -275,7 +275,7 @@ class ReviewActivity : BaseActivity({ // presigned URL 요청 // TODO : MemberID 받기 - viewModel.requestPresignedUrl(fileExtension, 1) + viewModel.requestPreSignedUrl(fileExtension, 1) // presigned URL 응답 관찰 -> 업로드 -> ReviewDoneActivity 이동 viewModel.getPreSignedUrl.asLiveData().observe(this) { state -> diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index 1c9452d0..8835c8ad 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -1,5 +1,6 @@ package com.depromeet.presentation.seatReview +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.depromeet.core.state.UiState @@ -164,27 +165,6 @@ class ReviewViewModel @Inject constructor( } } - fun getSeatBlock(stadiumId: Int, sectionId: Int) { - viewModelScope.launch { - _seatBlockState.value = UiState.Loading - seatReviewRepository.getSeatBlock(stadiumId, sectionId) - .onSuccess { blocks -> - Timber.d("GET BLOCK SUCCESS : $blocks") - if (blocks.isEmpty()) { - _seatBlockState.value = UiState.Empty - } else { - _seatBlockState.value = UiState.Success(blocks) - } - } - .onFailure { t -> - if (t is HttpException) { - Timber.e("GET BLOCK FAILURE : $t") - _seatBlockState.value = UiState.Failure(t.code().toString()) - } - } - } - } - fun getStadiumSection(stadiumId: Int) { viewModelScope.launch { _stadiumSectionState.value = UiState.Loading @@ -210,6 +190,27 @@ class ReviewViewModel @Inject constructor( } } + fun getSeatBlock(stadiumId: Int, sectionId: Int) { + viewModelScope.launch { + _seatBlockState.value = UiState.Loading + seatReviewRepository.getSeatBlock(stadiumId, sectionId) + .onSuccess { blocks -> + Timber.d("GET BLOCK SUCCESS : $blocks") + if (blocks.isEmpty()) { + _seatBlockState.value = UiState.Empty + } else { + _seatBlockState.value = UiState.Success(blocks) + } + } + .onFailure { t -> + if (t is HttpException) { + Timber.e("GET BLOCK FAILURE : $t") + _seatBlockState.value = UiState.Failure(t.code().toString()) + } + } + } + } + fun getSeatRange(stadiumId: Int, sectionId: Int) { viewModelScope.launch { _seatRangeState.value = UiState.Loading @@ -220,20 +221,21 @@ class ReviewViewModel @Inject constructor( Timber.d("GET RANGE SUCCESS : $range") if (range.isEmpty()) { _seatRangeState.value = UiState.Empty - return@launch + } else { + _seatRangeState.value = UiState.Success(range) } - } - .onFailure { t -> - if (t is HttpException) { - Timber.e("GET RANGE FAILURE : $t") - _seatRangeState.value = UiState.Failure(t.code().toString()) - } + }.onFailure { t -> + if (t is HttpException) { + Timber.e("GET RANGE FAILURE : $t") + _seatRangeState.value = UiState.Failure(t.code().toString()) } + } } } + // presigned URL 요청 - fun requestPresignedUrl(fileExtension: String, memberId: Int) { + fun requestPreSignedUrl(fileExtension: String, memberId: Int) { viewModelScope.launch { _getPreSignedUrl.value = UiState.Loading seatReviewRepository.postReviewImagePresigned(fileExtension, memberId) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index 037d5bbf..bb159073 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -7,7 +7,6 @@ import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE import android.view.ViewTreeObserver -import android.widget.Adapter import android.widget.AdapterView import android.widget.ArrayAdapter import androidx.core.content.ContextCompat @@ -45,22 +44,15 @@ class SelectSeatDialog : BindingBottomSheetDialog when (state) { @@ -83,7 +75,7 @@ class SelectSeatDialog : BindingBottomSheetDialog when (state) { is UiState.Success -> { @@ -110,43 +102,133 @@ class SelectSeatDialog : BindingBottomSheetDialog { toast("오류가 발생했습니다") } - is UiState.Loading -> {} - is UiState.Empty -> {} + + is UiState.Loading -> { + } + + is UiState.Empty -> { + } + else -> {} } } } - // TODO : 에러 처리 다시 + private fun observeReviewViewModel() { + viewModel.selectedSeatZone.asLiveData().observe(this) { adapter.notifyDataSetChanged() } + viewModel.selectedBlock.asLiveData().observe(this) { block -> + updateCompleteBtnState() + } + viewModel.selectedColumn.asLiveData().observe(this) { column -> + updateCompleteBtnState() + } - private fun updateColumnNumberUI(range: SeatRangeModel) { - val selectedBlock = viewModel.selectedBlock.value - val selectedColumn = viewModel.selectedColumn.value - val selectedNumber = viewModel.selectedNumber.value + viewModel.selectedNumber.asLiveData().observe(this) { number -> + updateCompleteBtnState() + } + } - if (selectedColumn.isNullOrEmpty() || selectedNumber.isNullOrEmpty()) { - return + private fun setupEditTextListeners() { + binding.etColumn.addTextChangedListener { text: Editable? -> + val newColumn = text.toString() + viewModel.setSelectedColumn(newColumn) + viewModel.seatRangeState.value?.let { state -> + if (state is UiState.Success) { + state.data.forEach { range -> + updateColumnNumberUI(range) + } + } + } } - if (range.code == selectedBlock) { - val matchingRowInfo = range.rowInfo.find { it.number.toString() == selectedColumn } - if (matchingRowInfo == null) { - updateColumnWarning("존재하지 않는 열이에요") - } else { - if (selectedNumber < matchingRowInfo.minSeatNum.toString() || selectedNumber > matchingRowInfo.maxSeatNum.toString()) { - updateNumberWarning("존재하지 않는 번이에요") - } else { - updateBackWarnings() + binding.etNumber.addTextChangedListener { text: Editable? -> + val newNumber = text.toString() + viewModel.setSelectedNumber(newNumber) + viewModel.seatRangeState.value?.let { state -> + if (state is UiState.Success) { + state.data.forEach { range -> + updateColumnNumberUI(range) + } } } - } else { - if (range.rowInfo.none { it.minSeatNum.toString() <= selectedNumber && it.maxSeatNum.toString() >= selectedNumber }) { - updateBothWarnings("존재하지 않는 열과 번이에요") + } + } + + // TODO : 추후 코드 개선 예정 (ㅠㅠ) + private fun updateColumnNumberUI(range: SeatRangeModel) { + if (viewModel.selectedColumn.value.isEmpty() && viewModel.selectedNumber.value.isEmpty()) { + binding.etColumn.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) + binding.etNumber.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) + binding.tvNoneColumnWarning.visibility = GONE + } + if (range.code == viewModel.selectedBlock.value) { + val matchingRowInfo = + range.rowInfo.find { it.number.toString() == viewModel.selectedColumn.value } + if (matchingRowInfo == null && viewModel.selectedColumn.value.isNotEmpty()) { + with(binding) { + etColumn.setBackgroundResource(R.drawable.rect_gray50_fill_red1_line_12) + etNumber.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) + tvNoneColumnWarning.text = "존재하지 않는 열이에요" + tvNoneColumnWarning.visibility = VISIBLE + binding.tvCompleteBtn.setBackgroundResource(R.drawable.rect_gray200_fill_6) + binding.tvCompleteBtn.setTextColor( + ContextCompat.getColor( + requireContext(), + android.R.color.white, + ), + ) + } + if (viewModel.selectedNumber.value.isNotEmpty()) { + // 열과 번호 모두 오류인 경우 + with(binding) { + etColumn.setBackgroundResource(R.drawable.rect_gray50_fill_red1_line_12) + etNumber.setBackgroundResource(R.drawable.rect_gray50_fill_red1_line_12) + tvNoneColumnWarning.text = "존재하지 않는 열과 번이에요" + binding.tvCompleteBtn.setBackgroundResource(R.drawable.rect_gray200_fill_6) + binding.tvCompleteBtn.setTextColor( + ContextCompat.getColor( + requireContext(), + android.R.color.white, + ), + ) + } + } } else { - updateBackWarnings() + if (matchingRowInfo != null && viewModel.selectedNumber.value.isNotEmpty()) { + if (viewModel.selectedNumber.value < matchingRowInfo.minSeatNum.toString() || viewModel.selectedNumber.value > matchingRowInfo.maxSeatNum.toString()) { + with(binding) { + etColumn.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) + etNumber.setBackgroundResource(R.drawable.rect_gray50_fill_red1_line_12) + tvNoneColumnWarning.text = "존재하지 않는 번이에요" + tvNoneColumnWarning.visibility = VISIBLE + binding.tvCompleteBtn.setBackgroundResource(R.drawable.rect_gray200_fill_6) + binding.tvCompleteBtn.setTextColor( + ContextCompat.getColor( + requireContext(), + android.R.color.white, + ), + ) + } + } else { + with(binding) { + etColumn.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) + etNumber.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) + tvNoneColumnWarning.visibility = GONE + binding.tvCompleteBtn.isEnabled + binding.tvCompleteBtn.setBackgroundResource(R.drawable.rect_gray900_fill_6) + binding.tvCompleteBtn.setTextColor( + ContextCompat.getColor( + requireContext(), + android.R.color.white, + ), + ) + } + } + } } } } @@ -170,14 +252,19 @@ class SelectSeatDialog : BindingBottomSheetDialog) { - val blockCodes = blockItems.map { it.code } + val blockCodes = mutableListOf().apply { + add("") + addAll(blockItems.map { it.code }) + } + val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, blockCodes) adapter.setDropDownViewResource(R.layout.custom_spinner_dropdown_item) with(binding.spinnerBlock) { this.adapter = adapter - this.setSelection(Adapter.NO_SELECTION, false) + this.setSelection(0, false) + this.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected( parent: AdapterView<*>?, @@ -185,8 +272,16 @@ class SelectSeatDialog : BindingBottomSheetDialog?) { @@ -202,22 +297,11 @@ class SelectSeatDialog : BindingBottomSheetDialog - viewModel.setSelectedColumn(text.toString()) - } - - binding.etNumber.addTextChangedListener { text: Editable? -> - viewModel.setSelectedNumber(text.toString()) - } - } - private fun setupTransactionSelectSeat() { with(binding) { layoutSeatAgain.setOnSingleClickListener { @@ -277,37 +361,4 @@ class SelectSeatDialog : BindingBottomSheetDialog Date: Sun, 21 Jul 2024 15:38:01 +0900 Subject: [PATCH 33/49] =?UTF-8?q?[feat/#33]=20preSignedUrl=20URL=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20API=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/seatReview/ReviewActivity.kt | 72 +++++++++++++------ .../seatReview/ReviewViewModel.kt | 13 ++-- .../seatReview/dialog/SelectSeatDialog.kt | 36 +--------- 3 files changed, 59 insertions(+), 62 deletions(-) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index 740bb6ae..2d229427 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -1,5 +1,6 @@ package com.depromeet.presentation.seatReview +import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle @@ -27,7 +28,8 @@ import com.depromeet.presentation.seatReview.dialog.ImageUploadDialog import com.depromeet.presentation.seatReview.dialog.ReviewMySeatDialog import com.depromeet.presentation.seatReview.dialog.SelectSeatDialog import dagger.hilt.android.AndroidEntryPoint -import java.io.File +import java.io.FileNotFoundException +import java.io.InputStream import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale @@ -232,7 +234,6 @@ class ReviewActivity : BaseActivity({ } } for (index in selectedImageUris.size until selectedImage.size) { - val image = selectedImage[index] val layout = selectedImageLayout[index] layout.isVisible = false removeButtons[index].isVisible = false @@ -266,35 +267,60 @@ class ReviewActivity : BaseActivity({ } } + private fun getFileExtension(context: Context, uri: Uri): String { + val mimeType = context.contentResolver.getType(uri) + return mimeType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(it) } ?: "" + } + + private fun getFileInputStream(context: Context, uri: Uri): InputStream? { + return try { + context.contentResolver.openInputStream(uri) + } catch (e: FileNotFoundException) { + e.printStackTrace() + null + } + } + + private fun readImageData(context: Context, uri: Uri): ByteArray? { + val inputStream = getFileInputStream(context, uri) + return inputStream?.use { it.readBytes() } + } + private fun navigateToReviewDoneActivity() { binding.tvUploadBtn.setOnSingleClickListener { selectedImageUris.forEach { imageUriString -> val imageUri = Uri.parse(imageUriString) - val imageFile = File(imageUri.path!!) - val fileExtension = MimeTypeMap.getFileExtensionFromUrl(imageUri.toString()) + val fileExtension = getFileExtension(this, imageUri) + val imageData = readImageData(this, imageUri) + if (imageData != null) { + // TODO : MemberID 수정 + viewModel.requestPreSignedUrl(fileExtension, 1) + viewModel.getPreSignedUrl.asLiveData().observe(this) { state -> + when (state) { + is UiState.Success -> { + val presignedUrl = state.data.presignedUrl + viewModel.uploadImageToPreSignedUrl(presignedUrl, imageData) + Intent(this, ReviewDoneActivity::class.java).apply { + startActivity( + this, + ) + } + } - // presigned URL 요청 - // TODO : MemberID 받기 - viewModel.requestPreSignedUrl(fileExtension, 1) + is UiState.Failure -> { + Toast.makeText( + this, + "Presigned URL 요청 실패: $state", + Toast.LENGTH_SHORT, + ) + .show() + } - // presigned URL 응답 관찰 -> 업로드 -> ReviewDoneActivity 이동 - viewModel.getPreSignedUrl.asLiveData().observe(this) { state -> - when (state) { - is UiState.Success -> { - val presignedUrl = state.data.presignedUrl - val imageData = imageFile.readBytes() - // 이미지 업로드 - viewModel.uploadImageToPreSignedUrl(presignedUrl, imageData) - Intent(this, ReviewDoneActivity::class.java).apply { startActivity(this) } + else -> {} } - - is UiState.Failure -> { - Toast.makeText(this, "Presigned URL 요청 실패: $state", Toast.LENGTH_SHORT) - .show() - } - - else -> {} } + } else { + Toast.makeText(this, "파일을 읽을 수 없습니다.", Toast.LENGTH_SHORT).show() } } } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index 8835c8ad..baa03c3e 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -1,6 +1,5 @@ package com.depromeet.presentation.seatReview -import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.depromeet.core.state.UiState @@ -233,23 +232,25 @@ class ReviewViewModel @Inject constructor( } } - // presigned URL 요청 fun requestPreSignedUrl(fileExtension: String, memberId: Int) { viewModelScope.launch { _getPreSignedUrl.value = UiState.Loading seatReviewRepository.postReviewImagePresigned(fileExtension, memberId) .onSuccess { response -> - Timber.e("REQUEST PRESIGNED URL SUCCESS : $response") + Timber.d("REQUEST PRESIGNED URL SUCCESS : $response") _getPreSignedUrl.value = UiState.Success(response) } .onFailure { t -> Timber.e("REQUEST PRESIGNED URL FAILURE : $t") - if (t is HttpException) { - _getPreSignedUrl.value = UiState.Failure(t.code().toString()) + val errorMessage = if (t is HttpException) { + val errorBody = t.response()?.errorBody()?.string() + "HTTP Error ${t.code()}: ${errorBody ?: "Unknown error"}" } else { - _getPreSignedUrl.value = UiState.Failure(t.message ?: "Unknown error") + t.message ?: "Unknown error" } + + _getPreSignedUrl.value = UiState.Failure(errorMessage) } } } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index bb159073..5d369615 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -44,7 +44,6 @@ class SelectSeatDialog : BindingBottomSheetDialog - updateCompleteBtnState() - } - viewModel.selectedColumn.asLiveData().observe(this) { column -> - updateCompleteBtnState() - } - - viewModel.selectedNumber.asLiveData().observe(this) { number -> - updateCompleteBtnState() - } - } - private fun setupEditTextListeners() { binding.etColumn.addTextChangedListener { text: Editable? -> val newColumn = text.toString() @@ -218,7 +203,7 @@ class SelectSeatDialog : BindingBottomSheetDialog Date: Sun, 21 Jul 2024 16:37:23 +0900 Subject: [PATCH 34/49] =?UTF-8?q?[fix/#33]=20=EC=A7=81=EA=B4=80=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0=20=EB=93=B1=EB=A1=9D=20API=20request=20membe?= =?UTF-8?q?rId=20=ED=95=84=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasource/SeatReviewDataSource.kt | 1 - .../datasource/remote/SeatReviewDataSourceImpl.kt | 2 -- .../depromeet/data/remote/SeatReviewService.kt | 1 - .../domain/repository/SeatReviewRepository.kt | 1 - .../presentation/seatReview/ReviewActivity.kt | 11 +++-------- .../presentation/seatReview/ReviewViewModel.kt | 15 +++++++++++---- 6 files changed, 14 insertions(+), 17 deletions(-) diff --git a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt index a038d698..6d936614 100644 --- a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt +++ b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt @@ -35,7 +35,6 @@ interface SeatReviewDataSource { ) suspend fun postSeatReviewData( - memberId: Int, seatId: Int, requestSeatReviewDto: RequestSeatReviewDto, ) diff --git a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt index d56a9b5a..42ed1f70 100644 --- a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt +++ b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt @@ -59,12 +59,10 @@ class SeatReviewDataSourceImpl @Inject constructor( } override suspend fun postSeatReviewData( - memberId: Int, seatId: Int, requestSeatReviewDto: RequestSeatReviewDto, ) { return seatReviewService.postSeatReview( - memberId, seatId, requestSeatReviewDto, ) diff --git a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt index 88c4afaf..b9d3ea79 100644 --- a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt +++ b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt @@ -50,7 +50,6 @@ interface SeatReviewService { @POST("/api/v1/seats/{seatId}/members/{memberId}/reviews") suspend fun postSeatReview( - @Path("memberId") memberId: Int, @Path("seatId") seatId: Int, @Body requestPostSignupDto: RequestSeatReviewDto, ) diff --git a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt index 046c65d1..c1fa49c7 100644 --- a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt +++ b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt @@ -35,7 +35,6 @@ interface SeatReviewRepository { ): Result suspend fun postSeatReview( - memberId: Int, seatId: Int, seatReviewInfo: SeatReviewModel, ): Result diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index 2d229427..2c3f220a 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -10,7 +10,6 @@ import android.view.View.VISIBLE import android.webkit.MimeTypeMap import android.widget.FrameLayout import android.widget.ImageView -import android.widget.Toast import androidx.activity.viewModels import androidx.core.content.ContextCompat import androidx.core.view.isVisible @@ -294,6 +293,7 @@ class ReviewActivity : BaseActivity({ val imageData = readImageData(this, imageUri) if (imageData != null) { // TODO : MemberID 수정 + // TODO : postSeatReview() 호출 -> 이미지 업로드 완료 viewModel.requestPreSignedUrl(fileExtension, 1) viewModel.getPreSignedUrl.asLiveData().observe(this) { state -> when (state) { @@ -308,19 +308,14 @@ class ReviewActivity : BaseActivity({ } is UiState.Failure -> { - Toast.makeText( - this, - "Presigned URL 요청 실패: $state", - Toast.LENGTH_SHORT, - ) - .show() + toast("Presigned URL 요청 실패: $state") } else -> {} } } } else { - Toast.makeText(this, "파일을 읽을 수 없습니다.", Toast.LENGTH_SHORT).show() + toast("파일을 읽을 수 없습니다.") } } } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index baa03c3e..1d5d7832 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -11,6 +11,7 @@ import com.depromeet.domain.entity.response.seatReview.StadiumNameModel import com.depromeet.domain.entity.response.seatReview.StadiumSectionModel import com.depromeet.domain.repository.SeatReviewRepository import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -256,11 +257,16 @@ class ReviewViewModel @Inject constructor( } // 이미지 업로드 - fun uploadImageToPreSignedUrl(presignedUrl: String, image: ByteArray) { + fun uploadImageToPreSignedUrl( + presignedUrl: String, + image: ByteArray, + ): CompletableDeferred { + val deferred = CompletableDeferred() viewModelScope.launch { val result = seatReviewRepository.putImagePreSignedUrl(presignedUrl, image) result.onSuccess { Timber.d("UPLOAD IMAGE SUCCESS") + deferred.complete(true) }.onFailure { t -> Timber.e("UPLOAD IMAGE FAILURE : $t") if (t is HttpException) { @@ -268,11 +274,13 @@ class ReviewViewModel @Inject constructor( } else { Timber.e("General error: ${t.message ?: "Unknown error"}") } + deferred.complete(false) } } + return deferred } - fun postSeatReview(memberId: Int) { + fun postSeatReview() { viewModelScope.launch { val seatReviewModel = SeatReviewModel( images = _selectedImages.value, @@ -281,9 +289,8 @@ class ReviewViewModel @Inject constructor( bad = _selectedBadReview.value, content = _detailReviewText.value, ) - _postReviewState.value = UiState.Loading - seatReviewRepository.postSeatReview(memberId, _selectedStadiumId.value, seatReviewModel) + seatReviewRepository.postSeatReview(_selectedStadiumId.value, seatReviewModel) .onSuccess { _postReviewState.value = UiState.Success(Unit) Timber.d("POST REVIEW SUCCESS") From d602126cfef7583929b6565c104b476c0a3fc56f Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Sun, 21 Jul 2024 20:21:16 +0900 Subject: [PATCH 35/49] =?UTF-8?q?[fix/#33]=20=EC=97=B4/=EB=B2=88=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=B2=98=EB=A6=AC=20dto=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../seatReview/ResponseSeatRangeDto.kt | 9 +-- .../repository/SeatReviewRepositoryImpl.kt | 2 - .../response/seatReview/SeatRangeModel.kt | 3 +- .../seatReview/dialog/SelectSeatDialog.kt | 68 ++++++++++--------- 4 files changed, 39 insertions(+), 43 deletions(-) diff --git a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatRangeDto.kt b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatRangeDto.kt index 83e6c23a..07fabd11 100644 --- a/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatRangeDto.kt +++ b/data/src/main/java/com/depromeet/data/model/response/seatReview/ResponseSeatRangeDto.kt @@ -19,17 +19,14 @@ data class ResponseSeatRangeDto( val id: Int, @SerialName("number") val number: Int, - @SerialName("minSeatNum") - val minSeatNum: Int, - @SerialName("maxSeatNum") - val maxSeatNum: Int, + @SerialName("seatNumList") + val seatNumList: List, ) { fun toRowInfo(): SeatRangeModel.RowInfo { return SeatRangeModel.RowInfo( id = id, number = number, - minSeatNum = minSeatNum, - maxSeatNum = maxSeatNum, + seatNumList = seatNumList, ) } } diff --git a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt index 663c9554..63cc9dca 100644 --- a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt +++ b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt @@ -77,13 +77,11 @@ class SeatReviewRepositoryImpl @Inject constructor( } override suspend fun postSeatReview( - memberId: Int, seatId: Int, seatReviewInfo: SeatReviewModel, ): Result { return runCatching { seatReviewDataSource.postSeatReviewData( - memberId, seatId, seatReviewInfo.toSeatReview(), ) diff --git a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatRangeModel.kt b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatRangeModel.kt index 15faa4af..92c47270 100644 --- a/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatRangeModel.kt +++ b/domain/src/main/java/com/depromeet/domain/entity/response/seatReview/SeatRangeModel.kt @@ -8,7 +8,6 @@ data class SeatRangeModel( data class RowInfo( val id: Int, val number: Int, - val minSeatNum: Int, - val maxSeatNum: Int, + val seatNumList: List, ) } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index 5d369615..40ae8788 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -2,6 +2,7 @@ package com.depromeet.presentation.seatReview.dialog import android.os.Bundle import android.text.Editable +import android.util.Log import android.view.View import android.view.View.GONE import android.view.View.INVISIBLE @@ -143,7 +144,7 @@ class SelectSeatDialog : BindingBottomSheetDialog matchingRowInfo.maxSeatNum.toString()) { - with(binding) { - etColumn.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) - etNumber.setBackgroundResource(R.drawable.rect_gray50_fill_red1_line_12) - tvNoneColumnWarning.text = "존재하지 않는 번이에요" - tvNoneColumnWarning.visibility = VISIBLE - binding.tvCompleteBtn.setBackgroundResource(R.drawable.rect_gray200_fill_6) - binding.tvCompleteBtn.setTextColor( - ContextCompat.getColor( - requireContext(), - android.R.color.white, - ), - ) - } - } else { - with(binding) { - etColumn.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) - etNumber.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) - tvNoneColumnWarning.visibility = GONE - binding.tvCompleteBtn.isEnabled = true - binding.tvCompleteBtn.setBackgroundResource(R.drawable.rect_gray900_fill_6) - binding.tvCompleteBtn.setTextColor( - ContextCompat.getColor( - requireContext(), - android.R.color.white, - ), - ) - } + } else if (matchingRowInfo != null && viewModel.selectedNumber.value.isNotEmpty() && viewModel.selectedNumber.value.isNotBlank()) { + if (!matchingRowInfo.seatNumList.contains(viewModel.selectedNumber.value.toInt())) { + with(binding) { + etColumn.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) + etNumber.setBackgroundResource(R.drawable.rect_gray50_fill_red1_line_12) + tvNoneColumnWarning.text = "존재하지 않는 번이에요" + tvNoneColumnWarning.visibility = VISIBLE + binding.tvCompleteBtn.setBackgroundResource(R.drawable.rect_gray200_fill_6) + binding.tvCompleteBtn.setTextColor( + ContextCompat.getColor( + requireContext(), + android.R.color.white, + ), + ) + binding.tvCompleteBtn.isEnabled = false + } + } else { + with(binding) { + etColumn.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) + etNumber.setBackgroundResource(R.drawable.rect_gray50_fill_gray200_line_12) + tvNoneColumnWarning.visibility = GONE + binding.tvCompleteBtn.isEnabled = true + binding.tvCompleteBtn.setBackgroundResource(R.drawable.rect_gray900_fill_6) + binding.tvCompleteBtn.setTextColor( + ContextCompat.getColor( + requireContext(), + android.R.color.white, + ), + ) } } } @@ -331,4 +333,4 @@ class SelectSeatDialog : BindingBottomSheetDialog Date: Sun, 21 Jul 2024 20:27:26 +0900 Subject: [PATCH 36/49] =?UTF-8?q?[fix/#33]=20=EC=8B=9C=EC=95=BC=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0=20=EA=B0=9C=EC=88=98=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../seatReview/dialog/ReviewMySeatDialog.kt | 24 ++++++++++++++----- .../seatReview/dialog/SelectSeatDialog.kt | 1 - 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/ReviewMySeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/ReviewMySeatDialog.kt index 8c4a3228..258d041c 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/ReviewMySeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/ReviewMySeatDialog.kt @@ -58,10 +58,16 @@ class ReviewMySeatDialog : BindingBottomSheetDialog Date: Sun, 21 Jul 2024 20:31:42 +0900 Subject: [PATCH 37/49] =?UTF-8?q?[fix/#33]=20=EC=A2=8C=EC=84=9D=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=20=EC=97=B4,=20=EB=B2=88=20maxlength=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/res/layout/fragment_select_seat_bottom_sheet.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/presentation/src/main/res/layout/fragment_select_seat_bottom_sheet.xml b/presentation/src/main/res/layout/fragment_select_seat_bottom_sheet.xml index 17fe1796..54285382 100644 --- a/presentation/src/main/res/layout/fragment_select_seat_bottom_sheet.xml +++ b/presentation/src/main/res/layout/fragment_select_seat_bottom_sheet.xml @@ -237,6 +237,7 @@ android:paddingVertical="13dp" android:paddingStart="16dp" android:hint="ex)12" + android:maxLength="4" android:paddingEnd="64dp" android:textColor="#121212" app:layout_constraintTop_toTopOf="parent" @@ -263,6 +264,7 @@ android:layout_height="wrap_content" android:background="@drawable/rect_gray50_fill_gray200_line_12" android:hint="ex)12" + android:maxLength="4" android:layout_marginEnd="4dp" android:paddingVertical="13dp" android:paddingStart="16dp" From 0c76b0e64e24f4102ca6251970129edf493f510b Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Sun, 21 Jul 2024 21:22:30 +0900 Subject: [PATCH 38/49] =?UTF-8?q?[fix/#33]=20=EC=9E=84=EC=8B=9C=20token=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=EC=85=89=ED=84=B0=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/depromeet/presentation/seatReview/ReviewActivity.kt | 6 +++--- presentation/src/main/res/layout/activity_review.xml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index 2c3f220a..bc4beade 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -292,14 +292,14 @@ class ReviewActivity : BaseActivity({ val fileExtension = getFileExtension(this, imageUri) val imageData = readImageData(this, imageUri) if (imageData != null) { - // TODO : MemberID 수정 - // TODO : postSeatReview() 호출 -> 이미지 업로드 완료 + // TODO : viewModel.uploadImageToPreSignedUrl 사진 업로드 서버 통신 + // TODO : postSeatReview() 시야 등록 후기 request -> ReviewDoneActivity 이동 viewModel.requestPreSignedUrl(fileExtension, 1) viewModel.getPreSignedUrl.asLiveData().observe(this) { state -> when (state) { is UiState.Success -> { val presignedUrl = state.data.presignedUrl - viewModel.uploadImageToPreSignedUrl(presignedUrl, imageData) + // viewModel.uploadImageToPreSignedUrl(presignedUrl, imageData) Intent(this, ReviewDoneActivity::class.java).apply { startActivity( this, diff --git a/presentation/src/main/res/layout/activity_review.xml b/presentation/src/main/res/layout/activity_review.xml index c450e073..2417d54a 100644 --- a/presentation/src/main/res/layout/activity_review.xml +++ b/presentation/src/main/res/layout/activity_review.xml @@ -15,7 +15,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> - Date: Sun, 21 Jul 2024 21:43:52 +0900 Subject: [PATCH 39/49] [fix/#33] manifest exported true -> false --- app/src/main/AndroidManifest.xml | 4 ++-- "\353\254\264\354\240\234" | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2e3b7003..80829c2a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -73,12 +73,12 @@ android:screenOrientation="portrait" /> Date: Mon, 22 Jul 2024 00:25:28 +0900 Subject: [PATCH 40/49] =?UTF-8?q?[fix/#33]=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20dependency=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 - buildSrc/build/kotlin/buildSrcjar-classes.txt | 2 +- .../compileKotlin/cacheable/last-build.bin | Bin 18 -> 18 bytes .../local-state/build-history.bin | Bin 31 -> 31 bytes buildSrc/build/libs/buildSrc.jar | Bin 12253 -> 12391 bytes 5 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a7fe5641..53e91fb7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -94,7 +94,6 @@ dependencies { } implementation(MaterialDesignDependencies.materialDesign) - implementation("com.google.firebase:firebase-database-ktx:21.0.0") TestDependencies.run { testImplementation(jUnit) diff --git a/buildSrc/build/kotlin/buildSrcjar-classes.txt b/buildSrc/build/kotlin/buildSrcjar-classes.txt index c98318a7..d7dd3381 100644 --- a/buildSrc/build/kotlin/buildSrcjar-classes.txt +++ b/buildSrc/build/kotlin/buildSrcjar-classes.txt @@ -1 +1 @@ -/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/AndroidXDependencies.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/ClassPathPlugins.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/ComposeDependency.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/Constants.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/DependenciesKt.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/FirebaseDependencies.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/KaptDependencies.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/KotlinDependencies.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/MaterialDesignDependencies.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/TestDependencies.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/ThirdPartyDependencies.class:/Users/ssongsik/AndroidStudioProjects/SPOT-Android/buildSrc/build/classes/kotlin/main/Versions.class \ No newline at end of file +/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/AndroidXDependencies.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/ClassPathPlugins.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/ComposeDependency.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/Constants.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/DependenciesKt.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/FirebaseDependencies.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/KaptDependencies.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/KotlinDependencies.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/MaterialDesignDependencies.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/TestDependencies.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/ThirdPartyDependencies.class:/Users/minju1459/DepromeetAndroid/buildSrc/build/classes/kotlin/main/Versions.class \ No newline at end of file diff --git a/buildSrc/build/kotlin/compileKotlin/cacheable/last-build.bin b/buildSrc/build/kotlin/compileKotlin/cacheable/last-build.bin index 8ded29aaf285f252b08cee91fc2af6cf135559a1..4b7f20cc502b86ca394680d9df8d93134930c869 100644 GIT binary patch literal 18 YcmZ4UmVvdLhk=1{!qxNkoeV$#065|W9{>OV literal 18 YcmZ4UmVvdLhk=1{!Zw?GTN!`=062REH~;_u diff --git a/buildSrc/build/kotlin/compileKotlin/local-state/build-history.bin b/buildSrc/build/kotlin/compileKotlin/local-state/build-history.bin index b4355cc7c5393cae3179503bd71ce8bdb8111504..ebb3f9b34b9a512525430cf5e2e916d8bdf53142 100644 GIT binary patch literal 31 dcmZ4UmVvcgk^ur385kHRTs?2!3FR{|003n11uXyo literal 31 dcmZ4UmVvcgk^ur385kHRY_qwy70PE|003nN1x5e> diff --git a/buildSrc/build/libs/buildSrc.jar b/buildSrc/build/libs/buildSrc.jar index bfe6d490c775f4d3e281d48f17c5221a7cf1cd1d..6d91849a8b0902b0066d03587c2408d6d286097c 100644 GIT binary patch delta 7176 zcmZX31yodR7w*svLk~lD!w5)+ba%%fLkLI@DWD^bGz=}>DIh5&3?+z&bcd9LfV3cR z@w@B)|MlJbu65RX;@SH-`>eBeET2mPP%0A}5+k04fW2^UnE^q*dgUEcsdX-$@a?g+jgwQG5-jHwmksP^{r08x* z0KX9paDMAr05S2pN}ImeR}UkZ%Sf>nZ8{jGj8UYRm~$Xfz`Sv+xanXbWV2P4P{=ge zFT|`Fq!YKboY_BI)D_K1FH0pfI|MqteJw2TXCM+o*6LV#?=ix_Z{b2b!*?73h(GNK z=g3}tmuea(tKo(}52n*eIG{gdtO!#EQsz?8j$hx8J%@2?d-|*?!H<6&M<_j|=Ogd7 zESP@1-4ME8sv6qU_o0W1FwHP?vvA*$>q5l!0lP%Z;StQx)VEFf%WaHBzx5u0e)ntDJ`|lWCoKJ#^`L@AwENiikH_IEQR=x~H#O>p02x@xy zk8DkzG<|yi^{~lC?ANa(2YA9)$`!jy^W#)B{#3G(9){qnvZZPUO){cGp=6Fx;= zQx$0G8VP7TTBizV0PwE29_8zffAJfS>ZsW!3BLbfL+82VmIOR7#v8-}W@0$Sr}=>U z-4@0bq*^(LtI0cKIx>QX0OOuA^t~AgPyUP?NE_S2@Ufra$3GM)-Gl?5B|`EYa`cED zJL+Xrhjp;tafaFcmbUcS3mmS5gTabXQN3-J4L_umjvg%x-0OGThRAJ&TY+0;KQU}} zT2UptOCWCT=L@o;6nyL{2lI3+I4MVa_%!8v9(qcL>YtlQHF>Pn!=Zv( zs#RZgDr6kNK_XT8<>apc$#r&BK$8(GCr6G9)8b#C3Z+-h* zA09am#=mS%|9z~}?<%A#B4XT}{cCTM#3K%u{n{q71PqNq{4l5$FmD&p%_wRYd7823 zQ)!(6_Nmm*$o8o;&!GA-p#Zfg(Tp{&{NRkIu371<-`KqH+}T3q*1~skt9`0Wlh?<& zRG@aEbh^DsC1YmKphKW`vh=*YNj^h#Z-N85H(vUsy-7KvWp6?h`renjOw&s_@XDKw zS=6kYxh2;a(HX$jN0Gad_|DodowojuQg6B86wO(=mCum0qsMOn*?;gr{henL}=Y3B?4~?dM zm|P3rl0h4|mOLsb3ms)-GURD%3K&)=I`FC~S}bf`5R@|l4d?iaOby!j`$=&Vj`1>hliKsyK7AQ=Lt;G=5!FExCx=?eiLtUu5enerjT{NMHH4c(c#F_`u zs7Q7{E!Yryy3t&8zY3+FhOHxopNZ{Pu7l2g0j`7Zb|9pg25fhzCZDw-4#mMHT3KJy z564HGvZtHQvA-!qwe^=#_?g>I;&<>g>`?k?+Y0iys@Mwh9<=vc8_yBF>3UcHmMq8+ z*2;F_hvhp=2m&%L0J=%nr*vJ+-&<@NAYX+m&@Mk=DFLB`LUw?%QH*UJ;3P? zCk&^^-zAyKc0n_3*8(E5)y|2NHs#-jt5|KMW$V zuj3@j_2JJ^Bm*Jck&L^)IV+F!osROus^qV1fUu>YV~| z=9pu41x%RfuPsTGNw@%}Bx(^Tj#3Uk#VrSZ$EC-;0X_q|Gabmc#nfXIqU~N0>M;(7HGZpgMxgyG zrZukPJD}tJ4P*R;7h^?8T_R3|r7pQV#oe<&%XD~}rTnkZ5oITW3X*7!=oFF4$7mBy z0om?RLOvZXer1@e0pI6W*N}DnIAq#{y<&*eU#_1^slpb&~Ub;A&J@SwPNDmoC zhCHVHlrnyj_J^tK{TUs*FM3>|zv;o7INnZ;Z8v9ANsbU)#bEOnBb_$U2rX@DWHhro zYYe91)B)EQNb3D;_*YPk!;qVq+p(U<7C!A;-iYa0b)Vitruw%qmLr_B(RU)HPq=g) z!*_nGfVK>+kF^UA+eM5^HfKTCrx~;=md2_IdJCjg_G zth)}yqiOykZ+&vO_w^Umwl6`t#IEiU>S6<#=ch@DIdpu-d_h_Dh^yTl@uRK%<*sD? zy!dV@nI%Hfo6Xk+bH-e+C^f7iYD9mWX?C(M&7u?dyQJ-n@OxkqE9$zemR3i?T)Pfw zjFA!^y3WS%tq}*)M?T*kKWCLTq;Q8CFxFi=;LE(&8dm5sN$D+P(w+0@W?d@6H8RT& zxrls9LieEz#p2tdhxj!jlv#e4FaaG*#$U{B$#FJ16D!VCV7yjOF3qtkh^KX3k6dg^ zrgCqJr*&C(Z}!^YU&{R`uJh5XIJ=yOyajz=ZuC)2F?N?H2X>5E$)kCSg0KYT+psZ$ zQ%GfB`Ki>^jUC&^qMZsfPigl!XX(OB*5APmkEXalJC72Z)2HpUBBLM&Ui_O-s3*v~d003bEBtMW1VQEOAN-?BMQcuVW z*Vh9U|9YkV>;kw_iUZ{$K%G3pCc|7C1pB&*9KGi_bs4RLbKryk zL5oFh*}p1|o-vrVAk2)36c|IMSS~-bYZn}yq@BY+uZ_Uhu%ss+7dBIRjCT8&J;)%` z)z(82M3EPe*sba2Quz7`gf8+77{iJ=mZr4!tR=GwGxbJD@#Ryls2>9k z&b64Hh$^SKlN~A|vufwdv(Cx=!r9P^?7^&{1q|*#p~s7SdVEavMT)N9B!fP@8Dja< z#T6ufVgluo&M{55M=NKzFKu+FULg3sc~FzL#TLS>|5U%~10NVufWa)M!h7nOr3y5&{=t&FbY8ziC%vqFSQ&~78J|}x0Q*9hk|WJZ z(I(~cULbP&EiohChInFdY)Lu_b|~+`o9CwJ!Gfh26WW^7K?@qMt?LsGIT!W{Pq;S#G@ll3w08Skj>V--w%Jg zH+Pm38&KhEQI3wFq|5L(2=c?UoY~Wn@@PKh@NMI&IluoQ)Lh?sg$_VR@L={Jpdj z_z0m*V>dXN+|W&~(sDn4GwWiZesowMl9Xzo?hN71Qg`O!E=5ihmWbq}5_6U&mxyGi zA_h|?0t_TmQWH2z-;{`Cr6#bKZamt(^{C{|ZIb?0DZRp~Q||G0NCW*rhX)QtFp><1{l< zJ&jQ{;WgpPcqHtjnr)mgWfTnyUO8J%Mm^JdLO3^`Fc}|S23znAIt2%m7d8wBtqRp~jrsikZ@3usooAKZuvJBVJ*JdDWZbo5QhFh$Ekv6J=#?i_C zvVPdF1S2p3fEzpj01QCp3eX`UjK3N#s!+V*?V%b(Q+)lzX@Y~9xv=>U2D%cndM{fo z`9@PFnSoAh4Jd@~MUxA)d(KJJ*_2CJ)M%41edNlus_(T)%Fo(V=bu^2t*e(Si{;B6 z{`c26t#W`hygzC-@V?5RcgCx$LY8HQV)T7N=47}bqmO=$=V<4u9GW2!U!f zmI(v-WT3Wt8$lw)B(+{9Hax-;)>zWacfmaCcIbQUu$w||z3`6~?`HhOs!Dg9+t4dv zn5uY7m2K9n{;nsAi5A44^@qG9w$fw$rnjbB-0Z4K)&sYBUuoDBO$fdpl=Q=htS zT(~v9@P%`kfa6UL14fL6yrl_@>a$}`VCOQ6XB{=8Lg1WZS4&@Q8TWIlNkrs+ko~;) z7PLmwrOmxhv{}+kL`#=iNpv)PlJ5o!k-)1(nW-}>lB zt}3neeg3pWT=r6H?h~?!>?J;&}#k z`&uVB`!pK(ATo12X0dc`%93B|w;#Q2YIK$IWT9mxt5mp;xT)ZLM`a1vw$j*6ut#l8 zcwwY?eraG-lkfqdEOBnQbtjw_|IXJZ&6^T8?n2tSh-3Cnkl@g6()Yc>N0(*3&sX|2 z4u*b4!qSsIo*i<9TRmaF8Sdb=n>-=B{0lu8_2j`7oiEV}aiF>unjuX-=-gVdWX*Ks zB**x9PiphkY5^lhVF}-Mh7?yFH7fXdfvuIt56aFnK+~KEo@U&tF4*B zpXTV6rP8pM+6#;s#_+`H9}G>)t?~*}?bpPW>i0 zfo#sPK0m_Co?=ef5;@6Wfk=|OMpKpWdTArqbVWKvBHKkRi4oUB>pB^NL0GJHJSN{n z8%)$S;IZkixAH3dtPXO~i$gJYxkw)n`;vS)-!7CI2cj40F>>riGjdF(9bg=Z@5%^( z=|#-qK)x^ytVvW8tc-*+3QrhvpG$4|z=h393_{$jTY<>4Y`TQ>;w4Kpp^yg-vY0UMoF! z6?L3^{9O%;;rF2wtYa;raOZOiB$AJc25(zWJu1X$v<8*X1vdY5a(ld2hEOh8RD=yC(o8shCvZSYj)b0S-j)f)H9 zB26c?vAOC0XgbJ~yq!+7a@LL%&0iP&dfqPD8YlF8{MxVIF>smE+E!PXO71pkTcZ1M zw&v(BgOi`T-&$Y?))^I35_@NMl5Qh)kjbg%zNtr|EK#rnM3!_?2rq`JI&`j_eayo* z)U4UzvAB^%bMObR7Is`zsv4UwC^`I z&dRT6Sj?3h5ie0wCwMg*-9gSk!p=dV;e$E=;Wu0#2hbf-TKn5J?H&5vA?Ca;Na~QB z&j03w>X1~n@R)`N(h;Ua>1{k}>V=n~K9I)q?)w69=AiG=oV-fkjM=!FOWb7|cl=wq zf0xf@$fS~b!iPYHg9`vWG>QRA$VyI1gtj5AD)1#Mo^x_Ul9(4RXc1&*SyL;>WJBOO z6e(^3ObUSN7+2ua)M7uXUnuoc6wf2H`M!18+{1Cm;782TBY(HMdiauk%ZlTQfY1sG@m@VkgHGqR4X9 z_hoNEIp=47BSm#7)Aa6a$2RY0@WSAO5u-ex=ckEy0o4bA%}XtMl!=-{U%iIBC`~8W zgQktEi55AGYC0_U&aCmySXUB(0*?q8hML)U;=zhV{+UnS6Vcm`Q}J0!8pu4uE$4ii z|EY=InlK+y^1<-j%6$1VTywb02yrvO%4*jPr6!pFaF80w+x272qGc@Yn5{JyTAY+S ztm6W*g^U$^VBN1>iXAe+ksE~sVy`ACUrR<;6O!++3XS-3&o7XkfctI}S%o|DoHTxI zM|*8Z50))@R{dlO8YSM~=sUYNWo6};bgC-hOo(m;Z3}2o1o5;rOtYT2AtG^P?1xNy zYYjwK{b{QL0*iJqPv<)$I|zlLE&O5;%^vebbIn5X>U-|SbI@%gKT*qZ$S~wwziies zwI-Hax?T7Zbm;rSa>8Wbi9Ov)YO{%iZdB&3Of=M*K&QD@hedd4W%+vq4Ut5o%S5TI znp7kC)RVlpyc{afpr4g80w=%GYO&08CGb`3kl#2_8BLLBS6j1oRv2in+3g^(5v$k3R* zcwd!zHL@-*^eO;IQabn}KQ5}N`&Z%AdT(@@>Y1Kwsw3m8H_yXCZxBxFM&UEpW0o;x zER3(>2hqz7`Q~)1MM|KLWn$Y6Jf40z{cvGZQkhdiua0!iu zCDv#8ld`#_ZL@wI?hz#?VYAJoTR?h%Kv=Km(9pgxgI<9jHJ;Fr+BlbbhGj8UO;Q4O zatZ^U8jRvzL}Wns-Z72ca6kuP16?)ucMun&h;mwcW1Q~Cx^6HDW`|j2hZ?QvR_5n zf6U{cnI%feIJ;pZ4@}>DoyVCqRj#!`&(Nu?@bQlL)Fb)2i*^I?73AT7>#8R!$E<9D zGekL|SfPg{#wA4{rnag?NnUcK(8HA5%W+atuHVPE8#Px1_E^JX%S*n)Uks2Bq>)P9 z!R`)=H!b<7N-vjWUH(#HI%w#(*TV2;;4+Ms3|!)hX49q1TBXcMo|m8af5We@98k8D5dUvm_)DpWNR# zhScSgB>tNi|8JOt%-|DX`gFYLVSskW<+%V1^%yF0szqc3#bc3J_0iQ1v37no-bvy<$M6}Jsgbx z0;VJT`33&L{4F5fJg}ZUydL$xFxN;@0givHlN6vuYeSj{aQ-9l@8th~A|)ZQ$Q%J~ zj=#*hGVW6j4_YT4v{L;K@?eVs698c3;O&EaD!}lMaza5`^hFk=I}7N)2L4S3{z@nR jf6{^MNH;;Ye?Hg$>$?BLQW8SWg=`Syz}Ek3HsJpNOjarm delta 7052 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 D6t?w- From 7e3712ccda531af62b2b42cb0476b97f3ac8ebac Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Thu, 25 Jul 2024 20:13:08 +0900 Subject: [PATCH 41/49] =?UTF-8?q?[fix/#33]=20=EC=A7=81=EA=B4=80=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0=20=EB=93=B1=EB=A1=9D=20DTO=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 4 ++-- .../com/depromeet/data/datasource/SeatReviewDataSource.kt | 3 ++- .../data/datasource/remote/SeatReviewDataSourceImpl.kt | 6 ++++-- .../java/com/depromeet/data/remote/SeatReviewService.kt | 3 ++- .../depromeet/data/repository/SeatReviewRepositoryImpl.kt | 6 ++++-- .../com/depromeet/domain/entity/request/SeatReviewModel.kt | 2 +- .../com/depromeet/domain/repository/SeatReviewRepository.kt | 1 + .../depromeet/presentation/seatReview/ReviewViewModel.kt | 4 ++-- 8 files changed, 18 insertions(+), 11 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 80829c2a..2e3b7003 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -73,12 +73,12 @@ android:screenOrientation="portrait" /> { return runCatching { seatReviewDataSource.postSeatReviewData( - seatId, + blockId, + seatNumber, seatReviewInfo.toSeatReview(), ) } diff --git a/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt b/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt index 2c1b6ef4..c4f7d689 100644 --- a/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt +++ b/domain/src/main/java/com/depromeet/domain/entity/request/SeatReviewModel.kt @@ -5,5 +5,5 @@ data class SeatReviewModel( val dateTime: String, val good: List, val bad: List, - val content: String, + val content: String?, ) diff --git a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt index c1fa49c7..518de7d1 100644 --- a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt +++ b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt @@ -36,6 +36,7 @@ interface SeatReviewRepository { suspend fun postSeatReview( seatId: Int, + seatNumber: Int, seatReviewInfo: SeatReviewModel, ): Result } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index 1d5d7832..7db311ae 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -3,7 +3,6 @@ package com.depromeet.presentation.seatReview import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.depromeet.core.state.UiState -import com.depromeet.domain.entity.request.SeatReviewModel import com.depromeet.domain.entity.response.seatReview.ResponsePresignedUrlModel import com.depromeet.domain.entity.response.seatReview.SeatBlockModel import com.depromeet.domain.entity.response.seatReview.SeatRangeModel @@ -280,6 +279,7 @@ class ReviewViewModel @Inject constructor( return deferred } + /* fun postSeatReview() { viewModelScope.launch { val seatReviewModel = SeatReviewModel( @@ -304,5 +304,5 @@ class ReviewViewModel @Inject constructor( } } } - } + }*/ } From d88ca19e2a37e9d1174bf857475c11db375d560f Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Fri, 26 Jul 2024 10:59:20 +0900 Subject: [PATCH 42/49] =?UTF-8?q?[fix/#33]=20preSignedUrl=20=20DTO=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/depromeet/data/datasource/SeatReviewDataSource.kt | 1 - .../data/datasource/remote/SeatReviewDataSourceImpl.kt | 2 -- .../main/java/com/depromeet/data/remote/SeatReviewService.kt | 3 +-- .../com/depromeet/data/repository/SeatReviewRepositoryImpl.kt | 2 -- .../com/depromeet/domain/repository/SeatReviewRepository.kt | 1 - .../com/depromeet/presentation/seatReview/ReviewActivity.kt | 2 +- .../com/depromeet/presentation/seatReview/ReviewViewModel.kt | 4 ++-- 7 files changed, 4 insertions(+), 11 deletions(-) diff --git a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt index 3193a590..0568321f 100644 --- a/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt +++ b/data/src/main/java/com/depromeet/data/datasource/SeatReviewDataSource.kt @@ -26,7 +26,6 @@ interface SeatReviewDataSource { suspend fun postImagePreSignedData( fileExtension: String, - memberId: Int, ): ResponsePreSignedUrlDto suspend fun putReviewImageData( diff --git a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt index 67d16fc1..0bd83708 100644 --- a/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt +++ b/data/src/main/java/com/depromeet/data/datasource/remote/SeatReviewDataSourceImpl.kt @@ -42,11 +42,9 @@ class SeatReviewDataSourceImpl @Inject constructor( override suspend fun postImagePreSignedData( fileExtension: String, - memberId: Int, ): ResponsePreSignedUrlDto { return seatReviewService.postImagePreSignedUrl( RequestPreSignedUrlDto(fileExtension), - memberId, ) } diff --git a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt index 4ba14ca5..c42f7284 100644 --- a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt +++ b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt @@ -36,10 +36,9 @@ interface SeatReviewService { @Path("sectionId") sectionId: Int, ): List - @POST("/api/v1/members/{memberId}/reviews/images") + @POST("/api/v1/reviews/images") suspend fun postImagePreSignedUrl( @Body body: RequestPreSignedUrlDto, - @Path("memberId") memberId: Int, ): ResponsePreSignedUrlDto @PUT diff --git a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt index 85507f38..2de42db2 100644 --- a/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt +++ b/data/src/main/java/com/depromeet/data/repository/SeatReviewRepositoryImpl.kt @@ -54,12 +54,10 @@ class SeatReviewRepositoryImpl @Inject constructor( override suspend fun postReviewImagePresigned( fileExtension: String, - memberId: Int, ): Result { return runCatching { seatReviewDataSource.postImagePreSignedData( fileExtension, - memberId, ).toResponsePreSignedUrl() } } diff --git a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt index 518de7d1..489b974f 100644 --- a/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt +++ b/domain/src/main/java/com/depromeet/domain/repository/SeatReviewRepository.kt @@ -26,7 +26,6 @@ interface SeatReviewRepository { suspend fun postReviewImagePresigned( fileExtension: String, - memberId: Int, ): Result suspend fun putImagePreSignedUrl( diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index bc4beade..b86f26ce 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -294,7 +294,7 @@ class ReviewActivity : BaseActivity({ if (imageData != null) { // TODO : viewModel.uploadImageToPreSignedUrl 사진 업로드 서버 통신 // TODO : postSeatReview() 시야 등록 후기 request -> ReviewDoneActivity 이동 - viewModel.requestPreSignedUrl(fileExtension, 1) + viewModel.requestPreSignedUrl(fileExtension) viewModel.getPreSignedUrl.asLiveData().observe(this) { state -> when (state) { is UiState.Success -> { diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index 7db311ae..c691b595 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -233,10 +233,10 @@ class ReviewViewModel @Inject constructor( } // presigned URL 요청 - fun requestPreSignedUrl(fileExtension: String, memberId: Int) { + fun requestPreSignedUrl(fileExtension: String) { viewModelScope.launch { _getPreSignedUrl.value = UiState.Loading - seatReviewRepository.postReviewImagePresigned(fileExtension, memberId) + seatReviewRepository.postReviewImagePresigned(fileExtension) .onSuccess { response -> Timber.d("REQUEST PRESIGNED URL SUCCESS : $response") _getPreSignedUrl.value = UiState.Success(response) From d73a139468e4ef059709564e76c1071481286292 Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Sat, 27 Jul 2024 19:19:39 +0900 Subject: [PATCH 43/49] =?UTF-8?q?[fix/#33]=20=EC=A7=81=EA=B4=80=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0=20=EB=93=B1=EB=A1=9D=20API=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=20=EC=88=98=EC=A0=95=EC=82=AC=ED=95=AD=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/remote/SeatReviewService.kt | 2 +- .../presentation/seatReview/ReviewActivity.kt | 62 ++++++++++++------- .../seatReview/ReviewViewModel.kt | 61 ++++++++++++++++-- .../seatReview/dialog/SelectSeatDialog.kt | 6 ++ 4 files changed, 103 insertions(+), 28 deletions(-) diff --git a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt index c42f7284..26a91bb8 100644 --- a/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt +++ b/data/src/main/java/com/depromeet/data/remote/SeatReviewService.kt @@ -47,7 +47,7 @@ interface SeatReviewService { @Body image: RequestBody, ) - @POST("/api/v1/seats/{seatId}/members/{memberId}/reviews") + @POST("/api/v1/blocks/{blockId}/seats/{seatNumber}/reviews") suspend fun postSeatReview( @Path("blockId") blockId: Int, @Path("seatNumber") seatNumber: Int, diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index b86f26ce..de07bb75 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -38,7 +38,7 @@ class ReviewActivity : BaseActivity({ ActivityReviewBinding.inflate(it) }) { companion object { - private const val DATE_FORMAT = "yyyy.MM.dd" + private const val DATE_FORMAT = "yy.MM.dd" private const val FRAGMENT_RESULT_KEY = "requestKey" private const val SELECTED_IMAGES = "selected_images" private const val MAX_SELECTED_IMAGES = 3 @@ -72,6 +72,7 @@ class ReviewActivity : BaseActivity({ super.onCreate(savedInstanceState) viewModel.getStadiumName() observeStadiumName() + observeUploadReview() initDatePickerDialog() initUploadDialog() initSeatReviewDialog() @@ -285,6 +286,43 @@ class ReviewActivity : BaseActivity({ return inputStream?.use { it.readBytes() } } + private fun observePreSignedUrl(imageData: ByteArray) { + viewModel.getPreSignedUrl.asLiveData().observe(this) { state -> + when (state) { + is UiState.Success -> { + val presignedUrl = state.data.presignedUrl + viewModel.uploadImageToPreSignedUrl(presignedUrl, imageData) + viewModel.postSeatReview() + Intent(this, ReviewDoneActivity::class.java).apply { + startActivity(this) + } + } + + is UiState.Failure -> { + toast("Presigned URL 요청 실패: $state") + } + + else -> {} + } + } + } + + private fun observeUploadReview() { + viewModel.postReviewState.asLiveData().observe(this) { state -> + when (state) { + is UiState.Success -> { + navigateToReviewDoneActivity() + } + + is UiState.Failure -> { + toast("리뷰 등록 실패: $state") + } + + else -> {} + } + } + } + private fun navigateToReviewDoneActivity() { binding.tvUploadBtn.setOnSingleClickListener { selectedImageUris.forEach { imageUriString -> @@ -292,28 +330,8 @@ class ReviewActivity : BaseActivity({ val fileExtension = getFileExtension(this, imageUri) val imageData = readImageData(this, imageUri) if (imageData != null) { - // TODO : viewModel.uploadImageToPreSignedUrl 사진 업로드 서버 통신 - // TODO : postSeatReview() 시야 등록 후기 request -> ReviewDoneActivity 이동 viewModel.requestPreSignedUrl(fileExtension) - viewModel.getPreSignedUrl.asLiveData().observe(this) { state -> - when (state) { - is UiState.Success -> { - val presignedUrl = state.data.presignedUrl - // viewModel.uploadImageToPreSignedUrl(presignedUrl, imageData) - Intent(this, ReviewDoneActivity::class.java).apply { - startActivity( - this, - ) - } - } - - is UiState.Failure -> { - toast("Presigned URL 요청 실패: $state") - } - - else -> {} - } - } + observePreSignedUrl(imageData) } else { toast("파일을 읽을 수 없습니다.") } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index c691b595..a528f78d 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -3,6 +3,7 @@ package com.depromeet.presentation.seatReview import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.depromeet.core.state.UiState +import com.depromeet.domain.entity.request.SeatReviewModel import com.depromeet.domain.entity.response.seatReview.ResponsePresignedUrlModel import com.depromeet.domain.entity.response.seatReview.SeatBlockModel import com.depromeet.domain.entity.response.seatReview.SeatRangeModel @@ -75,6 +76,9 @@ class ReviewViewModel @Inject constructor( private val _selectedSectionId = MutableStateFlow(0) val selectedSectionId: StateFlow = _selectedSectionId.asStateFlow() + private val _selectedBlockId = MutableStateFlow(0) + val selectedBlockId: StateFlow = _selectedBlockId.asStateFlow() + private val _stadiumSectionState = MutableStateFlow>(UiState.Empty) val stadiumSectionState: StateFlow> = _stadiumSectionState @@ -99,6 +103,10 @@ class ReviewViewModel @Inject constructor( _selectedSectionId.value = sectionId } + fun updateSelectedBlockId(blockId: Int) { + _selectedBlockId.value = blockId + } + fun updateSelectedDate(date: String) { _selectedDate.value = date } @@ -270,6 +278,7 @@ class ReviewViewModel @Inject constructor( Timber.e("UPLOAD IMAGE FAILURE : $t") if (t is HttpException) { Timber.e("HTTP error code: ${t.code()}") + Timber.e("HTTP error response: ${t.response()?.errorBody()?.string()}") } else { Timber.e("General error: ${t.message ?: "Unknown error"}") } @@ -279,7 +288,6 @@ class ReviewViewModel @Inject constructor( return deferred } - /* fun postSeatReview() { viewModelScope.launch { val seatReviewModel = SeatReviewModel( @@ -289,20 +297,63 @@ class ReviewViewModel @Inject constructor( bad = _selectedBadReview.value, content = _detailReviewText.value, ) + + Timber.d("Selected Images: ${_selectedImages.value}") + Timber.d("Selected Date: ${_selectedDate.value}") + Timber.d("Good Review: ${_selectedGoodReview.value}") + Timber.d("Bad Review: ${_selectedBadReview.value}") + Timber.d("Detail Review Text: ${_detailReviewText.value}") + Timber.d("Selected Stadium ID: ${_selectedStadiumId.value}") + Timber.d("Selected Block ID: ${_selectedBlockId.value}") + Timber.d("Selected seatNumber: ${selectedNumber.value}") + + val selectedNumberValue = selectedNumber.value + if (selectedNumberValue.isNullOrEmpty()) { + Timber.e("Selected Number is null or empty") + _postReviewState.value = UiState.Failure("Selected Number is required") + return@launch + } + + val selectedNumberInt = try { + selectedNumberValue.toInt() + } catch (e: NumberFormatException) { + Timber.e("Selected Number is not a valid integer: $selectedNumberValue") + _postReviewState.value = UiState.Failure("Selected Number is not a valid integer") + return@launch + } + + Timber.d("Selected Number: $selectedNumberInt") + _postReviewState.value = UiState.Loading - seatReviewRepository.postSeatReview(_selectedStadiumId.value, seatReviewModel) + + seatReviewRepository.postSeatReview( + _selectedBlockId.value, + selectedNumberInt, + seatReviewModel, + ) .onSuccess { _postReviewState.value = UiState.Success(Unit) Timber.d("POST REVIEW SUCCESS") } .onFailure { t -> Timber.e("POST REVIEW FAILURE : $t") + if (t is HttpException) { - _postReviewState.value = UiState.Failure(t.code().toString()) + val errorBody = t.response()?.errorBody()?.string() + Timber.e("Error Body: $errorBody") + + val errorMessage = when { + t.code() == 403 -> "권한이 없습니다. 요청을 확인하고 다시 시도해 주세요." + errorBody.isNullOrEmpty() -> "HTTP ${t.code()} 에러 발생: ${t.message()}" + else -> errorBody + } + + _postReviewState.value = UiState.Failure(errorMessage) } else { - _postReviewState.value = UiState.Failure(t.message ?: "Unknown error") + _postReviewState.value = UiState.Failure(t.message ?: "알 수 없는 오류") } } } - }*/ + } + } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index 71d17c1c..d9b9688c 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -243,6 +243,8 @@ class SelectSeatDialog : BindingBottomSheetDialog?) { viewModel.setSelectedBlock("") + viewModel.updateSelectedBlockId(0) } } } From 111c949a93e75389524db54ae2ebad24e0f55bd1 Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Sat, 27 Jul 2024 20:31:59 +0900 Subject: [PATCH 44/49] =?UTF-8?q?[feat/#33]=20upload=20image=20API,=20?= =?UTF-8?q?=EC=A7=81=EA=B4=80=20=ED=9B=84=EA=B8=B0=20=EB=93=B1=EB=A1=9D=20?= =?UTF-8?q?API=20=EC=9E=91=EB=8F=99=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/depromeet/presentation/seatReview/ReviewViewModel.kt | 1 - "\353\254\264\354\240\234" | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index a528f78d..c1057a5b 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -325,7 +325,6 @@ class ReviewViewModel @Inject constructor( Timber.d("Selected Number: $selectedNumberInt") _postReviewState.value = UiState.Loading - seatReviewRepository.postSeatReview( _selectedBlockId.value, selectedNumberInt, diff --git "a/\353\254\264\354\240\234" "b/\353\254\264\354\240\234" index c47ffb04..26f973fe 160000 --- "a/\353\254\264\354\240\234" +++ "b/\353\254\264\354\240\234" @@ -1 +1 @@ -Subproject commit c47ffb0452bd1133d0acc4ddd286e341ab0a3c23 +Subproject commit 26f973fed59af8bcb407c2c7972f8614d2055560 From 64d59885b5962c3134346caa4e58b25671723f50 Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Sun, 28 Jul 2024 23:38:44 +0900 Subject: [PATCH 45/49] =?UTF-8?q?[fix/#33]=20=EB=82=A0=EC=A7=9C=20format?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20Date=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/seatReview/ReviewActivity.kt | 57 ++++++------------- .../seatReview/ReviewViewModel.kt | 12 ++-- .../presentation/util/CalenderUtil.kt | 2 +- .../src/main/res/layout/activity_review.xml | 2 +- "\353\254\264\354\240\234" | 2 +- 5 files changed, 28 insertions(+), 47 deletions(-) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index de07bb75..d419a33c 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -38,34 +38,17 @@ class ReviewActivity : BaseActivity({ ActivityReviewBinding.inflate(it) }) { companion object { - private const val DATE_FORMAT = "yy.MM.dd" + private const val DATE_FORMAT = "yyyy.MM.dd" + private const val ISO_DATE_FORMAT = "yyyy-MM-dd HH:mm" private const val FRAGMENT_RESULT_KEY = "requestKey" private const val SELECTED_IMAGES = "selected_images" private const val MAX_SELECTED_IMAGES = 3 } private val viewModel by viewModels() - private val selectedImage: List by lazy { - listOf( - binding.ivFirstImage, - binding.ivSecondImage, - binding.ivThirdImage, - ) - } - private val selectedImageLayout: List by lazy { - listOf( - binding.layoutFirstImage, - binding.layoutSecondImage, - binding.layoutThirdImage, - ) - } - private val removeButtons: List by lazy { - listOf( - binding.ivRemoveFirstImage, - binding.ivRemoveSecondImage, - binding.ivRemoveThirdImage, - ) - } + private val selectedImage: List by lazy { listOf(binding.ivFirstImage, binding.ivSecondImage, binding.ivThirdImage,) } + private val selectedImageLayout: List by lazy { listOf(binding.layoutFirstImage, binding.layoutSecondImage, binding.layoutThirdImage,) } + private val removeButtons: List by lazy { listOf(binding.ivRemoveFirstImage, binding.ivRemoveSecondImage, binding.ivRemoveThirdImage,) } private var selectedImageUris: MutableList = mutableListOf() override fun onCreate(savedInstanceState: Bundle?) { @@ -82,10 +65,6 @@ class ReviewActivity : BaseActivity({ } private fun observeReviewViewModel() { - viewModel.selectedDate.asLiveData().observe(this) { date -> - binding.tvDate.text = date - updateNextButtonState() - } viewModel.selectedImages.asLiveData().observe(this) { image -> updateNextButtonState() } @@ -127,6 +106,19 @@ class ReviewActivity : BaseActivity({ } } + private fun initDatePickerDialog() { + binding.layoutDatePicker.setOnSingleClickListener { + DatePickerDialog().show(supportFragmentManager, "DatePickerDialogTag") + } + viewModel.selectedDate.asLiveData().observe(this) { date -> + val originalFormat = SimpleDateFormat(ISO_DATE_FORMAT, Locale.getDefault()) + val targetFormat = SimpleDateFormat(DATE_FORMAT, Locale.getDefault()) + val dateOnly = originalFormat.parse(date)?.let { targetFormat.format(it) } + binding.tvDate.text = dateOnly ?: date.substring(0, 10) + updateNextButtonState() + } + } + private fun observeStadiumName() { viewModel.stadiumNameState.asLiveData().observe(this) { state -> when (state) { @@ -146,19 +138,6 @@ class ReviewActivity : BaseActivity({ } } } - - private fun initDatePickerDialog() { - val today = Calendar.getInstance() - val dateFormat = SimpleDateFormat(DATE_FORMAT, Locale.getDefault()) - with(binding) { - tvDate.text = dateFormat.format(today.time) - layoutDatePicker.setOnSingleClickListener { - val datePickerDialogFragment = DatePickerDialog() - datePickerDialogFragment.show(supportFragmentManager, datePickerDialogFragment.tag) - } - } - } - private fun initUploadDialog() { binding.btnAddImage.setOnClickListener { val uploadDialogFragment = ImageUploadDialog() diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index c1057a5b..aa0b0880 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -1,5 +1,6 @@ package com.depromeet.presentation.seatReview +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.depromeet.core.state.UiState @@ -18,7 +19,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import retrofit2.HttpException import timber.log.Timber -import java.time.LocalDate +import java.time.LocalDateTime import java.time.format.DateTimeFormatter import javax.inject.Inject @@ -27,12 +28,13 @@ class ReviewViewModel @Inject constructor( private val seatReviewRepository: SeatReviewRepository, ) : ViewModel() { - // 날짜 및 이미지 - private val dateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yy.MM.dd") - private val currentDate: String = LocalDate.now().format(dateFormatter) + // 날짜 + private val dateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") + private val currentDate: String = LocalDateTime.now().format(dateFormatter) private val _selectedDate = MutableStateFlow(currentDate) val selectedDate: StateFlow = _selectedDate.asStateFlow() + // 이미지 private val _selectedImages = MutableStateFlow>(emptyList()) val selectedImages: StateFlow> = _selectedImages.asStateFlow() @@ -109,6 +111,7 @@ class ReviewViewModel @Inject constructor( fun updateSelectedDate(date: String) { _selectedDate.value = date + Log.d("mm", _selectedDate.value.toString()) } fun setSelectedImages(image: List) { @@ -354,5 +357,4 @@ class ReviewViewModel @Inject constructor( } } } - } diff --git a/presentation/src/main/java/com/depromeet/presentation/util/CalenderUtil.kt b/presentation/src/main/java/com/depromeet/presentation/util/CalenderUtil.kt index b117e595..ebfb3e31 100644 --- a/presentation/src/main/java/com/depromeet/presentation/util/CalenderUtil.kt +++ b/presentation/src/main/java/com/depromeet/presentation/util/CalenderUtil.kt @@ -11,7 +11,7 @@ import java.util.Locale object CalendarUtil { private const val ISO_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss" - private const val DATE_FORMAT = "yy.MM.dd" + private const val DATE_FORMAT = "yyyy-MM-dd HH:mm" fun formatCalendarDate(calendar: Calendar): String { return SimpleDateFormat(DATE_FORMAT, Locale.getDefault()).format(calendar.time) diff --git a/presentation/src/main/res/layout/activity_review.xml b/presentation/src/main/res/layout/activity_review.xml index 2417d54a..0e0ae38c 100644 --- a/presentation/src/main/res/layout/activity_review.xml +++ b/presentation/src/main/res/layout/activity_review.xml @@ -47,7 +47,7 @@ android:textSize="13sp" android:textFontWeight="500" android:textStyle="normal" - tools:text="24.06.30" /> + tools:text="2024.06.30" /> Date: Mon, 29 Jul 2024 11:07:00 +0900 Subject: [PATCH 46/49] =?UTF-8?q?[fix/#33]=20=ED=95=A8=EC=88=98=EB=AA=85?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=ED=99=88=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=20=ED=94=8C=EB=A1=9C=EC=9A=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/seatReview/ReviewActivity.kt | 41 ++++++++++--------- .../seatReview/ReviewDoneActivity.kt | 1 + .../seatReview/ReviewViewModel.kt | 4 +- .../seatReview/adapter/SelectSeatAdapter.kt | 4 +- .../seatReview/dialog/ImageUploadDialog.kt | 27 ++++++------ .../seatReview/dialog/SelectSeatDialog.kt | 6 +-- 6 files changed, 42 insertions(+), 41 deletions(-) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index d419a33c..8b5fdf37 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -22,6 +22,7 @@ import com.depromeet.presentation.R import com.depromeet.presentation.databinding.ActivityReviewBinding import com.depromeet.presentation.extension.setOnSingleClickListener import com.depromeet.presentation.extension.toast +import com.depromeet.presentation.home.HomeActivity import com.depromeet.presentation.seatReview.dialog.DatePickerDialog import com.depromeet.presentation.seatReview.dialog.ImageUploadDialog import com.depromeet.presentation.seatReview.dialog.ReviewMySeatDialog @@ -30,7 +31,6 @@ import dagger.hilt.android.AndroidEntryPoint import java.io.FileNotFoundException import java.io.InputStream import java.text.SimpleDateFormat -import java.util.Calendar import java.util.Locale @AndroidEntryPoint @@ -46,9 +46,9 @@ class ReviewActivity : BaseActivity({ } private val viewModel by viewModels() - private val selectedImage: List by lazy { listOf(binding.ivFirstImage, binding.ivSecondImage, binding.ivThirdImage,) } - private val selectedImageLayout: List by lazy { listOf(binding.layoutFirstImage, binding.layoutSecondImage, binding.layoutThirdImage,) } - private val removeButtons: List by lazy { listOf(binding.ivRemoveFirstImage, binding.ivRemoveSecondImage, binding.ivRemoveThirdImage,) } + private val selectedImage: List by lazy { listOf(binding.ivFirstImage, binding.ivSecondImage, binding.ivThirdImage) } + private val selectedImageLayout: List by lazy { listOf(binding.layoutFirstImage, binding.layoutSecondImage, binding.layoutThirdImage) } + private val removeButtons: List by lazy { listOf(binding.ivRemoveFirstImage, binding.ivRemoveSecondImage, binding.ivRemoveThirdImage) } private var selectedImageUris: MutableList = mutableListOf() override fun onCreate(savedInstanceState: Bundle?) { @@ -61,7 +61,8 @@ class ReviewActivity : BaseActivity({ initSeatReviewDialog() setupFragmentResultListener() setupRemoveButtons() - navigateToReviewDoneActivity() + uploadAllReviewDone() + navigateToHomeActivity() } private fun observeReviewViewModel() { @@ -119,6 +120,18 @@ class ReviewActivity : BaseActivity({ } } + private fun initUploadDialog() { + binding.btnAddImage.setOnClickListener { + ImageUploadDialog().show(supportFragmentManager, "ImageUploadDialog") + } + } + + private fun navigateToHomeActivity() { + binding.btnBack.setOnSingleClickListener { + Intent(this, HomeActivity::class.java).apply { startActivity(this) } + } + } + private fun observeStadiumName() { viewModel.stadiumNameState.asLiveData().observe(this) { state -> when (state) { @@ -138,12 +151,6 @@ class ReviewActivity : BaseActivity({ } } } - private fun initUploadDialog() { - binding.btnAddImage.setOnClickListener { - val uploadDialogFragment = ImageUploadDialog() - uploadDialogFragment.show(supportFragmentManager, uploadDialogFragment.tag) - } - } private fun initSeatReviewDialog() { binding.layoutReviewMySeat.setOnSingleClickListener { @@ -269,18 +276,16 @@ class ReviewActivity : BaseActivity({ viewModel.getPreSignedUrl.asLiveData().observe(this) { state -> when (state) { is UiState.Success -> { - val presignedUrl = state.data.presignedUrl - viewModel.uploadImageToPreSignedUrl(presignedUrl, imageData) + val preSignedUrl = state.data.presignedUrl + viewModel.uploadImageToPreSignedUrl(preSignedUrl, imageData) viewModel.postSeatReview() Intent(this, ReviewDoneActivity::class.java).apply { startActivity(this) } } - is UiState.Failure -> { toast("Presigned URL 요청 실패: $state") } - else -> {} } } @@ -290,19 +295,17 @@ class ReviewActivity : BaseActivity({ viewModel.postReviewState.asLiveData().observe(this) { state -> when (state) { is UiState.Success -> { - navigateToReviewDoneActivity() + uploadAllReviewDone() } - is UiState.Failure -> { toast("리뷰 등록 실패: $state") } - else -> {} } } } - private fun navigateToReviewDoneActivity() { + private fun uploadAllReviewDone() { binding.tvUploadBtn.setOnSingleClickListener { selectedImageUris.forEach { imageUriString -> val imageUri = Uri.parse(imageUriString) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewDoneActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewDoneActivity.kt index eb507a16..c09dde9f 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewDoneActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewDoneActivity.kt @@ -21,6 +21,7 @@ class ReviewDoneActivity : BaseActivity({ lifecycleScope.launch { delay(2000L) finish() + // TODO : 기록된 모든 정보 삭제 } } } diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index aa0b0880..cf15c800 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -34,6 +34,7 @@ class ReviewViewModel @Inject constructor( private val _selectedDate = MutableStateFlow(currentDate) val selectedDate: StateFlow = _selectedDate.asStateFlow() + // 이미지 private val _selectedImages = MutableStateFlow>(emptyList()) @@ -51,7 +52,6 @@ class ReviewViewModel @Inject constructor( val selectedBadReview: StateFlow> = _selectedBadReview.asStateFlow() private val _detailReviewText = MutableStateFlow("") - val detailReviewText: StateFlow = _detailReviewText.asStateFlow() // 좌석 선택 @@ -111,7 +111,6 @@ class ReviewViewModel @Inject constructor( fun updateSelectedDate(date: String) { _selectedDate.value = date - Log.d("mm", _selectedDate.value.toString()) } fun setSelectedImages(image: List) { @@ -301,6 +300,7 @@ class ReviewViewModel @Inject constructor( content = _detailReviewText.value, ) + // 추후 Timber 삭제 예정 Timber.d("Selected Images: ${_selectedImages.value}") Timber.d("Selected Date: ${_selectedDate.value}") Timber.d("Good Review: ${_selectedGoodReview.value}") diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/adapter/SelectSeatAdapter.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/adapter/SelectSeatAdapter.kt index 988bb840..dfb29492 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/adapter/SelectSeatAdapter.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/adapter/SelectSeatAdapter.kt @@ -12,9 +12,9 @@ import com.depromeet.presentation.R import com.depromeet.presentation.databinding.ItemSelectSeatBinding import com.depromeet.presentation.util.ItemDiffCallback -class SectionListAdapter( +class SelectSeatAdapter( private val onItemClick: (Int, Int) -> Unit -) : ListAdapter(diffUtil) { +) : ListAdapter(diffUtil) { private var selectedPosition = RecyclerView.NO_POSITION diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/ImageUploadDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/ImageUploadDialog.kt index d3db5e64..d2bc31ac 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/ImageUploadDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/ImageUploadDialog.kt @@ -12,7 +12,6 @@ import android.os.Build import android.os.Bundle import android.provider.MediaStore import android.view.View -import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts @@ -24,6 +23,7 @@ import com.depromeet.presentation.R import com.depromeet.presentation.databinding.FragmentUploadBottomSheetBinding import com.depromeet.presentation.extension.setOnSingleClickListener import com.depromeet.presentation.extension.toUri +import com.depromeet.presentation.extension.toast import com.depromeet.presentation.home.UploadErrorDialog import dagger.hilt.android.AndroidEntryPoint @@ -55,18 +55,16 @@ class ImageUploadDialog : BindingBottomSheetDialog 15) { val fragment = UploadErrorDialog( getString(R.string.upload_error_capacity_description), diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt index d9b9688c..cff179b3 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/dialog/SelectSeatDialog.kt @@ -25,7 +25,7 @@ import com.depromeet.presentation.databinding.FragmentSelectSeatBottomSheetBindi import com.depromeet.presentation.extension.setOnSingleClickListener import com.depromeet.presentation.extension.toast import com.depromeet.presentation.seatReview.ReviewViewModel -import com.depromeet.presentation.seatReview.adapter.SectionListAdapter +import com.depromeet.presentation.seatReview.adapter.SelectSeatAdapter import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -34,7 +34,7 @@ class SelectSeatDialog : BindingBottomSheetDialog + adapter = SelectSeatAdapter { position, sectionId -> val selectedSeatInfo = adapter.currentList[position] adapter.setItemSelected(position) viewModel.setSelectedSeatZone(selectedSeatInfo.name) From 0e35b510f937b5e6fc12d2e36e549052ab5d5145 Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Mon, 29 Jul 2024 11:08:01 +0900 Subject: [PATCH 47/49] [fix/#33] manifest true -> false --- app/src/main/AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2e3b7003..80829c2a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -73,12 +73,12 @@ android:screenOrientation="portrait" /> Date: Mon, 29 Jul 2024 18:00:31 +0900 Subject: [PATCH 48/49] =?UTF-8?q?[fix/#33]=20=EB=8B=A4=EC=A4=91=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=A7=81=EA=B4=80=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20API=20=20=EC=A4=91=EB=B3=B5=20=ED=86=B5=EC=8B=A0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/seatReview/ReviewActivity.kt | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index 8b5fdf37..e463572a 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -28,6 +28,10 @@ import com.depromeet.presentation.seatReview.dialog.ImageUploadDialog import com.depromeet.presentation.seatReview.dialog.ReviewMySeatDialog import com.depromeet.presentation.seatReview.dialog.SelectSeatDialog import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import java.io.FileNotFoundException import java.io.InputStream import java.text.SimpleDateFormat @@ -272,19 +276,22 @@ class ReviewActivity : BaseActivity({ return inputStream?.use { it.readBytes() } } - private fun observePreSignedUrl(imageData: ByteArray) { + private fun observePreSignedUrl(deferred: CompletableDeferred, imageData: ByteArray) { viewModel.getPreSignedUrl.asLiveData().observe(this) { state -> when (state) { is UiState.Success -> { val preSignedUrl = state.data.presignedUrl - viewModel.uploadImageToPreSignedUrl(preSignedUrl, imageData) - viewModel.postSeatReview() - Intent(this, ReviewDoneActivity::class.java).apply { - startActivity(this) + viewModel.uploadImageToPreSignedUrl(preSignedUrl, imageData).invokeOnCompletion { + if (it == null) { + deferred.complete(true) + } else { + deferred.complete(false) + } } } is UiState.Failure -> { - toast("Presigned URL 요청 실패: $state") + toast("Presigned URL 요청 실패") + deferred.complete(false) } else -> {} } @@ -296,6 +303,9 @@ class ReviewActivity : BaseActivity({ when (state) { is UiState.Success -> { uploadAllReviewDone() + Intent(this, ReviewDoneActivity::class.java).apply { + startActivity(this) + } } is UiState.Failure -> { toast("리뷰 등록 실패: $state") @@ -307,17 +317,29 @@ class ReviewActivity : BaseActivity({ private fun uploadAllReviewDone() { binding.tvUploadBtn.setOnSingleClickListener { + val uploadResults = mutableListOf>() selectedImageUris.forEach { imageUriString -> val imageUri = Uri.parse(imageUriString) val fileExtension = getFileExtension(this, imageUri) val imageData = readImageData(this, imageUri) if (imageData != null) { + val deferred = CompletableDeferred() + uploadResults.add(deferred) viewModel.requestPreSignedUrl(fileExtension) - observePreSignedUrl(imageData) + observePreSignedUrl(deferred, imageData) } else { toast("파일을 읽을 수 없습니다.") } } + CoroutineScope(Dispatchers.Main).launch { + val allUploadsCompleted = uploadResults.all { it.await() } + if (allUploadsCompleted) { + viewModel.postSeatReview() + } else { + toast("이미지 업로드에 실패했습니다.") + } + } } } + } From ebd315d05f487156b34a2d72ee5f73740c9126ee Mon Sep 17 00:00:00 2001 From: pmj1459 <76741702+minju1459@users.noreply.github.com> Date: Mon, 29 Jul 2024 22:53:54 +0900 Subject: [PATCH 49/49] =?UTF-8?q?[fix/#33]=20=EC=A7=81=EA=B4=80=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=EB=B7=B0=20git=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../compileKotlin/cacheable/last-build.bin | Bin 18 -> 18 bytes .../local-state/build-history.bin | Bin 31 -> 31 bytes buildSrc/build/libs/buildSrc.jar | Bin 12391 -> 12415 bytes .../presentation/seatReview/ReviewActivity.kt | 23 ++++++++++++------ .../seatReview/ReviewViewModel.kt | 19 +++++++++++++-- "\353\254\264\354\240\234" | 2 +- 6 files changed, 34 insertions(+), 10 deletions(-) diff --git a/buildSrc/build/kotlin/compileKotlin/cacheable/last-build.bin b/buildSrc/build/kotlin/compileKotlin/cacheable/last-build.bin index 4b7f20cc502b86ca394680d9df8d93134930c869..5da00f18e584ee22af7450624cbd84bb2bfe17b8 100644 GIT binary patch literal 18 YcmZ4UmVvdLhk=1{!oP!c^BI5u06VY+j{pDw literal 18 YcmZ4UmVvdLhk=1{!qxNkoeV$#065|W9{>OV diff --git a/buildSrc/build/kotlin/compileKotlin/local-state/build-history.bin b/buildSrc/build/kotlin/compileKotlin/local-state/build-history.bin index ebb3f9b34b9a512525430cf5e2e916d8bdf53142..da69078944beafc8348656098dac02f344e86298 100644 GIT binary patch literal 31 dcmZ4UmVvcgk^ur385kHR{5x1TAIfK7003$41)Tr@ literal 31 dcmZ4UmVvcgk^ur385kHRTs?2!3FR{|003n11uXyo diff --git a/buildSrc/build/libs/buildSrc.jar b/buildSrc/build/libs/buildSrc.jar index 6d91849a8b0902b0066d03587c2408d6d286097c..e6f39100ae24666496772fd9e27e274ceb2da54a 100644 GIT binary patch delta 8139 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 P=|XsVGp9e>gz*0WTa5VK delta 7997 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 diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt index e463572a..ff05d965 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewActivity.kt @@ -281,12 +281,17 @@ class ReviewActivity : BaseActivity({ when (state) { is UiState.Success -> { val preSignedUrl = state.data.presignedUrl - viewModel.uploadImageToPreSignedUrl(preSignedUrl, imageData).invokeOnCompletion { - if (it == null) { - deferred.complete(true) - } else { - deferred.complete(false) + if (viewModel.preSignedUrlImages.value.contains(preSignedUrl).not()) { + viewModel.setPreSignedUrlImages(preSignedUrl) + viewModel.uploadImageToPreSignedUrl(preSignedUrl, imageData).invokeOnCompletion { + if (it == null) { + deferred.complete(true) + } else { + deferred.complete(false) + } } + } else { + deferred.complete(false) } } is UiState.Failure -> { @@ -315,13 +320,16 @@ class ReviewActivity : BaseActivity({ } } - private fun uploadAllReviewDone() { + fun uploadAllReviewDone() { binding.tvUploadBtn.setOnSingleClickListener { val uploadResults = mutableListOf>() - selectedImageUris.forEach { imageUriString -> + val uniqueImageUris = selectedImageUris.distinct() // 중복된 URI를 제거합니다. + + uniqueImageUris.forEach { imageUriString -> val imageUri = Uri.parse(imageUriString) val fileExtension = getFileExtension(this, imageUri) val imageData = readImageData(this, imageUri) + if (imageData != null) { val deferred = CompletableDeferred() uploadResults.add(deferred) @@ -331,6 +339,7 @@ class ReviewActivity : BaseActivity({ toast("파일을 읽을 수 없습니다.") } } + CoroutineScope(Dispatchers.Main).launch { val allUploadsCompleted = uploadResults.all { it.await() } if (allUploadsCompleted) { diff --git a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt index cf15c800..bbf7e349 100644 --- a/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt +++ b/presentation/src/main/java/com/depromeet/presentation/seatReview/ReviewViewModel.kt @@ -1,5 +1,6 @@ package com.depromeet.presentation.seatReview +import android.net.Uri import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -40,6 +41,9 @@ class ReviewViewModel @Inject constructor( private val _selectedImages = MutableStateFlow>(emptyList()) val selectedImages: StateFlow> = _selectedImages.asStateFlow() + private val _preSignedUrlImages = MutableStateFlow>(emptyList()) + val preSignedUrlImages: StateFlow> = _preSignedUrlImages.asStateFlow() + // 시야 후기 private val _reviewCount = MutableStateFlow(0) @@ -117,6 +121,12 @@ class ReviewViewModel @Inject constructor( _selectedImages.value = image } + fun setPreSignedUrlImages(image: String) { + val newImage = removeQueryParameters(image) + val currentImages = _preSignedUrlImages.value.toMutableSet() + currentImages.add(newImage) + _preSignedUrlImages.value = currentImages.toList() + } fun setReviewCount(count: Int) { _reviewCount.value = count } @@ -290,10 +300,15 @@ class ReviewViewModel @Inject constructor( return deferred } + private fun removeQueryParameters(url: String): String { + val uri = Uri.parse(url) + return uri.buildUpon().clearQuery().build().toString() + } + fun postSeatReview() { viewModelScope.launch { val seatReviewModel = SeatReviewModel( - images = _selectedImages.value, + images = _preSignedUrlImages.value, dateTime = _selectedDate.value, good = _selectedGoodReview.value, bad = _selectedBadReview.value, @@ -301,7 +316,7 @@ class ReviewViewModel @Inject constructor( ) // 추후 Timber 삭제 예정 - Timber.d("Selected Images: ${_selectedImages.value}") + Timber.d("Selected Images: ${_preSignedUrlImages.value}") Timber.d("Selected Date: ${_selectedDate.value}") Timber.d("Good Review: ${_selectedGoodReview.value}") Timber.d("Bad Review: ${_selectedBadReview.value}") diff --git "a/\353\254\264\354\240\234" "b/\353\254\264\354\240\234" index f2f24ae6..003bfe71 160000 --- "a/\353\254\264\354\240\234" +++ "b/\353\254\264\354\240\234" @@ -1 +1 @@ -Subproject commit f2f24ae6e34aa3554f175f3b166b0981f8381036 +Subproject commit 003bfe7125c73b58f8b5db2d4c1499726576653e