diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d7df3eb2..37525e6c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -45,6 +45,9 @@ + 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 6e02b0d1..bd08713b 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 @@ -16,7 +16,7 @@ internal class GiftDataSource @Inject constructor( suspend fun approveGiftPayment(request: GiftApproveRequest): ApproveGiftPaymentResponse = service.approveGiftPayment(request) - suspend fun getGift(giftId: String): GiftResponse = service.getGift(giftId) + suspend fun getGift(giftUuid: String): GiftResponse = service.getGift(giftUuid) suspend fun getGiftImages(): List = service.getGiftImages() } \ 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 53d1be08..8eaa5a0f 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 @@ -17,8 +17,8 @@ internal interface GiftService { @POST("/app/api/v1/order/gift-approve-payment") suspend fun approveGiftPayment(@Body request: GiftApproveRequest): ApproveGiftPaymentResponse - @GET("/app/api/v1/gift/{giftId}") - suspend fun getGift(@Path("giftId") giftId: String): GiftResponse + @GET("/app/api/v1/gift/{giftUuid}") + suspend fun getGift(@Path("giftUuid") giftUuid: String): GiftResponse @GET("/app/api/v1/gift/img-list") suspend fun getGiftImages(): List diff --git a/data/src/main/java/com/nexters/boolti/data/network/request/GiftReceiveRequest.kt b/data/src/main/java/com/nexters/boolti/data/network/request/GiftReceiveRequest.kt index 567ffa3c..1f766d0a 100644 --- a/data/src/main/java/com/nexters/boolti/data/network/request/GiftReceiveRequest.kt +++ b/data/src/main/java/com/nexters/boolti/data/network/request/GiftReceiveRequest.kt @@ -1,5 +1,5 @@ package com.nexters.boolti.data.network.request data class GiftReceiveRequest( - val giftId: String, + val giftUuid: String, ) \ No newline at end of file diff --git a/data/src/main/java/com/nexters/boolti/data/network/response/ApproveGiftPaymentResponse.kt b/data/src/main/java/com/nexters/boolti/data/network/response/ApproveGiftPaymentResponse.kt index 4125ccd3..3afade57 100644 --- a/data/src/main/java/com/nexters/boolti/data/network/response/ApproveGiftPaymentResponse.kt +++ b/data/src/main/java/com/nexters/boolti/data/network/response/ApproveGiftPaymentResponse.kt @@ -8,12 +8,14 @@ data class ApproveGiftPaymentResponse( val orderId: String, val reservationId: String, val giftId: String, + val giftUuid: String, ) { fun toDomain(): ApproveGiftPayment { return ApproveGiftPayment( orderId = orderId, reservationId = reservationId, - giftId = giftId + giftId = giftId, + giftUuid = giftUuid, ) } } \ No newline at end of file diff --git a/data/src/main/java/com/nexters/boolti/data/network/response/GiftResponse.kt b/data/src/main/java/com/nexters/boolti/data/network/response/GiftResponse.kt index 0ce94fec..d331171f 100644 --- a/data/src/main/java/com/nexters/boolti/data/network/response/GiftResponse.kt +++ b/data/src/main/java/com/nexters/boolti/data/network/response/GiftResponse.kt @@ -1,32 +1,39 @@ package com.nexters.boolti.data.network.response +import com.nexters.boolti.data.util.toLocalDate import com.nexters.boolti.domain.model.Gift import kotlinx.serialization.Serializable @Serializable data class GiftResponse( val id: String, + val giftUuid: String, val orderId: String, val reservationId: String, val giftImgId: String, + val giftImgPath: String, val message: String, val senderName: String, val senderPhoneNumber: String, val recipientName: String, val recipientPhoneNumber: String, + val salesEndTime: String, val isDone: Boolean, ) { fun toDomain(): Gift { return Gift( id = id, + uuid = giftUuid, orderId = orderId, reservationId = reservationId, giftImgId = giftImgId, + imagePath = giftImgPath, message = message, senderName = senderName, senderPhoneNumber = senderPhoneNumber, recipientName = recipientName, recipientPhoneNumber = recipientPhoneNumber, + salesEndTime = salesEndTime.toLocalDate(), isDone = isDone, ) } 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 795fc14e..4faa3ca5 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 @@ -15,16 +15,16 @@ import javax.inject.Inject internal class GiftRepositoryImpl @Inject constructor( private val dataSource: GiftDataSource ) : GiftRepository { - override fun receiveGift(giftId: String): Flow = flow { - emit(dataSource.receiveGift(GiftReceiveRequest(giftId))) + override fun receiveGift(giftUuid: String): Flow = flow { + emit(dataSource.receiveGift(GiftReceiveRequest(giftUuid))) } override fun approveGiftPayment(request: GiftApproveRequest): Flow = flow { emit(dataSource.approveGiftPayment(request).toDomain()) } - override fun getGift(giftId: String): Flow = flow { - emit(dataSource.getGift(giftId).toDomain()) + override fun getGift(giftUuid: String): Flow = flow { + emit(dataSource.getGift(giftUuid).toDomain()) } override fun getGiftImages(): Flow> = flow { diff --git a/domain/src/main/java/com/nexters/boolti/domain/model/ApproveGiftPayment.kt b/domain/src/main/java/com/nexters/boolti/domain/model/ApproveGiftPayment.kt index 0b12f78f..33041985 100644 --- a/domain/src/main/java/com/nexters/boolti/domain/model/ApproveGiftPayment.kt +++ b/domain/src/main/java/com/nexters/boolti/domain/model/ApproveGiftPayment.kt @@ -4,4 +4,5 @@ data class ApproveGiftPayment( val orderId: String, val reservationId: String, val giftId: String, + val giftUuid: String, ) diff --git a/domain/src/main/java/com/nexters/boolti/domain/model/Gift.kt b/domain/src/main/java/com/nexters/boolti/domain/model/Gift.kt index e8f7956b..f0cbdaab 100644 --- a/domain/src/main/java/com/nexters/boolti/domain/model/Gift.kt +++ b/domain/src/main/java/com/nexters/boolti/domain/model/Gift.kt @@ -1,14 +1,19 @@ package com.nexters.boolti.domain.model +import java.time.LocalDate + data class Gift( val id: String, + val uuid: String, val orderId: String, val reservationId: String, val giftImgId: String, + val imagePath: String, val message: String, val senderName: String, val senderPhoneNumber: String, val recipientName: String, val recipientPhoneNumber: String, + val salesEndTime: LocalDate, val isDone: Boolean, ) 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 39ffbd94..e860bcfd 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 @@ -7,11 +7,11 @@ import com.nexters.boolti.domain.request.GiftApproveRequest import kotlinx.coroutines.flow.Flow interface GiftRepository { - fun receiveGift(giftId: String): Flow + fun receiveGift(giftUuid: String): Flow fun approveGiftPayment(request: GiftApproveRequest): Flow - fun getGift(giftId: String): Flow + fun getGift(giftUuid: String): Flow fun getGiftImages(): Flow> } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 54738dcd..c4f8259f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -109,6 +109,7 @@ firebase-dynamic-links = { module = "com.google.firebase:firebase-dynamic-links- zoomable = { module = "net.engawapg.lib:zoomable", version.ref = "zoomable" } zxing-android-embedded = { module = "com.journeyapps:zxing-android-embedded", version.ref = "zxing" } kakao-login = { group = "com.kakao.sdk", name = "v2-user", version.ref = "kakao" } +kakao-share = { group = "com.kakao.sdk", name = "v2-share", version.ref = "kakao" } retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "serializationConverter" } timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" } diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index ac7568e4..b884ff72 100644 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -18,6 +18,7 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles("consumer-rules.pro") buildConfigField("String", "PACKAGE_NAME", "\"${libs.versions.packageName.get()}\"") + buildConfigField("String", "DEV_SUBDOMAIN", getLocalProperty("DEV_SUBDOMAIN")) } buildTypes { @@ -81,6 +82,7 @@ dependencies { implementation(libs.lottie) implementation(libs.bundles.coil) api(libs.kakao.login) + implementation(libs.kakao.share) implementation(libs.timber) implementation(libs.zxing.android.embedded) diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/MainDestination.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/MainDestination.kt index 4fbfab36..f5d80ba1 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/MainDestination.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/MainDestination.kt @@ -39,15 +39,15 @@ sealed class MainDestination(val route: String) { ) } - data object GiftComplete : MainDestination(route = "giftComplete?reservationId={reservationId}&giftId={giftId}") { + data object GiftComplete : MainDestination(route = "giftComplete?reservationId={reservationId}&giftUuid={giftUuid}") { val arguments = listOf( navArgument(reservationId) { type = NavType.StringType }, ) fun createRoute( reservationId: String, - giftId: String, - ): String = "giftComplete?reservationId=$reservationId&giftId=$giftId" + giftUuid: String, + ): String = "giftComplete?reservationId=$reservationId&giftUuid=$giftUuid" } data object TicketDetail : MainDestination(route = "tickets") { diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/gift/GiftNavigation.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/gift/GiftNavigation.kt index e00f5d04..9ac4b6d2 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/gift/GiftNavigation.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/gift/GiftNavigation.kt @@ -18,8 +18,8 @@ fun NavGraphBuilder.addGiftScreen( modifier = modifier, popBackStack = popBackStack, navigateToBusiness = { navigateTo(MainDestination.Business.route) }, - navigateToComplete = { reservationId, giftId -> - navigateTo(MainDestination.GiftComplete.createRoute(reservationId, giftId)) + navigateToComplete = { reservationId, giftUuid -> + navigateTo(MainDestination.GiftComplete.createRoute(reservationId, giftUuid)) } ) } diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/gift/GiftScreen.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/gift/GiftScreen.kt index b9523333..c5f87a25 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/gift/GiftScreen.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/gift/GiftScreen.kt @@ -81,7 +81,7 @@ import com.nexters.boolti.tosspayments.TossPaymentWidgetActivity.Companion.RESUL fun GiftScreen( popBackStack: () -> Unit, navigateToBusiness: () -> Unit, - navigateToComplete: (reservationId: String, giftId: String) -> Unit, + navigateToComplete: (reservationId: String, giftUuid: String) -> Unit, modifier: Modifier = Modifier, viewModel: GiftViewModel = hiltViewModel(), ) { @@ -100,10 +100,10 @@ fun GiftScreen( val reservationId = intent.getStringExtra("reservationId") ?: return@rememberLauncherForActivityResult - val giftId = intent.getStringExtra("giftId") + val giftUuid = intent.getStringExtra("giftUuid") ?: return@rememberLauncherForActivityResult - navigateToComplete(reservationId, giftId) + navigateToComplete(reservationId, giftUuid) } RESULT_SOLD_OUT -> showTicketSoldOutDialog = true diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/giftcomplete/GiftCompleteScreen.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/giftcomplete/GiftCompleteScreen.kt index 1d880622..70d39490 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/giftcomplete/GiftCompleteScreen.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/giftcomplete/GiftCompleteScreen.kt @@ -1,5 +1,6 @@ package com.nexters.boolti.presentation.screen.giftcomplete +import android.content.Context import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -33,8 +34,16 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.kakao.sdk.share.ShareClient +import com.kakao.sdk.template.model.Button +import com.kakao.sdk.template.model.Content +import com.kakao.sdk.template.model.FeedTemplate +import com.kakao.sdk.template.model.Link +import com.nexters.boolti.domain.model.Gift import com.nexters.boolti.domain.model.PaymentType import com.nexters.boolti.domain.model.ReservationDetail +import com.nexters.boolti.presentation.BuildConfig import com.nexters.boolti.presentation.R import com.nexters.boolti.presentation.extension.cardCodeToCompanyName import com.nexters.boolti.presentation.screen.payment.PaymentToolbar @@ -48,6 +57,7 @@ import com.nexters.boolti.presentation.theme.Grey95 import com.nexters.boolti.presentation.theme.KakaoYellow import com.nexters.boolti.presentation.theme.marginHorizontal import com.nexters.boolti.presentation.theme.point4 +import timber.log.Timber @Composable fun GiftCompleteScreen( @@ -57,6 +67,7 @@ fun GiftCompleteScreen( ) { val reservation by viewModel.reservation.collectAsStateWithLifecycle() val gift by viewModel.gift.collectAsStateWithLifecycle() + val context = LocalContext.current BackHandler(onBack = onClickClose) @@ -70,6 +81,11 @@ fun GiftCompleteScreen( .padding(innerPadding) .padding(horizontal = marginHorizontal) ) { + val month = gift?.salesEndTime?.month?.value ?: 0 + val day = gift?.salesEndTime?.dayOfMonth ?: 0 + val dateText = stringResource(id = R.string.gift_expiration_date, month, day) + val buttonsText = stringResource(id = R.string.gift_check) + Text( modifier = Modifier.padding(vertical = 20.dp), text = stringResource(id = R.string.gift_complete_note), @@ -93,7 +109,15 @@ fun GiftCompleteScreen( shape = RoundedCornerShape(4.dp), colors = ButtonDefaults.outlinedButtonColors(containerColor = KakaoYellow), contentPadding = PaddingValues(horizontal = 20.dp), - onClick = { TODO() } + onClick = { + if (ShareClient.instance.isKakaoTalkSharingAvailable(context)) { + gift?.let { + sendMessage(context, it, dateText, buttonsText) + } + } else { + // TODO: 카카오톡 미설치 케이스 (아직은 고려 X) + } + } ) { Box( modifier = Modifier.fillMaxWidth(), @@ -129,6 +153,44 @@ fun GiftCompleteScreen( } } +private fun sendMessage(context: Context, gift: Gift, dateText: String, buttonText: String) { + val subDomain = if (BuildConfig.DEBUG) BuildConfig.DEV_SUBDOMAIN else "" + val giftUrl = "https://${subDomain}boolti.in/gift/${gift.uuid}" + + val defaultFeed = FeedTemplate( + content = Content( + title = "To. ${gift.recipientName}", + description = dateText, + imageUrl = gift.imagePath, + link = Link( + webUrl = giftUrl, + mobileWebUrl = giftUrl + ) + ), + buttons = listOf( + Button( + buttonText, + Link( + webUrl = giftUrl, + mobileWebUrl = giftUrl + ) + ), + ) + ) + + ShareClient.instance.shareDefault(context, defaultFeed) { sharingResult, error -> + if (error != null) { + FirebaseCrashlytics.getInstance().recordException(error) + Timber.e(error) + } else if (sharingResult != null) { + context.startActivity(sharingResult.intent) + + Timber.w("Warning Msg: ${sharingResult.warningMsg}") + Timber.w("Argument Msg: ${sharingResult.argumentMsg}") + } + } +} + @Composable private fun InfoRow( modifier: Modifier = Modifier, diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/giftcomplete/GiftCompleteViewModel.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/giftcomplete/GiftCompleteViewModel.kt index a1891a96..60da49c3 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/giftcomplete/GiftCompleteViewModel.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/giftcomplete/GiftCompleteViewModel.kt @@ -19,8 +19,8 @@ class GiftCompleteViewModel @Inject constructor( private val reservationId: String = requireNotNull(savedStateHandle["reservationId"]) { "reservationId 가 전달되지 않았습니다." } - private val giftId: String = requireNotNull(savedStateHandle["giftId"]) { - "giftId 가 전달되지 않았습니다." + private val giftUuid: String = requireNotNull(savedStateHandle["giftUuid"]) { + "giftUuid 가 전달되지 않았습니다." } val reservation = ticketingRepository @@ -31,7 +31,7 @@ class GiftCompleteViewModel @Inject constructor( initialValue = null, ) - val gift = giftRepository.getGift(giftId) + val gift = giftRepository.getGift(giftUuid) .stateIn( scope = viewModelScope, started = SharingStarted.Eagerly, diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 66b43c7b..26be2cd3 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -178,6 +178,8 @@ 받는 분 선택하기 선택된 선물 이미지 선물 편지에 포함될 이미지 + %d월 %d일까지 불티앱에서 선물을 등록해주세요. + 선물 확인하기 선물 등록 기간은 해당 공연의 티켓 판매 기간까지입니다. 마이 > 결제 내역 > 결제 내역 상세에서 선물을 취소할 수 있습니다. diff --git a/tosspayments/src/main/java/com/nexters/boolti/tosspayments/PaymentEvent.kt b/tosspayments/src/main/java/com/nexters/boolti/tosspayments/PaymentEvent.kt index 0d1fffcc..b54f48f3 100644 --- a/tosspayments/src/main/java/com/nexters/boolti/tosspayments/PaymentEvent.kt +++ b/tosspayments/src/main/java/com/nexters/boolti/tosspayments/PaymentEvent.kt @@ -4,7 +4,7 @@ sealed interface PaymentEvent { data class Approved( val orderId: String, val reservationId: String, - val giftId: String = "", + val giftUuid: String = "", ) : PaymentEvent data object TicketSoldOut : PaymentEvent diff --git a/tosspayments/src/main/java/com/nexters/boolti/tosspayments/TossPaymentWidgetActivity.kt b/tosspayments/src/main/java/com/nexters/boolti/tosspayments/TossPaymentWidgetActivity.kt index bd938282..6b96cc11 100644 --- a/tosspayments/src/main/java/com/nexters/boolti/tosspayments/TossPaymentWidgetActivity.kt +++ b/tosspayments/src/main/java/com/nexters/boolti/tosspayments/TossPaymentWidgetActivity.kt @@ -135,7 +135,7 @@ class TossPaymentWidgetActivity : AppCompatActivity() { val intent = Intent().apply { putExtra("orderId", event.orderId) putExtra("reservationId", event.reservationId) - putExtra("giftId", event.giftId) + putExtra("giftUuid", event.giftUuid) } setResult(RESULT_SUCCESS, intent) finish() diff --git a/tosspayments/src/main/java/com/nexters/boolti/tosspayments/TossPaymentsWidgetViewModel.kt b/tosspayments/src/main/java/com/nexters/boolti/tosspayments/TossPaymentsWidgetViewModel.kt index eef5abc3..d78154c3 100644 --- a/tosspayments/src/main/java/com/nexters/boolti/tosspayments/TossPaymentsWidgetViewModel.kt +++ b/tosspayments/src/main/java/com/nexters/boolti/tosspayments/TossPaymentsWidgetViewModel.kt @@ -125,7 +125,7 @@ class TossPaymentsWidgetViewModel @Inject constructor( event(PaymentEvent.TicketSoldOut) } }.singleOrNull()?.let { - event(PaymentEvent.Approved(it.orderId, it.reservationId, it.giftId)) + event(PaymentEvent.Approved(it.orderId, it.reservationId, it.giftUuid)) } }