diff --git a/app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/response/DutchPayDetailResponseDto.kt b/app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/response/DutchPayDetailResponseDto.kt index 4383686..bef5f09 100644 --- a/app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/response/DutchPayDetailResponseDto.kt +++ b/app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/response/DutchPayDetailResponseDto.kt @@ -13,19 +13,6 @@ data class DutchPayDetailResponseDto( val shares: List, val roundedPerPerson: Int?, val payMore: Boolean, - val createdAt: String -) - -@Serializable -data class CreatorDto( - val id: Long, - val name: String -) - -@Serializable -data class ShareDto( - val userId: Long, - val name: String, - val amount: Int, - val status: String + val createdAt: String, + val requestUserId: Long ) diff --git a/app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/response/DutchPayRequestDetailResponseDto.kt b/app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/response/DutchPayRequestDetailResponseDto.kt index b9373e5..75f040f 100644 --- a/app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/response/DutchPayRequestDetailResponseDto.kt +++ b/app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/response/DutchPayRequestDetailResponseDto.kt @@ -1,16 +1,30 @@ package com.ssafy.tiggle.data.model.dutchpay.response +import kotlinx.serialization.Serializable +@Serializable data class DutchPayRequestDetailResponseDto( - val dutchpayId: Long, + val id: Long, + val requestUserId: Long, val title: String, val message: String, - val requesterName: String, - val participantCount: Int, val totalAmount: Long, - val requestedAt: String, - val myAmount: Long, - val originalAmount: Long, - val tiggleAmount: Long, - val payMoreDefault: Boolean, - val creator: Boolean + val status: String, + val creator: CreatorDto, + val shares: List, + val roundedPerPerson: Long, + val createdAt: String +) +@Serializable +data class CreatorDto( + val id: Long, + val name: String +) + +@Serializable +data class ShareDto( + val userId: Long, + val name: String, + val amount: Long, + val status: String, + val tiggleAmount: Long? ) \ No newline at end of file diff --git a/app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/response/UserSummaryDto.kt b/app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/response/UserSummaryDto.kt index 1a63e8f..2a531cc 100644 --- a/app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/response/UserSummaryDto.kt +++ b/app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/response/UserSummaryDto.kt @@ -7,10 +7,14 @@ import com.ssafy.tiggle.domain.entity.dutchpay.UserSummary */ data class UserSummaryDto( val id: Long, - val name: String + val name: String, + val university: String? = null, + val department: String? = null ) { fun toDomain(): UserSummary = UserSummary( id = id, - name = name + name = name, + university = university, + department = department ) } \ No newline at end of file diff --git a/app/src/main/java/com/ssafy/tiggle/data/repository/DutchPayRepositoryImpl.kt b/app/src/main/java/com/ssafy/tiggle/data/repository/DutchPayRepositoryImpl.kt index 2b678fb..ad1e687 100644 --- a/app/src/main/java/com/ssafy/tiggle/data/repository/DutchPayRepositoryImpl.kt +++ b/app/src/main/java/com/ssafy/tiggle/data/repository/DutchPayRepositoryImpl.kt @@ -49,19 +49,23 @@ class DutchPayRepositoryImpl @Inject constructor( if (response.isSuccessful) { val responseData = response.body()?.data if (responseData != null) { + + val currentUserId = responseData.requestUserId + val currentShare = responseData.shares.find { it.userId == currentUserId } + val detail = DutchPayRequestDetail( - dutchPayId = responseData.dutchpayId, + dutchPayId = responseData.id, title = responseData.title, message = responseData.message, - requesterName = responseData.requesterName, - participantCount = responseData.participantCount, + requesterName = responseData.creator.name, + participantCount = responseData.shares.size, totalAmount = responseData.totalAmount, - requestedAt = responseData.requestedAt, - myAmount = responseData.myAmount, - originalAmount = responseData.originalAmount, - tiggleAmount = responseData.tiggleAmount, - payMoreDefault = responseData.payMoreDefault, - isCreator = responseData.creator + requestedAt = responseData.createdAt, + myAmount = currentShare?.amount ?: 0L, + originalAmount = currentShare?.amount ?: 0L, + tiggleAmount = responseData.roundedPerPerson - (currentShare?.amount ?: 0L), + payMoreDefault = true, // 기본값으로 설정 + isCreator = responseData.creator.id == currentUserId ) Result.success(detail) } else { @@ -96,13 +100,15 @@ class DutchPayRepositoryImpl @Inject constructor( Share( userId = shareDto.userId, name = shareDto.name, - amount = shareDto.amount, + amount = shareDto.amount.toInt(), + tiggleAmount = shareDto.tiggleAmount?.toInt(), status = shareDto.status ) }, roundedPerPerson = responseData.roundedPerPerson, payMore = responseData.payMore, - createdAt = responseData.createdAt + createdAt = responseData.createdAt, + requestUserId = responseData.requestUserId ) Result.success(detail) } else { diff --git a/app/src/main/java/com/ssafy/tiggle/domain/entity/dutchpay/DutchPayDetail.kt b/app/src/main/java/com/ssafy/tiggle/domain/entity/dutchpay/DutchPayDetail.kt index 84e96af..97fe625 100644 --- a/app/src/main/java/com/ssafy/tiggle/domain/entity/dutchpay/DutchPayDetail.kt +++ b/app/src/main/java/com/ssafy/tiggle/domain/entity/dutchpay/DutchPayDetail.kt @@ -10,7 +10,8 @@ data class DutchPayDetail( val shares: List, val roundedPerPerson: Int?, val payMore: Boolean, - val createdAt: String + val createdAt: String, + val requestUserId: Long ) data class Creator( @@ -22,5 +23,6 @@ data class Share( val userId: Long, val name: String, val amount: Int, + val tiggleAmount: Int? = null, val status: String ) diff --git a/app/src/main/java/com/ssafy/tiggle/domain/entity/dutchpay/UserSummary.kt b/app/src/main/java/com/ssafy/tiggle/domain/entity/dutchpay/UserSummary.kt index cccff60..de040b4 100644 --- a/app/src/main/java/com/ssafy/tiggle/domain/entity/dutchpay/UserSummary.kt +++ b/app/src/main/java/com/ssafy/tiggle/domain/entity/dutchpay/UserSummary.kt @@ -5,5 +5,7 @@ package com.ssafy.tiggle.domain.entity.dutchpay */ data class UserSummary( val id: Long, - val name: String + val name: String, + val university: String? = null, + val department: String? = null ) \ No newline at end of file diff --git a/app/src/main/java/com/ssafy/tiggle/presentation/navigation/NavigationGraph.kt b/app/src/main/java/com/ssafy/tiggle/presentation/navigation/NavigationGraph.kt index 8991972..f4f539f 100644 --- a/app/src/main/java/com/ssafy/tiggle/presentation/navigation/NavigationGraph.kt +++ b/app/src/main/java/com/ssafy/tiggle/presentation/navigation/NavigationGraph.kt @@ -239,7 +239,10 @@ fun NavigationGraph( is Screen.DutchPayDetail -> NavEntry(key) { DutchPayDetailScreen( dutchPayId = key.dutchPayId, - onBackClick = { navBackStack.removeLastOrNull() } + onBackClick = { navBackStack.removeLastOrNull() }, + onPaymentClick = { + navBackStack.add(Screen.DutchpayRecieve(key.dutchPayId)) + } ) } diff --git a/app/src/main/java/com/ssafy/tiggle/presentation/ui/components/UserPicker.kt b/app/src/main/java/com/ssafy/tiggle/presentation/ui/components/UserPicker.kt index e689e39..6ae2015 100644 --- a/app/src/main/java/com/ssafy/tiggle/presentation/ui/components/UserPicker.kt +++ b/app/src/main/java/com/ssafy/tiggle/presentation/ui/components/UserPicker.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Check import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon @@ -35,15 +36,20 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.ssafy.tiggle.R import com.ssafy.tiggle.domain.entity.dutchpay.UserSummary +import com.ssafy.tiggle.presentation.ui.theme.AppTypography import com.ssafy.tiggle.presentation.ui.theme.TiggleBlue +import com.ssafy.tiggle.presentation.ui.theme.TiggleBlueLight import com.ssafy.tiggle.presentation.ui.theme.TiggleGrayLight import com.ssafy.tiggle.presentation.ui.theme.TiggleGrayText +import com.ssafy.tiggle.presentation.ui.theme.TiggleSkyBlue @Composable fun UserPicker( @@ -123,39 +129,93 @@ private fun UserRow(user: UserSummary, isSelected: Boolean, onClick: () -> Unit) modifier = Modifier .fillMaxWidth() .clickable { onClick() } - .padding(horizontal = 12.dp, vertical = 14.dp), + .padding(horizontal = 16.dp, vertical = 16.dp), verticalAlignment = Alignment.CenterVertically ) { - // 아바타 (이니셜) + // 아바타 (둥근 사각형) Box( modifier = Modifier - .size(32.dp) + .size(48.dp) + .clip(RoundedCornerShape(12.dp)) .background( - color = if (isSelected) TiggleBlue else TiggleGrayLight, - shape = CircleShape + color = if (isSelected) TiggleBlue else TiggleGrayLight ), contentAlignment = Alignment.Center ) { Text( text = user.name.firstOrNull()?.toString() ?: "?", color = if (isSelected) Color.White else Color.Black, - fontWeight = FontWeight.Bold + fontWeight = FontWeight.Bold, + style = AppTypography.titleMedium ) } Column( modifier = Modifier .weight(1f) - .padding(start = 12.dp) + .padding(start = 16.dp) ) { - Text(text = user.name, color = Color.Black) + Text( + text = user.name, + color = Color.Black, + fontWeight = FontWeight.Medium, + style = AppTypography.bodyLarge + ) + + // 학교와 학과 정보 + if (user.university != null || user.department != null) { + Spacer(Modifier.height(2.dp)) + Text( + text = buildString { + if (user.university != null) { + append(user.university) + } + if (user.university != null && user.department != null) { + append(" • ") + } + if (user.department != null) { + append(user.department) + } + }, + color = TiggleGrayText, + style = AppTypography.bodySmall, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } } - Icon( - painter = androidx.compose.ui.res.painterResource(id = R.drawable.check), - contentDescription = null, - tint = if (isSelected) TiggleBlue else TiggleGrayText - ) + // 선택 상태 표시 (TiggleSkyBlue 원형 버튼) + Box( + modifier = Modifier + .size(24.dp) + .clip(CircleShape) + .background( + color = if (isSelected) TiggleBlueLight else Color.White + ) + .padding(2.dp), + contentAlignment = Alignment.Center + ) { + if (isSelected) { + Icon( + imageVector = Icons.Default.Check, + contentDescription = "selected", + tint = Color.White, + modifier = Modifier.size(16.dp) + ) + } else { + // 선택되지 않은 상태의 테두리 (동일한 크기 유지) + Box( + modifier = Modifier + .size(20.dp) + .clip(CircleShape) + .background( + color = TiggleGrayText.copy(alpha = 0.3f), + shape = CircleShape + ) + ) + } + } } } @@ -167,7 +227,7 @@ private fun SelectedUserChip(name: String, onRemove: () -> Unit, modifier: Modif .padding(horizontal = 10.dp, vertical = 6.dp), verticalAlignment = Alignment.CenterVertically ) { - Text(text = name, style = MaterialTheme.typography.bodySmall) + Text(text = name, style = AppTypography.bodySmall) Spacer(Modifier.size(6.dp)) Box( modifier = Modifier diff --git a/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/CreateDutchPayScreen.kt b/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/CreateDutchPayScreen.kt index 149a550..ba16ffe 100644 --- a/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/CreateDutchPayScreen.kt +++ b/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/CreateDutchPayScreen.kt @@ -1,5 +1,7 @@ package com.ssafy.tiggle.presentation.ui.dutchpay +import android.R.attr.label +import android.util.Log import androidx.compose.animation.core.animateIntAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.Image @@ -327,9 +329,18 @@ private fun AnimatedNumberCounter( targetValue: Long, modifier: Modifier = Modifier ) { + var isVisible by remember { mutableStateOf(false) } + + LaunchedEffect(Unit) { + isVisible = true + } + val animatedValue by animateIntAsState( - targetValue = targetValue.toInt(), - animationSpec = tween(durationMillis = 1000), + targetValue = if (isVisible) targetValue.toInt() else 0, + animationSpec = tween( + durationMillis = 1000, + easing = androidx.compose.animation.core.LinearEasing + ), label = "number_animation" ) @@ -512,8 +523,12 @@ fun DutchPayCompleteContent( val myAmount = if (payMore) roundUpToHundreds(perHead) else perHead.toLong() val friendAmount = perHead.toLong() + + + Text("참여자 (${participantCount}명)") + DetailRow( - label = "참여자 (${participantCount}명)", + label = "내가 낸 금액", value = Formatter.formatCurrency(myAmount) ) @@ -614,9 +629,9 @@ private fun InfoStep(text: String) { @Composable private fun PreviewDutchPay_PickUsers() { val sampleUsers = listOf( - UserSummary(4, "김테스트"), - UserSummary(5, "박테스트"), - UserSummary(6, "이테스트") + UserSummary(4, "김테스트", "신은대학교", "컴퓨터학부"), + UserSummary(5, "박테스트", "신한대학교", "건축학과"), + UserSummary(6, "이테스트", "싸피대학교", "인공지능학과") ) val uiState = CreateDutchPayState( step = CreateDutchPayStep.PICK_USERS, @@ -668,9 +683,9 @@ private fun PreviewDutchPay_InputAmount() { ) { DutchPayInputAmountContent( selectedUsers = listOf( - UserSummary(1, "김민호"), - UserSummary(2, "민경이"), - UserSummary(3, "홍길동") + UserSummary(1, "김민호", "신은대학교", "컴퓨터학부"), + UserSummary(2, "민경이", "신한대학교", "건축학과"), + UserSummary(3, "홍길동", "싸피대학교", "인공지능학과") ), amountText = uiState.amountText, onAmountChange = {}, @@ -704,8 +719,8 @@ private fun PreviewDutchPay_Complete() { DutchPayCompleteContent( totalAmount = 50000L, selectedUsers = listOf( - UserSummary(1, "김민준"), - UserSummary(2, "박예준") + UserSummary(1, "김민준", "신은대학교", "컴퓨터학부"), + UserSummary(2, "박예준", "신한대학교", "건축학과") ), payMore = true ) diff --git a/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/DutchPayDetailScreen.kt b/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/DutchPayDetailScreen.kt index 6517ca4..ddc8542 100644 --- a/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/DutchPayDetailScreen.kt +++ b/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/DutchPayDetailScreen.kt @@ -13,11 +13,15 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Tab +import androidx.compose.material3.TabRow import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -40,6 +44,8 @@ import com.ssafy.tiggle.core.utils.Formatter import com.ssafy.tiggle.domain.entity.dutchpay.DutchPayDetail import com.ssafy.tiggle.domain.entity.dutchpay.Creator import com.ssafy.tiggle.domain.entity.dutchpay.Share +import com.ssafy.tiggle.presentation.ui.components.TiggleButton +import com.ssafy.tiggle.presentation.ui.components.TiggleButtonVariant import com.ssafy.tiggle.presentation.ui.components.TiggleScreenLayout import com.ssafy.tiggle.presentation.ui.theme.AppTypography import com.ssafy.tiggle.presentation.ui.theme.TiggleBlue @@ -48,6 +54,7 @@ import com.ssafy.tiggle.presentation.ui.theme.TiggleBlue fun DutchPayDetailScreen( dutchPayId: Long, onBackClick: () -> Unit, + onPaymentClick: () -> Unit = {}, viewModel: DutchPayDetailViewModel = hiltViewModel() ) { val uiState by viewModel.uiState.collectAsState() @@ -60,7 +67,7 @@ fun DutchPayDetailScreen( showBackButton = true, title = "더치페이 현황", onBackClick = onBackClick, - enableScroll = true + enableScroll = false ) { when { uiState.isLoading -> { @@ -89,15 +96,27 @@ fun DutchPayDetailScreen( } uiState.dutchPayDetail != null -> { - DutchPayDetailContent(detail = uiState.dutchPayDetail!!) + DutchPayDetailContent( + detail = uiState.dutchPayDetail!!, + onPaymentClick = onPaymentClick + ) } } } } @Composable -private fun DutchPayDetailContent(detail: DutchPayDetail) { - var selectedTab by remember { mutableStateOf("PENDING") } // 기본값은 정산 미완료 +private fun DutchPayDetailContent( + detail: DutchPayDetail, + onPaymentClick: () -> Unit +) { + var selectedTabIndex by remember { mutableStateOf(1) } // 기본값은 정산 미완료 (인덱스 1) + + // 현재 사용자가 미정산 상태인지 확인 (PENDING 상태인 share가 있는지) + val currentUserPendingShare = detail.shares.find { it.status == "PENDING" } + + // 탭 인덱스를 상태 문자열로 변환 + val selectedTab = if (selectedTabIndex == 0) "PAID" else "PENDING" Column( modifier = Modifier @@ -108,7 +127,7 @@ private fun DutchPayDetailContent(detail: DutchPayDetail) { Card( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(16.dp), - colors = CardDefaults.cardColors(containerColor = Color(0xFFF5F5F5)), + colors = CardDefaults.cardColors(containerColor = TiggleBlue), elevation = CardDefaults.cardElevation(defaultElevation = 0.dp) ) { Column( @@ -117,27 +136,25 @@ private fun DutchPayDetailContent(detail: DutchPayDetail) { ) { Text( text = "${detail.shares.size}명 참여", - style = AppTypography.bodyMedium, - color = Color(0xFF666666) + fontSize = 14.sp, + color = Color.White.copy(alpha = 0.8f) ) Spacer(modifier = Modifier.height(8.dp)) Text( text = Formatter.formatCurrency(detail.totalAmount.toLong()), - style = AppTypography.headlineLarge.copy( - fontSize = 32.sp, - fontWeight = FontWeight.Bold - ), - color = Color.Black + fontSize = 32.sp, + fontWeight = FontWeight.Bold, + color = Color.White ) Spacer(modifier = Modifier.height(8.dp)) Text( text = "요청일 ${Formatter.formatDate(detail.createdAt)}", - style = AppTypography.bodySmall, - color = Color(0xFF999999) + fontSize = 14.sp, + color = Color.White.copy(alpha = 0.8f) ) Spacer(modifier = Modifier.height(16.dp)) @@ -168,101 +185,92 @@ private fun DutchPayDetailContent(detail: DutchPayDetail) { Spacer(modifier = Modifier.height(16.dp)) // 탭 (선택 가능) - Row( - modifier = Modifier - .fillMaxWidth() - .background(Color(0xFFF5F5F5), RoundedCornerShape(8.dp)) - .padding(4.dp) + val completedCount = detail.shares.count { it.status == "PAID" } + val pendingCount = detail.shares.count { it.status == "PENDING" } + + TabRow( + selectedTabIndex = selectedTabIndex, + modifier = Modifier.fillMaxWidth() ) { - // 정산 완료 탭 - Box( - modifier = Modifier - .weight(1f) - .background( - color = if (selectedTab == "PAID") Color.White else Color.Transparent, - shape = RoundedCornerShape(6.dp) - ) - .clickable { selectedTab = "PAID" } - .padding(vertical = 12.dp), - contentAlignment = Alignment.Center - ) { - val completedCount = detail.shares.count { it.status == "PAID" } - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text( - text = "${completedCount}명", - style = AppTypography.bodyMedium.copy(fontWeight = FontWeight.Medium), - color = if (selectedTab == "PAID") TiggleBlue else Color(0xFF666666) - ) - Text( - text = "정산 완료", - style = AppTypography.bodySmall, - color = if (selectedTab == "PAID") TiggleBlue else Color(0xFF999999) - ) + Tab( + selected = selectedTabIndex == 0, + onClick = { selectedTabIndex = 0 }, + text = { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = "${completedCount}명", + style = AppTypography.bodyMedium.copy(fontWeight = FontWeight.Medium) + ) + Text( + text = "정산 완료", + style = AppTypography.bodySmall + ) + } } - } - - // 정산 미완료 탭 - Box( - modifier = Modifier - .weight(1f) - .background( - color = if (selectedTab == "PENDING") Color.White else Color.Transparent, - shape = RoundedCornerShape(6.dp) - ) - .clickable { selectedTab = "PENDING" } - .padding(vertical = 12.dp), - contentAlignment = Alignment.Center - ) { - val pendingCount = detail.shares.count { it.status == "PENDING" } - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text( - text = "${pendingCount}명", - style = AppTypography.bodyMedium.copy( - fontWeight = FontWeight.Medium, - color = if (selectedTab == "PENDING") TiggleBlue else Color(0xFF666666) + ) + Tab( + selected = selectedTabIndex == 1, + onClick = { selectedTabIndex = 1 }, + text = { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = "${pendingCount}명", + style = AppTypography.bodyMedium.copy(fontWeight = FontWeight.Medium) ) - ) - Text( - text = "정산 미완료", - style = AppTypography.bodySmall, - color = if (selectedTab == "PENDING") TiggleBlue else Color(0xFF999999) - ) + Text( + text = "정산 미완료", + style = AppTypography.bodySmall + ) + } } - } + ) } Spacer(modifier = Modifier.height(16.dp)) + + // 참여자 목록 (선택된 탭에 따라 표시) val selectedShares = detail.shares.filter { it.status == selectedTab } - if (selectedShares.isNotEmpty()) { - selectedShares.forEach { share -> - ParticipantItem( - name = share.name, - amount = share.amount.toLong(), - status = share.status - ) - if (share != selectedShares.last()) { - HorizontalDivider( - modifier = Modifier.padding(vertical = 8.dp), - color = Color(0xFFE0E0E0) + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = androidx.compose.foundation.layout.PaddingValues(vertical = 8.dp) + ) { + if (selectedShares.isEmpty()) { + item { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 32.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = if (selectedTab == "PAID") "정산 완료인 참여자가 없습니다" else "정산 미완료인 참여자가 없습니다", + style = AppTypography.bodyMedium, + color = Color(0xFF999999) + ) + } + } + } else { + items(selectedShares, key = { it.userId }) { share -> + ParticipantItem( + name = share.name, + amount = share.amount.toLong(), + tiggleAmount = share.tiggleAmount?.toLong() ?: 0L, + status = share.status, + isCurrentUser = share.userId == detail.requestUserId, // requestUserId와 일치하는 사용자가 현재 사용자 + onPaymentClick = onPaymentClick ) + + if (share != selectedShares.last()) { + HorizontalDivider( + modifier = Modifier.padding(vertical = 8.dp), + color = Color(0xFFE0E0E0) + ) + } } } - } else { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 32.dp), - contentAlignment = Alignment.Center - ) { - Text( - text = if (selectedTab == "PAID") "정산 완료인 참여자가 없습니다" else "정산 미완료인 참여자가 없습니다", - style = AppTypography.bodyMedium, - color = Color(0xFF999999) - ) - } } } } @@ -278,9 +286,10 @@ private fun StatusItem( .width(120.dp) .height(60.dp), shape = RoundedCornerShape(12.dp), - colors = CardDefaults.cardColors(containerColor = backgroundColor), - elevation = CardDefaults.cardElevation(defaultElevation = 0.dp) - ) { + colors = CardDefaults.cardColors( + containerColor = Color.White.copy(alpha = 0.2f) + ) + ) { Column( modifier = Modifier .fillMaxSize() @@ -290,12 +299,15 @@ private fun StatusItem( ) { Text( text = "${count}명", - style = AppTypography.bodyMedium.copy(fontWeight = FontWeight.Bold) + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + color = Color.White ) Text( text = label, style = AppTypography.bodySmall, - color = Color(0xFF666666) + fontSize = 12.sp, + color = Color.White.copy(alpha = 0.8f) ) } } @@ -305,47 +317,117 @@ private fun StatusItem( private fun ParticipantItem( name: String, amount: Long, - status: String + tiggleAmount: Long, + status: String, + isCurrentUser: Boolean = false, + onPaymentClick: () -> Unit = {} ) { - Row( + Column( modifier = Modifier .fillMaxWidth() - .padding(vertical = 8.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + .padding(vertical = 8.dp) ) { Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = name, + style = AppTypography.bodyMedium, + color = Color.Black + ) + + // 상태 표시 (현재 사용자가 미정산 상태일 때는 정산하기 버튼, 그 외에는 상태 표시) + if (isCurrentUser && status == "PENDING") { + Box( + modifier = Modifier + .background( + color = TiggleBlue.copy(alpha = 0.1f), + shape = RoundedCornerShape(12.dp) + ) + .clickable { onPaymentClick() } + .padding(horizontal = 8.dp, vertical = 4.dp) + ) { + Text( + text = "정산하기", + style = AppTypography.bodySmall.copy(fontWeight = FontWeight.Medium), + color = TiggleBlue, + fontSize = 10.sp + ) + } + } else { + Box( + modifier = Modifier + .background( + color = if (status == "PAID") Color(0xFFE8F5E8) else Color(0xFFFFF3E0), + shape = RoundedCornerShape(4.dp) + ) + .padding(horizontal = 8.dp, vertical = 4.dp) + ) { + Text( + text = if (status == "PAID") "완료" else "대기", + style = AppTypography.bodySmall, + color = if (status == "PAID") Color(0xFF2E7D32) else Color(0xFFF57C00) + ) + } + } + } + + // 기본 금액 Text( - text = name, - style = AppTypography.bodyMedium, + text = Formatter.formatCurrency(amount), + style = AppTypography.bodyMedium.copy(fontWeight = FontWeight.Medium), color = Color.Black ) - - // 상태 표시 - Box( - modifier = Modifier - .background( - color = if (status == "PAID") Color(0xFFE8F5E8) else Color(0xFFFFF3E0), - shape = RoundedCornerShape(4.dp) - ) - .padding(horizontal = 8.dp, vertical = 4.dp) + } + + // tiggleAmount가 0이 아닐 때만 표시 + if (tiggleAmount > 0) { + Spacer(modifier = Modifier.height(4.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically ) { Text( - text = if (status == "PAID") "완료" else "대기", + text = "🐷 티끌 적립", style = AppTypography.bodySmall, - color = if (status == "PAID") Color(0xFF2E7D32) else Color(0xFFF57C00) + color = Color(0xFF666666) + ) + Text( + text = "+ ${Formatter.formatCurrency(tiggleAmount)}", + style = AppTypography.bodySmall.copy(fontWeight = FontWeight.Medium), + color = TiggleBlue + ) + } + + Spacer(modifier = Modifier.height(4.dp)) + HorizontalDivider(color = Color(0xFFE0E0E0)) + Spacer(modifier = Modifier.height(4.dp)) + + // 총 금액 + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "정산 금액", + style = AppTypography.bodyMedium.copy(fontWeight = FontWeight.Medium), + color = Color.Black + ) + Text( + text = Formatter.formatCurrency(amount + tiggleAmount), + style = AppTypography.bodyMedium.copy(fontWeight = FontWeight.Bold), + color = TiggleBlue ) } } - - Text( - text = Formatter.formatCurrency(amount), - style = AppTypography.bodyMedium.copy(fontWeight = FontWeight.Medium), - color = Color.Black - ) } } @@ -360,14 +442,15 @@ private fun DutchPayDetailScreenPreview() { status = "REQUESTED", creator = Creator(id = 1L, name = "김테스트"), shares = listOf( - Share(userId = 1L, name = "김테스트", amount = 3703, status = "PAID"), - Share(userId = 2L, name = "박테스트", amount = 3704, status = "PAID"), - Share(userId = 10L, name = "jiwon", amount = 3704, status = "PENDING") + Share(userId = 1L, name = "김테스트", amount = 3703, tiggleAmount = 297, status = "PAID"), + Share(userId = 2L, name = "박테스트", amount = 3704, tiggleAmount = 296, status = "PAID"), + Share(userId = 10L, name = "jiwon", amount = 3704, tiggleAmount = 296, status = "PENDING") ), roundedPerPerson = null, payMore = false, - createdAt = "2025-08-28T12:46:16" + createdAt = "2025-08-28T12:46:16", + requestUserId = 10L // jiwon이 현재 사용자 ) - DutchPayDetailContent(detail = sampleDetail) + DutchPayDetailContent(detail = sampleDetail, onPaymentClick = {}) } diff --git a/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/DutchPayStatusScreen.kt b/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/DutchPayStatusScreen.kt index 6648638..8ee4b60 100644 --- a/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/DutchPayStatusScreen.kt +++ b/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/DutchPayStatusScreen.kt @@ -122,9 +122,18 @@ private fun AnimatedNumberCounter( targetValue: Int, modifier: Modifier = Modifier ) { + var isVisible by remember { mutableStateOf(false) } + + LaunchedEffect(Unit) { + isVisible = true + } + val animatedValue by animateIntAsState( - targetValue = targetValue, - animationSpec = tween(durationMillis = 1000), + targetValue = if (isVisible) targetValue else 0, + animationSpec = tween( + durationMillis = 1000, + easing = androidx.compose.animation.core.LinearEasing + ), label = "number_animation" ) @@ -208,7 +217,7 @@ private fun DutchPayItemCard( ) ) Text( - text = "${item.participantCount}명 참여", + text = "총 ${item.participantCount}명", style = AppTypography.bodySmall, color = Color(0xFF666666) ) diff --git a/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/DutchpayRecieveScreen.kt b/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/DutchpayRecieveScreen.kt index fd3fc58..1c8e38b 100644 --- a/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/DutchpayRecieveScreen.kt +++ b/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/DutchpayRecieveScreen.kt @@ -45,6 +45,7 @@ import com.ssafy.tiggle.presentation.ui.components.TiggleSwitchRow import com.ssafy.tiggle.presentation.ui.theme.AppTypography import com.ssafy.tiggle.presentation.ui.theme.TiggleBlue import com.ssafy.tiggle.presentation.ui.dutchpay.DutchPayRequestDetailViewModel +import android.util.Log @Composable fun DutchpayRecieveScreen( @@ -60,10 +61,13 @@ fun DutchpayRecieveScreen( viewModel.loadDutchPayDetail(dutchPayId) } - // payMore 상태를 detail에서 초기화 + // payMore 상태를 detail에서 초기화 (한 번만) LaunchedEffect(uiState.dutchPayDetail) { uiState.dutchPayDetail?.let { detail -> - payMoreEnabled = detail.payMoreDefault + if (payMoreEnabled == false) { // 초기값일 때만 설정 + payMoreEnabled = detail.payMoreDefault + Log.d("DutchpayRecieveScreen", "payMoreEnabled 초기화: ${detail.payMoreDefault}") + } } } @@ -106,7 +110,10 @@ fun DutchpayRecieveScreen( DutchPayPaymentContent( detail = detail, payMoreEnabled = payMoreEnabled, - onPayMoreChanged = { payMoreEnabled = it } + onPayMoreChanged = { + Log.d("DutchpayRecieveScreen", "payMoreEnabled 변경: $it") + payMoreEnabled = it + } ) } } @@ -139,9 +146,18 @@ private fun AnimatedNumberCounter( targetValue: Long, modifier: Modifier = Modifier ) { + var isVisible by remember { mutableStateOf(false) } + + LaunchedEffect(Unit) { + isVisible = true + } + val animatedValue by animateIntAsState( - targetValue = targetValue.toInt(), - animationSpec = tween(durationMillis = 1000), + targetValue = if (isVisible) targetValue.toInt() else 0, + animationSpec = tween( + durationMillis = 1000, + easing = androidx.compose.animation.core.LinearEasing + ), label = "number_animation" ) @@ -159,6 +175,9 @@ private fun DutchPayPaymentContent( payMoreEnabled: Boolean, onPayMoreChanged: (Boolean) -> Unit ) { + // payMoreEnabled에 따른 tiggleAmount 계산 + val currentTiggleAmount = if (payMoreEnabled) detail.tiggleAmount else 0L + // 내가 낼 금액 계산 val myPaymentAmount = if (payMoreEnabled) { detail.originalAmount + detail.tiggleAmount @@ -279,7 +298,7 @@ private fun DutchPayPaymentContent( DetailRow(label = "원래 금액", value = Formatter.formatCurrency(detail.originalAmount)) - if (payMoreEnabled && detail.tiggleAmount > 0) { + if (payMoreEnabled && currentTiggleAmount > 0) { Spacer(modifier = Modifier.height(8.dp)) Row( modifier = Modifier.fillMaxWidth(), @@ -292,7 +311,7 @@ private fun DutchPayPaymentContent( color = Color.Gray ) AnimatedNumberCounter( - targetValue = detail.tiggleAmount + targetValue = currentTiggleAmount ) } } @@ -301,38 +320,6 @@ private fun DutchPayPaymentContent( Spacer(modifier = Modifier.height(20.dp)) - // 티끌 적립 정보 표시 (payMoreEnabled이고 tiggleAmount가 0보다 클 때만) - if (payMoreEnabled && detail.tiggleAmount > 0) { - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = Color(0xFFF0F8FF)), - elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), - shape = RoundedCornerShape(12.dp) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "🐷", - fontSize = 20.sp - ) - Spacer(modifier = Modifier.width(12.dp)) - Text( - text = "티끌 적립", - style = AppTypography.bodyLarge.copy(fontWeight = FontWeight.Bold), - color = Color(0xFF1B6BFF), - modifier = Modifier.weight(1f) - ) - AnimatedNumberCounter( - targetValue = detail.tiggleAmount - ) - } - } - Spacer(modifier = Modifier.height(16.dp)) - } // 돈 더내고 잔돈 기부하기 스위치 TiggleSwitchRow(