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/259] 선물하기 무료 케이스 및 다양한 케이스 처리 #267

Merged
merged 6 commits into from
Jul 27, 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 @@ -5,6 +5,7 @@ import com.nexters.boolti.data.network.request.GiftReceiveRequest
import com.nexters.boolti.data.network.response.ApproveGiftPaymentResponse
import com.nexters.boolti.data.network.response.GiftResponse
import com.nexters.boolti.data.network.response.ImageResponse
import com.nexters.boolti.domain.request.FreeGiftRequest
import com.nexters.boolti.domain.request.GiftApproveRequest
import javax.inject.Inject

Expand All @@ -16,6 +17,9 @@ internal class GiftDataSource @Inject constructor(
suspend fun approveGiftPayment(request: GiftApproveRequest): ApproveGiftPaymentResponse =
service.approveGiftPayment(request)

suspend fun createFreeGift(request: FreeGiftRequest): ApproveGiftPaymentResponse =
service.createFreeGift(request)

suspend fun getGift(giftUuid: String): GiftResponse = service.getGift(giftUuid)

suspend fun getGiftImages(): List<ImageResponse> = service.getGiftImages()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.nexters.boolti.data.network.request.GiftReceiveRequest
import com.nexters.boolti.data.network.response.ApproveGiftPaymentResponse
import com.nexters.boolti.data.network.response.GiftResponse
import com.nexters.boolti.data.network.response.ImageResponse
import com.nexters.boolti.domain.request.FreeGiftRequest
import com.nexters.boolti.domain.request.GiftApproveRequest
import retrofit2.http.Body
import retrofit2.http.GET
Expand All @@ -17,6 +18,9 @@ internal interface GiftService {
@POST("/app/api/v1/order/gift-approve-payment")
suspend fun approveGiftPayment(@Body request: GiftApproveRequest): ApproveGiftPaymentResponse

@POST("/app/api/v1/order/free-gift-reservation")
suspend fun createFreeGift(@Body request: FreeGiftRequest): ApproveGiftPaymentResponse

@GET("/app/api/v1/gift/{giftUuid}")
suspend fun getGift(@Path("giftUuid") giftUuid: String): GiftResponse

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable

@Serializable
data class ApproveGiftPaymentResponse(
val orderId: String,
val orderId: String = "-1", // 무료 티켓일 경우 orderId가 null
mangbaam marked this conversation as resolved.
Show resolved Hide resolved
val reservationId: String,
val giftId: String,
val giftUuid: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import kotlinx.serialization.Serializable
data class GiftResponse(
val id: String,
val giftUuid: String,
val orderId: String,
val orderId: String?,
val reservationId: String,
val giftImgId: String,
val giftImgPath: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.nexters.boolti.domain.model.ApproveGiftPayment
import com.nexters.boolti.domain.model.Gift
import com.nexters.boolti.domain.model.ImagePair
import com.nexters.boolti.domain.repository.GiftRepository
import com.nexters.boolti.domain.request.FreeGiftRequest
import com.nexters.boolti.domain.request.GiftApproveRequest
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
Expand All @@ -23,6 +24,10 @@ internal class GiftRepositoryImpl @Inject constructor(
emit(dataSource.approveGiftPayment(request).toDomain())
}

override fun sendFreeGift(request: FreeGiftRequest): Flow<ApproveGiftPayment> = flow {
emit(dataSource.createFreeGift(request).toDomain())
}

override fun getGift(giftUuid: String): Flow<Gift> = flow {
emit(dataSource.getGift(giftUuid).toDomain())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import java.time.LocalDate
data class Gift(
val id: String,
val uuid: String,
val orderId: String,
val orderId: String?,
val reservationId: String,
val giftImgId: String,
val imagePath: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ package com.nexters.boolti.domain.repository
import com.nexters.boolti.domain.model.ApproveGiftPayment
import com.nexters.boolti.domain.model.Gift
import com.nexters.boolti.domain.model.ImagePair
import com.nexters.boolti.domain.request.FreeGiftRequest
import com.nexters.boolti.domain.request.GiftApproveRequest
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

interface GiftRepository {
fun receiveGift(giftUuid: String): Flow<Boolean>

fun approveGiftPayment(request: GiftApproveRequest): Flow<ApproveGiftPayment>

fun sendFreeGift(request: FreeGiftRequest): Flow<ApproveGiftPayment>

fun getGift(giftUuid: String): Flow<Gift>

fun getGiftImages(): Flow<List<ImagePair>>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.nexters.boolti.domain.request

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class FreeGiftRequest(
val amount: Int,
val showId: String,
val salesTicketTypeId: String,
val ticketCount: Int,
@SerialName("giftImgId") val giftImageId: String,
val message: String,
val senderName: String,
val senderPhoneNumber: String,
val recipientName: String,
val recipientPhoneNumber: String,
)
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ kakao = "2.19.0"
timber = "5.0.1"
mockk = "1.13.8"
tosspayments = "0.1.15"
immutable = "0.3.7"

[libraries]
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activity-ktx" }
Expand Down Expand Up @@ -113,6 +114,7 @@ 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" }
immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "immutable" }
HamBP marked this conversation as resolved.
Show resolved Hide resolved

[plugins]
android-application = { id = "com.android.application", version.ref = "android" }
Expand Down
1 change: 1 addition & 0 deletions presentation/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ dependencies {
implementation(libs.material)
implementation(libs.bundles.lifecycle)
implementation(libs.bundles.compose)
implementation(libs.immutable)
implementation(platform(libs.andoridx.compose.compose.bom))
implementation(libs.bundles.coroutines)
implementation(libs.bundles.firebase)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package com.nexters.boolti.presentation.screen.gift

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.nexters.boolti.domain.model.ImagePair
import com.nexters.boolti.presentation.R
import com.nexters.boolti.presentation.theme.BooltiTheme
import com.nexters.boolti.presentation.theme.Grey10
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList


@Composable
fun CardSelection(
message: String,
onMessageChanged: (String) -> Unit,
images: ImmutableList<ImagePair>,
selectedImage: ImagePair?,
onImageSelected: (ImagePair) -> Unit,
) {
Column(
modifier = Modifier.padding(top = 24.dp, bottom = 48.dp),
) {
Column(
modifier = Modifier
.padding(horizontal = 32.dp)
.clip(RoundedCornerShape(8.dp))
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color(0xFFFF5A14),
Color(0xFFFFA883),
)
)
)
.border(
width = 1.dp,
color = Color(0xFFFFA883),
shape = RoundedCornerShape(8.dp)
)
.padding(horizontal = 20.dp, vertical = 32.dp)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
val maximumLength = 40
val messageLengthUnit = stringResource(id = R.string.gift_message_length_unit)

BasicTextField(
modifier = Modifier
.fillMaxWidth()
.height(80.dp),
value = message,
onValueChange = onMessageChanged,
textStyle = MaterialTheme.typography.titleLarge.copy(
color = Color.White,
textAlign = TextAlign.Center
),
cursorBrush = SolidColor(Color.White)
)
Text(
modifier = Modifier.padding(top = 12.dp),
text = "${message.length}/${maximumLength}${messageLengthUnit}",
style = MaterialTheme.typography.labelMedium.copy(color = Grey10),
)

AsyncImage(
model = selectedImage?.originImage,
contentDescription = stringResource(id = R.string.gift_selected_image),
modifier = Modifier
.padding(top = 28.dp)
.fillMaxWidth()
.aspectRatio(3 / 2f)
.background(Color.White),
contentScale = ContentScale.Crop,
)
}

// TODO: 현재 선택 가능한 카드가 1개, 이후 카드 개수가 추가되면 주석 풀기!
// CardCarousel(
// modifier = Modifier
// .padding(top = 44.dp)
// .fillMaxWidth(),
// images = images,
// selectedImage = selectedImage,
// onImageSelected = onImageSelected,
// )
}
}

@Composable
private fun CardCarousel(
images: ImmutableList<ImagePair>,
selectedImage: ImagePair?,
onImageSelected: (ImagePair) -> Unit,
modifier: Modifier = Modifier,
) {
LazyRow(
modifier = modifier,
contentPadding = PaddingValues(start = 32.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
items(images) { image ->
val cardModifier = if (image == selectedImage) {
Modifier.border(
width = 1.dp,
color = MaterialTheme.colorScheme.primary,
shape = RoundedCornerShape(4.dp)
)
} else {
Modifier
}

AsyncImage(
model = image.thumbnailImage,
contentDescription = stringResource(id = R.string.gift_image),
modifier = cardModifier
.size(52.dp)
.clip(RoundedCornerShape(4.dp))
.clickable {
onImageSelected(image)
},
contentScale = ContentScale.Crop,
)
}
}
}

@Preview
@Composable
private fun CardSelectionPreview() {
BooltiTheme {
CardSelection(
message = "공연에 초대합니다.",
onMessageChanged = {},
images = (1..10).map {
ImagePair(
it.toString(),
"https://picsum.photos/200",
"https://picsum.photos/200"
)
}.toPersistentList(),
selectedImage = ImagePair("", "", ""),
onImageSelected = {}
)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.nexters.boolti.presentation.screen.gift

sealed interface GiftEvent {
data class GiftSuccess(val reservationId: String, val showId: String) : GiftEvent
data class GiftSuccess(val reservationId: String, val giftUuid: String) : GiftEvent
data class ProgressPayment(val userId: String, val orderId: String) : GiftEvent
data object NoRemainingQuantity : GiftEvent
}
Loading
Loading