Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/341 받은 선물 취소하기 #342

Merged
merged 9 commits into from
Nov 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?: "",
Expand All @@ -53,7 +55,9 @@ internal data class TicketsDto(
csTicketId = "",
showDate = LocalDateTime.MIN,
)
}
},
isGift = giftUuid != null,
giftUuid = giftUuid,
)
}

Expand Down Expand Up @@ -91,6 +95,8 @@ internal data class TicketGroupDto(
val tickets: List<TicketDto>? = null,
@SerialName("userId")
val userId: String? = null,
@SerialName("giftUuid")
val giftUuid: String? = null,
) {
fun toDomain(): TicketGroup = TicketGroup(
userId = userId ?: "",
Expand All @@ -111,6 +117,8 @@ internal data class TicketGroupDto(
tickets = tickets?.map {
it.toDomain(showDate = showDate?.toLocalDateTime() ?: LocalDateTime.MIN)
} ?: emptyList(),
isGift = giftUuid != null,
giftUuid = giftUuid,
)

@Serializable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,8 @@ internal class GiftRepositoryImpl @Inject constructor(
override fun cancelGift(giftUuid: String): Flow<Boolean> = flow {
emit(dataSource.cancelGift(giftUuid))
}

override fun cancelRegisteredGift(giftUuid: String): Flow<Boolean> = flow {
emit(dataSource.cancelRegisteredGift(giftUuid))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ data class TicketGroup(
val hostName: String = "",
val hostPhoneNumber: String = "",
val tickets: List<Ticket> = emptyList(),
val giftUuid: String? = null,
val isGift: Boolean = false,
) {
data class Ticket(
val ticketId: String = "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ interface GiftRepository {
fun getGiftPaymentInfo(giftId: String): Flow<ReservationDetail>

fun cancelGift(giftUuid: String): Flow<Boolean>

fun cancelRegisteredGift(giftUuid: String): Flow<Boolean>
}
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -39,7 +38,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
Expand All @@ -51,7 +49,7 @@ 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
import androidx.compose.ui.Alignment
Expand All @@ -75,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
Expand All @@ -93,9 +92,9 @@ 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
import com.nexters.boolti.presentation.screen.MainDestination
import com.nexters.boolti.presentation.screen.qr.QrCoverView
import com.nexters.boolti.presentation.theme.BooltiTheme
Expand All @@ -112,8 +111,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
import java.time.LocalDate

fun NavGraphBuilder.TicketDetailScreen(
navigateTo: (String) -> Unit,
Expand All @@ -136,7 +133,7 @@ fun NavGraphBuilder.TicketDetailScreen(
}
}

@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TicketDetailScreen(
modifier: Modifier = Modifier,
Expand All @@ -148,10 +145,10 @@ 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

var contentWidth by remember { mutableFloatStateOf(0f) }
Expand All @@ -169,15 +166,27 @@ 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)
onBackClicked()
}
}
}
}
Expand All @@ -194,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 {
Expand Down Expand Up @@ -247,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)
Expand All @@ -260,7 +267,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
)
),
)
)
}
Expand Down Expand Up @@ -306,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 = {
Expand Down Expand Up @@ -344,7 +355,10 @@ private fun TicketDetailScreen(
Spacer(modifier = Modifier.size(20.dp))
RefundPolicySection(uiState.refundPolicy)

if (currentTicket.ticketState == TicketState.Ready) {
if (
currentTicket.ticketState == TicketState.Ready &&
uiState.isShowDate
) {
Text(
modifier = Modifier
.padding(top = 20.dp, bottom = 60.dp)
Expand All @@ -356,6 +370,19 @@ private fun TicketDetailScreen(
textDecoration = TextDecoration.Underline,
)
}

if (uiState.isRefundableGift) {
Text(
modifier = Modifier
.padding(top = 20.dp, bottom = 60.dp)
.align(Alignment.CenterHorizontally)
.clickable { showRefundGiftTicket = true },
text = stringResource(R.string.cancel_registered_gift_button),
style = MaterialTheme.typography.bodySmall,
color = Grey50,
textDecoration = TextDecoration.Underline,
)
}
}
PullToRefreshContainer(
modifier = Modifier.align(Alignment.TopCenter),
Expand All @@ -365,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) {
Expand All @@ -405,6 +417,26 @@ private fun TicketDetailScreen(
)
}
}

if (showRefundGiftTicket) {
BTDialog(
enableDismiss = false,
onClickPositiveButton = {
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,
)
}
}
}

@Composable
Expand Down Expand Up @@ -433,7 +465,6 @@ private fun Title(showName: String = "") {
}
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun QrCodes(
modifier: Modifier = Modifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> = 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()
}
Loading
Loading