From 3653773677593132c9e1db617c03161082706c63 Mon Sep 17 00:00:00 2001 From: algosketch Date: Sat, 23 Nov 2024 16:06:52 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat=20:=20=EB=B0=9B=EC=9D=80=20=EC=84=A0?= =?UTF-8?q?=EB=AC=BC=20=EC=B7=A8=EC=86=8C=ED=95=98=EA=B8=B0=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EB=85=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/network/response/TicketGroupDto.kt | 8 ++++++- .../com/nexters/boolti/domain/model/Ticket.kt | 1 + .../ticket/detail/TicketDetailScreen.kt | 23 +++++++++++++++++-- .../ticket/detail/TicketDetailViewModel.kt | 19 +++++++++++++++ .../screen/ticket/detail/TicketTempState.kt | 7 ++++++ presentation/src/main/res/values/strings.xml | 1 + 6 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketTempState.kt diff --git a/data/src/main/java/com/nexters/boolti/data/network/response/TicketGroupDto.kt b/data/src/main/java/com/nexters/boolti/data/network/response/TicketGroupDto.kt index 2520cc98..9768f9d7 100644 --- a/data/src/main/java/com/nexters/boolti/data/network/response/TicketGroupDto.kt +++ b/data/src/main/java/com/nexters/boolti/data/network/response/TicketGroupDto.kt @@ -27,6 +27,8 @@ internal data class TicketsDto( val ticketName: String? = null, @SerialName("ticketCount") val ticketCount: Int? = null, + @SerialName("giftUuid") + val giftUuid: String? = null, ) { fun toDomain(): TicketGroup = TicketGroup( userId = userId ?: "", @@ -53,7 +55,8 @@ internal data class TicketsDto( csTicketId = "", showDate = LocalDateTime.MIN, ) - } + }, + isGift = giftUuid != null, ) } @@ -91,6 +94,8 @@ internal data class TicketGroupDto( val tickets: List? = null, @SerialName("userId") val userId: String? = null, + @SerialName("giftUuid") + val giftUuid: String? = null, ) { fun toDomain(): TicketGroup = TicketGroup( userId = userId ?: "", @@ -111,6 +116,7 @@ internal data class TicketGroupDto( tickets = tickets?.map { it.toDomain(showDate = showDate?.toLocalDateTime() ?: LocalDateTime.MIN) } ?: emptyList(), + isGift = giftUuid != null, ) @Serializable diff --git a/domain/src/main/java/com/nexters/boolti/domain/model/Ticket.kt b/domain/src/main/java/com/nexters/boolti/domain/model/Ticket.kt index c33ab8e7..0ac854f8 100644 --- a/domain/src/main/java/com/nexters/boolti/domain/model/Ticket.kt +++ b/domain/src/main/java/com/nexters/boolti/domain/model/Ticket.kt @@ -31,6 +31,7 @@ data class TicketGroup( val hostName: String = "", val hostPhoneNumber: String = "", val tickets: List = emptyList(), + val isGift: Boolean = false, ) { data class Ticket( val ticketId: String = "", diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt index 587b32cc..26e83b4d 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt @@ -159,6 +159,7 @@ private fun TicketDetailScreen( var ticketSectionHeightUntilTicketInfo by remember { mutableFloatStateOf(0f) } val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val ticketTempState by viewModel.ticketTempState.collectAsStateWithLifecycle() val managerCodeState by viewModel.managerCodeState.collectAsStateWithLifecycle() val ticketGroup = uiState.ticketGroup val pagerState = rememberPagerState { ticketGroup.tickets.size } @@ -260,7 +261,12 @@ private fun TicketDetailScreen( .fillMaxWidth() .aspectRatio(317 / 125f) .background( - brush = Brush.verticalGradient(listOf(Black.copy(alpha = 0f), Black)), + brush = Brush.verticalGradient( + listOf( + Black.copy(alpha = 0f), + Black + ) + ), ) ) } @@ -344,7 +350,7 @@ private fun TicketDetailScreen( Spacer(modifier = Modifier.size(20.dp)) RefundPolicySection(uiState.refundPolicy) - if (currentTicket.ticketState == TicketState.Ready) { + if (ticketTempState == TicketTempState.CAN_ENTER) { Text( modifier = Modifier .padding(top = 20.dp, bottom = 60.dp) @@ -356,6 +362,19 @@ private fun TicketDetailScreen( textDecoration = TextDecoration.Underline, ) } + + if (ticketTempState == TicketTempState.REFUNDABLE_GIFT) { + Text( + modifier = Modifier + .padding(top = 20.dp, bottom = 60.dp) + .align(Alignment.CenterHorizontally) + .clickable { }, + text = stringResource(R.string.cancel_registered_gift_button), + style = MaterialTheme.typography.bodySmall, + color = Grey50, + textDecoration = TextDecoration.Underline, + ) + } } PullToRefreshContainer( modifier = Modifier.align(Alignment.TopCenter), diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailViewModel.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailViewModel.kt index 81e1db0a..c3d195b5 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailViewModel.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailViewModel.kt @@ -4,29 +4,35 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.nexters.boolti.domain.exception.ManagerCodeException import com.nexters.boolti.domain.exception.TicketException +import com.nexters.boolti.domain.repository.ShowRepository import com.nexters.boolti.domain.repository.TicketRepository import com.nexters.boolti.domain.request.ManagerCodeRequest import com.nexters.boolti.domain.usecase.GetRefundPolicyUsecase import com.nexters.boolti.presentation.base.BaseViewModel +import com.nexters.boolti.presentation.extension.stateInUi import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.singleOrNull import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.plus +import java.time.LocalDate import javax.inject.Inject @HiltViewModel class TicketDetailViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val repository: TicketRepository, + private val showRepository: ShowRepository, private val getRefundPolicyUsecase: GetRefundPolicyUsecase, ) : BaseViewModel() { private val ticketId: String = requireNotNull(savedStateHandle["ticketId"]) { @@ -39,6 +45,19 @@ class TicketDetailViewModel @Inject constructor( private val _managerCodeState = MutableStateFlow(ManagerCodeState()) val managerCodeState = _managerCodeState.asStateFlow() + val ticketTempState = uiState + .map { it.ticketGroup } + .filter { it.showId.isNotBlank() } + .map { + it.isGift to showRepository.searchById(it.showId).getOrNull()?.date?.toLocalDate() + }.map { (isGift, showDate) -> + when { + LocalDate.now() == showDate -> TicketTempState.CAN_ENTER + isGift && LocalDate.now() < showDate -> TicketTempState.REFUNDABLE_GIFT + else -> TicketTempState.NONE + } + }.stateInUi(viewModelScope, TicketTempState.NONE) + private val _event = Channel() val event = _event.receiveAsFlow() diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketTempState.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketTempState.kt new file mode 100644 index 00000000..47f4df02 --- /dev/null +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketTempState.kt @@ -0,0 +1,7 @@ +package com.nexters.boolti.presentation.screen.ticket.detail + +enum class TicketTempState { + REFUNDABLE_GIFT, + CAN_ENTER, + NONE, +} \ No newline at end of file diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index d5210869..47aaa1c2 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -240,6 +240,7 @@ 입장 코드 입장 코드 입력하기 + 받은 선물 취소하기 입장 코드로 입장 확인 입장 코드는 주최자 계정의 마이 > QR 스캔 > 해당 공연 스캐너에서 확인 가능해요. 입장 코드를 입력해 주세요 From fe7c4c30476fe4054eef0a0e2ccac80ee9b26a2e Mon Sep 17 00:00:00 2001 From: algosketch Date: Sat, 23 Nov 2024 16:41:37 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat=20:=20=EC=84=A0=EB=AC=BC=20=EC=B7=A8?= =?UTF-8?q?=EC=86=8C=20api=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/nexters/boolti/data/datasource/GiftDataSource.kt | 3 +++ .../java/com/nexters/boolti/data/network/api/GiftService.kt | 3 +++ .../nexters/boolti/data/network/response/TicketGroupDto.kt | 2 ++ .../com/nexters/boolti/data/repository/GiftRepositoryImpl.kt | 4 ++++ .../com/nexters/boolti/domain/repository/GiftRepository.kt | 2 ++ 5 files changed, 14 insertions(+) diff --git a/data/src/main/java/com/nexters/boolti/data/datasource/GiftDataSource.kt b/data/src/main/java/com/nexters/boolti/data/datasource/GiftDataSource.kt index 91de3ab4..05e85c4f 100644 --- a/data/src/main/java/com/nexters/boolti/data/datasource/GiftDataSource.kt +++ b/data/src/main/java/com/nexters/boolti/data/datasource/GiftDataSource.kt @@ -32,4 +32,7 @@ internal class GiftDataSource @Inject constructor( suspend fun cancelGift(giftUuid: String): Boolean = service.cancelGift(GiftCancelRequest(giftUuid = giftUuid)) + + suspend fun cancelRegisteredGift(giftUuid: String): Boolean = + service.cancelReceiveGift(GiftCancelRequest(giftUuid = giftUuid)) } \ No newline at end of file diff --git a/data/src/main/java/com/nexters/boolti/data/network/api/GiftService.kt b/data/src/main/java/com/nexters/boolti/data/network/api/GiftService.kt index f66570c7..e4ad8a6f 100644 --- a/data/src/main/java/com/nexters/boolti/data/network/api/GiftService.kt +++ b/data/src/main/java/com/nexters/boolti/data/network/api/GiftService.kt @@ -35,4 +35,7 @@ internal interface GiftService { @POST("app/api/v1/order/cancel-gift") suspend fun cancelGift(@Body request: GiftCancelRequest): Boolean + + @POST("app/api/v1/order/cancel-receive-gift") + suspend fun cancelReceiveGift(@Body request: GiftCancelRequest): Boolean } \ No newline at end of file diff --git a/data/src/main/java/com/nexters/boolti/data/network/response/TicketGroupDto.kt b/data/src/main/java/com/nexters/boolti/data/network/response/TicketGroupDto.kt index 9768f9d7..76773cca 100644 --- a/data/src/main/java/com/nexters/boolti/data/network/response/TicketGroupDto.kt +++ b/data/src/main/java/com/nexters/boolti/data/network/response/TicketGroupDto.kt @@ -57,6 +57,7 @@ internal data class TicketsDto( ) }, isGift = giftUuid != null, + giftUuid = giftUuid, ) } @@ -117,6 +118,7 @@ internal data class TicketGroupDto( it.toDomain(showDate = showDate?.toLocalDateTime() ?: LocalDateTime.MIN) } ?: emptyList(), isGift = giftUuid != null, + giftUuid = giftUuid, ) @Serializable diff --git a/data/src/main/java/com/nexters/boolti/data/repository/GiftRepositoryImpl.kt b/data/src/main/java/com/nexters/boolti/data/repository/GiftRepositoryImpl.kt index 40bf9a98..9f955134 100644 --- a/data/src/main/java/com/nexters/boolti/data/repository/GiftRepositoryImpl.kt +++ b/data/src/main/java/com/nexters/boolti/data/repository/GiftRepositoryImpl.kt @@ -54,4 +54,8 @@ internal class GiftRepositoryImpl @Inject constructor( override fun cancelGift(giftUuid: String): Flow = flow { emit(dataSource.cancelGift(giftUuid)) } + + override fun cancelRegisteredGift(giftUuid: String): Flow = flow { + emit(dataSource.cancelRegisteredGift(giftUuid)) + } } diff --git a/domain/src/main/java/com/nexters/boolti/domain/repository/GiftRepository.kt b/domain/src/main/java/com/nexters/boolti/domain/repository/GiftRepository.kt index 5e54be48..3198c28a 100644 --- a/domain/src/main/java/com/nexters/boolti/domain/repository/GiftRepository.kt +++ b/domain/src/main/java/com/nexters/boolti/domain/repository/GiftRepository.kt @@ -22,4 +22,6 @@ interface GiftRepository { fun getGiftPaymentInfo(giftId: String): Flow fun cancelGift(giftUuid: String): Flow + + fun cancelRegisteredGift(giftUuid: String): Flow } \ No newline at end of file From 5ec6ca00b0975fa40f653e62207b7316a6a28c54 Mon Sep 17 00:00:00 2001 From: algosketch Date: Sat, 23 Nov 2024 17:11:38 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat=20:=20=EC=84=A0=EB=AC=BC=20=EC=B7=A8?= =?UTF-8?q?=EC=86=8C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nexters/boolti/domain/model/Ticket.kt | 1 + .../ticket/detail/TicketDetailScreen.kt | 65 ++++++++++++------- .../ticket/detail/TicketDetailViewModel.kt | 10 +++ presentation/src/main/res/values/strings.xml | 2 + 4 files changed, 54 insertions(+), 24 deletions(-) diff --git a/domain/src/main/java/com/nexters/boolti/domain/model/Ticket.kt b/domain/src/main/java/com/nexters/boolti/domain/model/Ticket.kt index 0ac854f8..04d3b2b3 100644 --- a/domain/src/main/java/com/nexters/boolti/domain/model/Ticket.kt +++ b/domain/src/main/java/com/nexters/boolti/domain/model/Ticket.kt @@ -31,6 +31,7 @@ data class TicketGroup( val hostName: String = "", val hostPhoneNumber: String = "", val tickets: List = emptyList(), + val giftUuid: String?, val isGift: Boolean = false, ) { data class Ticket( diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt index 26e83b4d..a5cb3fce 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt @@ -52,6 +52,7 @@ import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment @@ -96,6 +97,7 @@ import com.nexters.boolti.presentation.component.ShowInquiry import com.nexters.boolti.presentation.component.ToastSnackbarHost import com.nexters.boolti.presentation.extension.toDp import com.nexters.boolti.presentation.extension.toPx +import com.nexters.boolti.presentation.screen.LocalSnackbarController import com.nexters.boolti.presentation.screen.MainDestination import com.nexters.boolti.presentation.screen.qr.QrCoverView import com.nexters.boolti.presentation.theme.BooltiTheme @@ -148,8 +150,11 @@ private fun TicketDetailScreen( val scrollState = rememberScrollState() var showEnterCodeDialog by remember { mutableStateOf(false) } var showTicketNotFoundDialog by remember { mutableStateOf(false) } + var showRefundGiftTicket by rememberSaveable { mutableStateOf(false) } + val snackbarHostState = remember { SnackbarHostState() } + val snackbarHostController = LocalSnackbarController.current val clipboardManager = LocalClipboardManager.current val scope = rememberCoroutineScope() val context = LocalContext.current @@ -350,7 +355,10 @@ private fun TicketDetailScreen( Spacer(modifier = Modifier.size(20.dp)) RefundPolicySection(uiState.refundPolicy) - if (ticketTempState == TicketTempState.CAN_ENTER) { + if ( + currentTicket.ticketState == TicketState.Ready && + ticketTempState == TicketTempState.CAN_ENTER + ) { Text( modifier = Modifier .padding(top = 20.dp, bottom = 60.dp) @@ -368,7 +376,7 @@ private fun TicketDetailScreen( modifier = Modifier .padding(top = 20.dp, bottom = 60.dp) .align(Alignment.CenterHorizontally) - .clickable { }, + .clickable { showRefundGiftTicket = true }, text = stringResource(R.string.cancel_registered_gift_button), style = MaterialTheme.typography.bodySmall, color = Grey50, @@ -384,28 +392,13 @@ private fun TicketDetailScreen( } if (showEnterCodeDialog) { - if (LocalDate.now().toEpochDay() < uiState.ticketGroup.showDate.toLocalDate().toEpochDay()) { - // 아직 공연일 아님 - BTDialog( - showCloseButton = false, - onClickPositiveButton = { showEnterCodeDialog = false }, - onDismiss = { showEnterCodeDialog = false }, - ) { - Text( - text = stringResource(R.string.error_show_not_today), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - } else if (currentTicket.ticketState == TicketState.Ready) { - ManagerCodeDialog( - managerCode = managerCodeState.code, - onManagerCodeChanged = viewModel::setManagerCode, - error = managerCodeState.error, - onDismiss = { showEnterCodeDialog = false }, - onClickConfirm = { viewModel.requestEntrance(it) } - ) - } + ManagerCodeDialog( + managerCode = managerCodeState.code, + onManagerCodeChanged = viewModel::setManagerCode, + error = managerCodeState.error, + onDismiss = { showEnterCodeDialog = false }, + onClickConfirm = { viewModel.requestEntrance(it) } + ) } if (showTicketNotFoundDialog) { @@ -424,6 +417,30 @@ private fun TicketDetailScreen( ) } } + + if (showRefundGiftTicket) { + val completeRefundString = stringResource(R.string.refund_complete_ticket_refund) + + BTDialog( + enableDismiss = false, + showCloseButton = false, + onClickPositiveButton = { + showRefundGiftTicket = false + viewModel.refundGiftTicket() + + if (true) { // todo 성공 시 + onBackClicked() + snackbarHostController.showMessage(message = completeRefundString) + } + }, + ) { + Text( + text = stringResource(R.string.refund_registered_ticket_dialog), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } } @Composable diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailViewModel.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailViewModel.kt index c3d195b5..50d31f3e 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailViewModel.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.nexters.boolti.domain.exception.ManagerCodeException import com.nexters.boolti.domain.exception.TicketException +import com.nexters.boolti.domain.repository.GiftRepository import com.nexters.boolti.domain.repository.ShowRepository import com.nexters.boolti.domain.repository.TicketRepository import com.nexters.boolti.domain.request.ManagerCodeRequest @@ -33,6 +34,7 @@ class TicketDetailViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val repository: TicketRepository, private val showRepository: ShowRepository, + private val giftRepository: GiftRepository, private val getRefundPolicyUsecase: GetRefundPolicyUsecase, ) : BaseViewModel() { private val ticketId: String = requireNotNull(savedStateHandle["ticketId"]) { @@ -109,6 +111,14 @@ class TicketDetailViewModel @Inject constructor( } } + fun refundGiftTicket() { + val giftUuid = uiState.value.ticketGroup.giftUuid ?: return + + giftRepository.cancelRegisteredGift(giftUuid = giftUuid) + .onEach { giftRepository.cancelRegisteredGift(giftUuid) } + .launchIn(viewModelScope + recordExceptionHandler) + } + fun setManagerCode(code: String) { _managerCodeState.update { it.copy(code = code, error = null) } } diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 47aaa1c2..d7841248 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -379,6 +379,8 @@ 환불 수단 취소/환불 규정을 확인했습니다 "알 수 없는 오류로 환불에 실패했어요" + 취소 시 선물을 보낸 분께 알림이 발송되며 결제가 자동 취소됩니다. 취소하시겠습니까? + 받은 선물을 취소했어요 신고하기 From f5318fab981c428cdefad7fd2485f3974bc5caa4 Mon Sep 17 00:00:00 2001 From: algosketch Date: Sun, 24 Nov 2024 02:45:43 +0900 Subject: [PATCH 4/9] =?UTF-8?q?refactor=20:=20uiState=EB=A1=9C=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nexters/boolti/domain/model/Ticket.kt | 2 +- .../ticket/detail/TicketDetailScreen.kt | 6 ++---- .../ticket/detail/TicketDetailUiState.kt | 7 ++++++- .../ticket/detail/TicketDetailViewModel.kt | 20 +------------------ .../screen/ticket/detail/TicketTempState.kt | 7 ------- 5 files changed, 10 insertions(+), 32 deletions(-) delete mode 100644 presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketTempState.kt diff --git a/domain/src/main/java/com/nexters/boolti/domain/model/Ticket.kt b/domain/src/main/java/com/nexters/boolti/domain/model/Ticket.kt index 04d3b2b3..551ed221 100644 --- a/domain/src/main/java/com/nexters/boolti/domain/model/Ticket.kt +++ b/domain/src/main/java/com/nexters/boolti/domain/model/Ticket.kt @@ -31,7 +31,7 @@ data class TicketGroup( val hostName: String = "", val hostPhoneNumber: String = "", val tickets: List = emptyList(), - val giftUuid: String?, + val giftUuid: String? = null, val isGift: Boolean = false, ) { data class Ticket( diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt index a5cb3fce..fe676831 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt @@ -115,7 +115,6 @@ import com.nexters.boolti.presentation.util.UrlParser import com.nexters.boolti.presentation.util.asyncImageBlurModel import com.nexters.boolti.presentation.util.rememberQrBitmapPainter import kotlinx.coroutines.launch -import java.time.LocalDate fun NavGraphBuilder.TicketDetailScreen( navigateTo: (String) -> Unit, @@ -164,7 +163,6 @@ private fun TicketDetailScreen( var ticketSectionHeightUntilTicketInfo by remember { mutableFloatStateOf(0f) } val uiState by viewModel.uiState.collectAsStateWithLifecycle() - val ticketTempState by viewModel.ticketTempState.collectAsStateWithLifecycle() val managerCodeState by viewModel.managerCodeState.collectAsStateWithLifecycle() val ticketGroup = uiState.ticketGroup val pagerState = rememberPagerState { ticketGroup.tickets.size } @@ -357,7 +355,7 @@ private fun TicketDetailScreen( if ( currentTicket.ticketState == TicketState.Ready && - ticketTempState == TicketTempState.CAN_ENTER + uiState.isShowDate ) { Text( modifier = Modifier @@ -371,7 +369,7 @@ private fun TicketDetailScreen( ) } - if (ticketTempState == TicketTempState.REFUNDABLE_GIFT) { + if (uiState.isRefundableGift) { Text( modifier = Modifier .padding(top = 20.dp, bottom = 60.dp) diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailUiState.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailUiState.kt index 546779f8..adea5d49 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailUiState.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailUiState.kt @@ -2,10 +2,15 @@ package com.nexters.boolti.presentation.screen.ticket.detail import com.nexters.boolti.domain.model.LegacyTicket import com.nexters.boolti.domain.model.TicketGroup +import java.time.LocalDate data class TicketDetailUiState( val legacyTicket: LegacyTicket = LegacyTicket(), val refundPolicy: List = emptyList(), val ticketGroup: TicketGroup = TicketGroup(), val currentPage: Int = 0, -) +) { + val isShowDate: Boolean = LocalDate.now() == ticketGroup.showDate.toLocalDate() + val isRefundableGift: Boolean = ticketGroup.isGift && + LocalDate.now() < ticketGroup.showDate.toLocalDate() +} diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailViewModel.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailViewModel.kt index 50d31f3e..a097d6b3 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailViewModel.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailViewModel.kt @@ -5,38 +5,33 @@ import androidx.lifecycle.viewModelScope import com.nexters.boolti.domain.exception.ManagerCodeException import com.nexters.boolti.domain.exception.TicketException import com.nexters.boolti.domain.repository.GiftRepository -import com.nexters.boolti.domain.repository.ShowRepository import com.nexters.boolti.domain.repository.TicketRepository import com.nexters.boolti.domain.request.ManagerCodeRequest import com.nexters.boolti.domain.usecase.GetRefundPolicyUsecase import com.nexters.boolti.presentation.base.BaseViewModel -import com.nexters.boolti.presentation.extension.stateInUi import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.singleOrNull import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.plus -import java.time.LocalDate import javax.inject.Inject @HiltViewModel class TicketDetailViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val repository: TicketRepository, - private val showRepository: ShowRepository, private val giftRepository: GiftRepository, private val getRefundPolicyUsecase: GetRefundPolicyUsecase, ) : BaseViewModel() { + // 실제로는 reservationId가 들어온다. api 변경에 따른 수정 private val ticketId: String = requireNotNull(savedStateHandle["ticketId"]) { "TicketDetailViewModel 에 ticketId 가 전달되지 않았습니다." } @@ -47,19 +42,6 @@ class TicketDetailViewModel @Inject constructor( private val _managerCodeState = MutableStateFlow(ManagerCodeState()) val managerCodeState = _managerCodeState.asStateFlow() - val ticketTempState = uiState - .map { it.ticketGroup } - .filter { it.showId.isNotBlank() } - .map { - it.isGift to showRepository.searchById(it.showId).getOrNull()?.date?.toLocalDate() - }.map { (isGift, showDate) -> - when { - LocalDate.now() == showDate -> TicketTempState.CAN_ENTER - isGift && LocalDate.now() < showDate -> TicketTempState.REFUNDABLE_GIFT - else -> TicketTempState.NONE - } - }.stateInUi(viewModelScope, TicketTempState.NONE) - private val _event = Channel() val event = _event.receiveAsFlow() diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketTempState.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketTempState.kt deleted file mode 100644 index 47f4df02..00000000 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketTempState.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.nexters.boolti.presentation.screen.ticket.detail - -enum class TicketTempState { - REFUNDABLE_GIFT, - CAN_ENTER, - NONE, -} \ No newline at end of file From d4d72e2b92bcb700d3d19342abca01cbeb316761 Mon Sep 17 00:00:00 2001 From: algosketch Date: Sun, 24 Nov 2024 02:50:24 +0900 Subject: [PATCH 5/9] =?UTF-8?q?fix=20:=20=EC=84=A0=EB=AC=BC=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EB=AC=B8=EA=B5=AC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- presentation/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index d7841248..b44795b0 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -210,7 +210,7 @@ 선물이 등록되었어요 로그인 후 선물 등록이 가능합니다.\n로그인해 주세요. 등록하기 - 선물을 등록하면\n선물 취소 및 환불이 불가합니다.\n등록하시겠습니까? + 선물을 등록하시겠습니까? 본인이 결제한 선물입니다.\n선물을 등록하면 다른 분께 보낼 수 없습니다. 등록하시겠습니까? 선물 등록에 실패했어요 선물 등록 중 오류가 발생했습니다.\n웹 링크에서 선물 상태를 확인 후 다시 시도해 주세요 From dd155eacfef83c03bae29309b7e42abfffe3f1c0 Mon Sep 17 00:00:00 2001 From: algosketch Date: Sun, 24 Nov 2024 03:06:40 +0900 Subject: [PATCH 6/9] =?UTF-8?q?fix=20:=20=ED=99=98=EB=B6=88=20=EC=84=B1?= =?UTF-8?q?=EA=B3=B5=20=EC=8B=9C=EC=A0=90=EC=97=90=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EB=9D=84=EC=9A=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../screen/ticket/detail/TicketDetailEvent.kt | 3 ++ .../ticket/detail/TicketDetailScreen.kt | 45 +++++++++---------- .../ticket/detail/TicketDetailViewModel.kt | 6 ++- presentation/src/main/res/values/strings.xml | 3 ++ 4 files changed, 31 insertions(+), 26 deletions(-) diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailEvent.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailEvent.kt index 6737459b..e4d44557 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailEvent.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailEvent.kt @@ -4,4 +4,7 @@ sealed interface TicketDetailEvent { data object ManagerCodeValid : TicketDetailEvent data object OnRefresh : TicketDetailEvent data object NotFound : TicketDetailEvent + data object GiftRefunded : TicketDetailEvent + data object FailedToRefund : TicketDetailEvent + data object NetworkError : TicketDetailEvent } diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt index fe676831..5d5ca3b1 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt @@ -39,7 +39,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -94,7 +93,6 @@ import com.nexters.boolti.presentation.component.BtBackAppBar import com.nexters.boolti.presentation.component.DottedDivider import com.nexters.boolti.presentation.component.InstagramIndicator import com.nexters.boolti.presentation.component.ShowInquiry -import com.nexters.boolti.presentation.component.ToastSnackbarHost import com.nexters.boolti.presentation.extension.toDp import com.nexters.boolti.presentation.extension.toPx import com.nexters.boolti.presentation.screen.LocalSnackbarController @@ -114,7 +112,6 @@ import com.nexters.boolti.presentation.util.TicketShape import com.nexters.boolti.presentation.util.UrlParser import com.nexters.boolti.presentation.util.asyncImageBlurModel import com.nexters.boolti.presentation.util.rememberQrBitmapPainter -import kotlinx.coroutines.launch fun NavGraphBuilder.TicketDetailScreen( navigateTo: (String) -> Unit, @@ -151,11 +148,8 @@ private fun TicketDetailScreen( var showTicketNotFoundDialog by remember { mutableStateOf(false) } var showRefundGiftTicket by rememberSaveable { mutableStateOf(false) } - - val snackbarHostState = remember { SnackbarHostState() } val snackbarHostController = LocalSnackbarController.current val clipboardManager = LocalClipboardManager.current - val scope = rememberCoroutineScope() val context = LocalContext.current var contentWidth by remember { mutableFloatStateOf(0f) } @@ -173,15 +167,26 @@ private fun TicketDetailScreen( val pullToRefreshState = rememberPullToRefreshState() val entranceSuccessMsg = stringResource(R.string.message_ticket_validated) + val failedToRefundMsg = stringResource(R.string.refund_failed_to_registered_gift) + val networkErrorMsg = stringResource(R.string.error_network) + val giftRefundedString = stringResource(R.string.refund_complete_ticket_refund) LaunchedEffect(viewModel.event) { viewModel.event.collect { when (it) { - TicketDetailEvent.ManagerCodeValid -> snackbarHostState.showSnackbar( + TicketDetailEvent.ManagerCodeValid -> snackbarHostController.showMessage( entranceSuccessMsg ) TicketDetailEvent.OnRefresh -> showEnterCodeDialog = false TicketDetailEvent.NotFound -> showTicketNotFoundDialog = true + TicketDetailEvent.FailedToRefund -> snackbarHostController.showMessage( + failedToRefundMsg + ) + + TicketDetailEvent.NetworkError -> snackbarHostController.showMessage(networkErrorMsg) + TicketDetailEvent.GiftRefunded -> { + snackbarHostController.showMessage(giftRefundedString) + } } } } @@ -198,12 +203,6 @@ private fun TicketDetailScreen( onClickBack = onBackClicked, ) }, - snackbarHost = { - ToastSnackbarHost( - modifier = Modifier.padding(bottom = 54.dp), - hostState = snackbarHostState, - ) - } ) { innerPadding -> if (pullToRefreshState.isRefreshing) { viewModel.refresh().invokeOnCompletion { @@ -251,7 +250,11 @@ private fun TicketDetailScreen( // 배경 블러된 이미지 Box(contentAlignment = Alignment.BottomCenter) { AsyncImage( - model = asyncImageBlurModel(context, ticketGroup.poster, radius = 24), + model = asyncImageBlurModel( + context, + ticketGroup.poster, + radius = 24 + ), modifier = Modifier .width(contentWidth.toDp()) .aspectRatio(317 / 570f) @@ -315,16 +318,15 @@ private fun TicketDetailScreen( ) { Notice(notice = ticketGroup.ticketNotice) - val copySuccessMessage = stringResource(id = R.string.ticketing_address_copied_message) + val copySuccessMessage = + stringResource(id = R.string.ticketing_address_copied_message) Inquiry( hostName = ticketGroup.hostName, hostPhoneNumber = ticketGroup.hostPhoneNumber, onClickCopyPlace = { clipboardManager.setText(AnnotatedString(ticketGroup.streetAddress)) if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { - scope.launch { - snackbarHostState.showSnackbar(copySuccessMessage) - } + snackbarHostController.showMessage(copySuccessMessage) } }, onClickNavigateToShowDetail = { @@ -417,19 +419,12 @@ private fun TicketDetailScreen( } if (showRefundGiftTicket) { - val completeRefundString = stringResource(R.string.refund_complete_ticket_refund) - BTDialog( enableDismiss = false, showCloseButton = false, onClickPositiveButton = { showRefundGiftTicket = false viewModel.refundGiftTicket() - - if (true) { // todo 성공 시 - onBackClicked() - snackbarHostController.showMessage(message = completeRefundString) - } }, ) { Text( diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailViewModel.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailViewModel.kt index a097d6b3..817a09e6 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailViewModel.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailViewModel.kt @@ -97,7 +97,11 @@ class TicketDetailViewModel @Inject constructor( val giftUuid = uiState.value.ticketGroup.giftUuid ?: return giftRepository.cancelRegisteredGift(giftUuid = giftUuid) - .onEach { giftRepository.cancelRegisteredGift(giftUuid) } + .onEach { isSuccessful -> + if (isSuccessful) event(TicketDetailEvent.GiftRefunded) + else event(TicketDetailEvent.FailedToRefund) + } + .catch { event(TicketDetailEvent.NetworkError) } .launchIn(viewModelScope + recordExceptionHandler) } diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index b44795b0..314ec156 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -81,6 +81,8 @@ %d/%d자 + 네트워크 오류가 발생했어요 + 지금 불티에 로그인하고\n당신의 공연을 시작해 보세요! 카카오톡으로 시작하기 @@ -381,6 +383,7 @@ "알 수 없는 오류로 환불에 실패했어요" 취소 시 선물을 보낸 분께 알림이 발송되며 결제가 자동 취소됩니다. 취소하시겠습니까? 받은 선물을 취소했어요 + 받은 선물 취소 중 오류가 발생했어요 신고하기 From 0fb7108b7fc869c106b21362973ae5057e556774 Mon Sep 17 00:00:00 2001 From: algosketch Date: Sun, 24 Nov 2024 03:09:25 +0900 Subject: [PATCH 7/9] =?UTF-8?q?refactor=20:=20=EC=95=BD=EA=B0=84=EC=9D=98?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/screen/ticket/detail/TicketDetailScreen.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt index 5d5ca3b1..065d6ab3 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt @@ -50,7 +50,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow @@ -134,7 +133,7 @@ fun NavGraphBuilder.TicketDetailScreen( } } -@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun TicketDetailScreen( modifier: Modifier = Modifier, @@ -462,7 +461,6 @@ private fun Title(showName: String = "") { } } -@OptIn(ExperimentalFoundationApi::class) @Composable private fun QrCodes( modifier: Modifier = Modifier, From e00431980253a20399ecb0ad3b0e2ec0abc8ea9e Mon Sep 17 00:00:00 2001 From: algosketch Date: Sun, 24 Nov 2024 03:19:38 +0900 Subject: [PATCH 8/9] =?UTF-8?q?fix=20:=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC,=20=EB=92=A4=EB=A1=9C=EA=B0=80=EA=B8=B0=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 --- .../screen/ticket/detail/TicketDetailScreen.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt index 065d6ab3..90aed4df 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticket/detail/TicketDetailScreen.kt @@ -6,7 +6,6 @@ import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -74,6 +73,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -185,6 +185,7 @@ private fun TicketDetailScreen( TicketDetailEvent.NetworkError -> snackbarHostController.showMessage(networkErrorMsg) TicketDetailEvent.GiftRefunded -> { snackbarHostController.showMessage(giftRefundedString) + onBackClicked() } } } @@ -420,16 +421,19 @@ private fun TicketDetailScreen( if (showRefundGiftTicket) { BTDialog( enableDismiss = false, - showCloseButton = false, onClickPositiveButton = { - showRefundGiftTicket = false viewModel.refundGiftTicket() + showRefundGiftTicket = false }, + onDismiss = { + showRefundGiftTicket = false + } ) { Text( text = stringResource(R.string.refund_registered_ticket_dialog), style = MaterialTheme.typography.titleLarge, color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center, ) } } From 8401fd53f76be91478e5c8be45a33e543c3b7b73 Mon Sep 17 00:00:00 2001 From: algosketch Date: Sun, 24 Nov 2024 03:20:50 +0900 Subject: [PATCH 9/9] =?UTF-8?q?chore=20:=20=EB=B2=84=EC=A0=84=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c95a3317..8506ef01 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,8 @@ [versions] minSdk = "26" targetSdk = "34" -versionCode = "23" -versionName = "1.7.2" +versionCode = "24" +versionName = "1.8.0" packageName = "com.nexters.boolti" compileSdk = "34" targetJvm = "17"