diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f79ce79..b7fb28a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -32,12 +32,25 @@
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Tiggle"
+ android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize">
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/ssafy/tiggle/MainActivity.kt b/app/src/main/java/com/ssafy/tiggle/MainActivity.kt
index 9cf31dd..689bb38 100644
--- a/app/src/main/java/com/ssafy/tiggle/MainActivity.kt
+++ b/app/src/main/java/com/ssafy/tiggle/MainActivity.kt
@@ -14,14 +14,15 @@ import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
-import com.google.firebase.messaging.FirebaseMessaging
-
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.ssafy.tiggle.presentation.navigation.NavigationGraph
import com.ssafy.tiggle.presentation.ui.theme.TiggleTheme
import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.MutableStateFlow
/**
* 메인 액티비티
@@ -29,13 +30,17 @@ import dagger.hilt.android.AndroidEntryPoint
*/
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
+ // 1. 딥링크 Intent를 담을 StateFlow 생성
+ private val deepLinkIntent = MutableStateFlow(null)
+
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
-
- // ✅ 디버그용: 현재 기기의 FCM 토큰 로그로 확인
- FirebaseMessaging.getInstance().token.addOnSuccessListener {
- Log.d("TiggleFCM", "FCM token = $it")
+
+ // 2. 앱 시작 시 초기 Intent가 딥링크를 포함하는지 확인
+ if (intent?.data != null) {
+ deepLinkIntent.value = intent
}
setContent {
@@ -43,10 +48,24 @@ class MainActivity : ComponentActivity() {
// ✅ Android 13+ 알림 권한 1회 요청
RequestPostNotificationsPermissionOnce()
// ⬇️ 기존 네비게이션
- NavigationGraph()
+ val intentState by deepLinkIntent.collectAsStateWithLifecycle()
+ NavigationGraph(
+ intent = intentState,
+ // 6. 딥링크 처리가 완료되면 StateFlow를 null로 비워주는 콜백 전달
+ onDeepLinkHandled = { deepLinkIntent.value = null }
+ )
}
}
}
+
+ // 4. 앱이 백그라운드에 있을 때 새 Intent를 받는 부분
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ // 5. 새로 받은 Intent로 StateFlow를 업데이트 -> Compose에 변경 사항 알림
+ if (intent.data != null) {
+ deepLinkIntent.value = intent
+ }
+ }
}
/** Android 13+에서 POST_NOTIFICATIONS 권한 1회 요청 */
diff --git a/app/src/main/java/com/ssafy/tiggle/core/fcm/TiggleMessageService.kt b/app/src/main/java/com/ssafy/tiggle/core/fcm/TiggleMessageService.kt
index 0ad2d40..b10732c 100644
--- a/app/src/main/java/com/ssafy/tiggle/core/fcm/TiggleMessageService.kt
+++ b/app/src/main/java/com/ssafy/tiggle/core/fcm/TiggleMessageService.kt
@@ -97,6 +97,11 @@ class TiggleMessagingService : FirebaseMessagingService() {
val intent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ // 딥링크 정보를 Intent에 추가
+ if (deepLink != null) {
+ // URI 형태로 설정하여 NavigationGraph에서 처리할 수 있도록 함
+ data = android.net.Uri.parse(deepLink)
+ }
}
val pending = PendingIntent.getActivity(
this, 0, intent,
diff --git a/app/src/main/java/com/ssafy/tiggle/core/utils/Formatter.kt b/app/src/main/java/com/ssafy/tiggle/core/utils/Formatter.kt
index fbf0c5d..ed2a398 100644
--- a/app/src/main/java/com/ssafy/tiggle/core/utils/Formatter.kt
+++ b/app/src/main/java/com/ssafy/tiggle/core/utils/Formatter.kt
@@ -1,5 +1,9 @@
package com.ssafy.tiggle.core.utils
+import android.annotation.SuppressLint
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+import java.time.format.DateTimeParseException
import java.util.Locale
object Formatter {
@@ -7,4 +11,63 @@ object Formatter {
fun formatCurrency(amount: Long, locale: Locale = Locale.KOREA): String =
String.format(locale, "%,d원", amount)
+ // ISO 8601 날짜/시간 포맷팅: "2025-08-26T01:25:21" -> "2025.08.26 01:25"
+ @SuppressLint("NewApi")
+ fun formatDateTime(isoDateTime: String): String {
+ return try {
+ val dateTime = LocalDateTime.parse(isoDateTime)
+ val formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm")
+ dateTime.format(formatter)
+ } catch (e: DateTimeParseException) {
+ // 파싱 실패 시 원본 문자열 반환
+ isoDateTime
+ }
+ }
+
+ // 간단한 날짜 포맷팅: "2025-08-26T01:25:21" -> "8월 26일"
+ @SuppressLint("NewApi")
+ fun formatDateOnly(isoDateTime: String): String {
+ return try {
+ val dateTime = LocalDateTime.parse(isoDateTime)
+ val formatter = DateTimeFormatter.ofPattern("M월 d일", Locale.KOREA)
+ dateTime.format(formatter)
+ } catch (e: DateTimeParseException) {
+ // 파싱 실패 시 원본 문자열 반환
+ isoDateTime
+ }
+ }
+
+ // 시간만 포맷팅: "2025-08-26T01:25:21" -> "오전 1:25"
+ @SuppressLint("NewApi")
+ fun formatTimeOnly(isoDateTime: String): String {
+ return try {
+ val dateTime = LocalDateTime.parse(isoDateTime)
+ val formatter = DateTimeFormatter.ofPattern("a h:mm", Locale.KOREA)
+ dateTime.format(formatter)
+ } catch (e: DateTimeParseException) {
+ // 파싱 실패 시 원본 문자열 반환
+ isoDateTime
+ }
+ }
+
+ // 상대적 시간 표시: "방금", "5분 전", "3시간 전", "2일 전" 등
+ @SuppressLint("NewApi")
+ fun formatRelativeTime(isoDateTime: String): String {
+ return try {
+ val dateTime = LocalDateTime.parse(isoDateTime)
+ val now = LocalDateTime.now()
+ val duration = java.time.Duration.between(dateTime, now)
+
+ when {
+ duration.toMinutes() < 1 -> "방금"
+ duration.toMinutes() < 60 -> "${duration.toMinutes()}분 전"
+ duration.toHours() < 24 -> "${duration.toHours()}시간 전"
+ duration.toDays() < 7 -> "${duration.toDays()}일 전"
+ else -> formatDateOnly(isoDateTime)
+ }
+ } catch (e: DateTimeParseException) {
+ // 파싱 실패 시 원본 문자열 반환
+ isoDateTime
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/AuthApiService.kt b/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/AuthApiService.kt
index 3acdb69..20258ff 100644
--- a/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/AuthApiService.kt
+++ b/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/AuthApiService.kt
@@ -1,8 +1,8 @@
package com.ssafy.tiggle.data.datasource.remote
import com.ssafy.tiggle.data.model.BaseResponse
-import com.ssafy.tiggle.data.model.LoginRequestDto
-import com.ssafy.tiggle.data.model.SignUpRequestDto
+import com.ssafy.tiggle.data.model.auth.request.LoginRequestDto
+import com.ssafy.tiggle.data.model.auth.request.SignUpRequestDto
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.POST
diff --git a/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/DutchPayApiService.kt b/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/DutchPayApiService.kt
index 55bc137..c5099ba 100644
--- a/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/DutchPayApiService.kt
+++ b/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/DutchPayApiService.kt
@@ -3,13 +3,21 @@ package com.ssafy.tiggle.data.datasource.remote
import com.ssafy.tiggle.data.model.BaseResponse
import com.ssafy.tiggle.data.model.EmptyResponse
import com.ssafy.tiggle.data.model.dutchpay.request.DutchPayRequestDto
+import com.ssafy.tiggle.data.model.dutchpay.response.DutchPayRequestDetailResponseDto
import retrofit2.Response
import retrofit2.http.Body
+import retrofit2.http.GET
import retrofit2.http.POST
+import retrofit2.http.Path
interface DutchPayApiService {
@POST("/api/dutchpay/requests")
suspend fun createDutchPayRequest(
@Body request: DutchPayRequestDto
): Response>
+
+ @GET("/api/dutchpay/requests/{id}")
+ suspend fun getDutchPayRequestDetail(
+ @Path("id") dutchPayId: Long
+ ): Response>
}
diff --git a/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/UniversityApiService.kt b/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/UniversityApiService.kt
index e571946..4e4dedf 100644
--- a/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/UniversityApiService.kt
+++ b/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/UniversityApiService.kt
@@ -1,8 +1,8 @@
package com.ssafy.tiggle.data.datasource.remote
import com.ssafy.tiggle.data.model.BaseResponse
-import com.ssafy.tiggle.data.model.DepartmentDto
-import com.ssafy.tiggle.data.model.UniversityDto
+import com.ssafy.tiggle.data.model.auth.response.DepartmentDto
+import com.ssafy.tiggle.data.model.auth.response.UniversityDto
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Path
diff --git a/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/UserApiService.kt b/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/UserApiService.kt
index ce74042..5765282 100644
--- a/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/UserApiService.kt
+++ b/app/src/main/java/com/ssafy/tiggle/data/datasource/remote/UserApiService.kt
@@ -1,7 +1,7 @@
package com.ssafy.tiggle.data.datasource.remote
import com.ssafy.tiggle.data.model.BaseResponse
-import com.ssafy.tiggle.data.model.UserSummaryDto
+import com.ssafy.tiggle.data.model.dutchpay.response.UserSummaryDto
import retrofit2.Response
import retrofit2.http.GET
diff --git a/app/src/main/java/com/ssafy/tiggle/data/model/LoginRequestDto.kt b/app/src/main/java/com/ssafy/tiggle/data/model/auth/request/LoginRequestDto.kt
similarity index 60%
rename from app/src/main/java/com/ssafy/tiggle/data/model/LoginRequestDto.kt
rename to app/src/main/java/com/ssafy/tiggle/data/model/auth/request/LoginRequestDto.kt
index 19fba05..8454409 100644
--- a/app/src/main/java/com/ssafy/tiggle/data/model/LoginRequestDto.kt
+++ b/app/src/main/java/com/ssafy/tiggle/data/model/auth/request/LoginRequestDto.kt
@@ -1,6 +1,6 @@
-package com.ssafy.tiggle.data.model
+package com.ssafy.tiggle.data.model.auth.request
data class LoginRequestDto(
val email: String,
val password: String
-)
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/ssafy/tiggle/data/model/SignUpRequestDto.kt b/app/src/main/java/com/ssafy/tiggle/data/model/auth/request/SignUpRequestDto.kt
similarity index 83%
rename from app/src/main/java/com/ssafy/tiggle/data/model/SignUpRequestDto.kt
rename to app/src/main/java/com/ssafy/tiggle/data/model/auth/request/SignUpRequestDto.kt
index b0c070d..ab48f0f 100644
--- a/app/src/main/java/com/ssafy/tiggle/data/model/SignUpRequestDto.kt
+++ b/app/src/main/java/com/ssafy/tiggle/data/model/auth/request/SignUpRequestDto.kt
@@ -1,4 +1,4 @@
-package com.ssafy.tiggle.data.model
+package com.ssafy.tiggle.data.model.auth.request
/**
* 회원가입 요청 DTO
@@ -11,4 +11,4 @@ data class SignUpRequestDto(
val studentId: String,
val password: String,
val phone: String
-)
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/ssafy/tiggle/data/model/DepartmentDto.kt b/app/src/main/java/com/ssafy/tiggle/data/model/auth/response/DepartmentDto.kt
similarity index 88%
rename from app/src/main/java/com/ssafy/tiggle/data/model/DepartmentDto.kt
rename to app/src/main/java/com/ssafy/tiggle/data/model/auth/response/DepartmentDto.kt
index 2b52ea2..f815f22 100644
--- a/app/src/main/java/com/ssafy/tiggle/data/model/DepartmentDto.kt
+++ b/app/src/main/java/com/ssafy/tiggle/data/model/auth/response/DepartmentDto.kt
@@ -1,4 +1,4 @@
-package com.ssafy.tiggle.data.model
+package com.ssafy.tiggle.data.model.auth.response
import com.ssafy.tiggle.domain.entity.auth.Department
@@ -20,4 +20,4 @@ data class DepartmentDto(
name = name
)
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ssafy/tiggle/data/model/UniversityDto.kt b/app/src/main/java/com/ssafy/tiggle/data/model/auth/response/UniversityDto.kt
similarity index 88%
rename from app/src/main/java/com/ssafy/tiggle/data/model/UniversityDto.kt
rename to app/src/main/java/com/ssafy/tiggle/data/model/auth/response/UniversityDto.kt
index f3ba932..c1c19b0 100644
--- a/app/src/main/java/com/ssafy/tiggle/data/model/UniversityDto.kt
+++ b/app/src/main/java/com/ssafy/tiggle/data/model/auth/response/UniversityDto.kt
@@ -1,4 +1,4 @@
-package com.ssafy.tiggle.data.model
+package com.ssafy.tiggle.data.model.auth.response
import com.ssafy.tiggle.domain.entity.auth.University
@@ -20,4 +20,4 @@ data class UniversityDto(
name = name
)
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ssafy/tiggle/data/model/UserDto.kt b/app/src/main/java/com/ssafy/tiggle/data/model/auth/response/UserDto.kt
similarity index 93%
rename from app/src/main/java/com/ssafy/tiggle/data/model/UserDto.kt
rename to app/src/main/java/com/ssafy/tiggle/data/model/auth/response/UserDto.kt
index ea83444..97c18b7 100644
--- a/app/src/main/java/com/ssafy/tiggle/data/model/UserDto.kt
+++ b/app/src/main/java/com/ssafy/tiggle/data/model/auth/response/UserDto.kt
@@ -1,4 +1,4 @@
-package com.ssafy.tiggle.data.model
+package com.ssafy.tiggle.data.model.auth.response
import com.ssafy.tiggle.domain.entity.auth.User
@@ -32,6 +32,4 @@ data class UserDto(
updatedAt = updatedAt
)
}
-}
-
-
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/request/DutchPayRequestDto.kt b/app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/request/DutchPayRequestDto.kt
index a2dbc15..217347b 100644
--- a/app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/request/DutchPayRequestDto.kt
+++ b/app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/request/DutchPayRequestDto.kt
@@ -6,4 +6,4 @@ data class DutchPayRequestDto(
val title: String,
val message: String,
val payMore: Boolean
-)
+)
\ No newline at end of file
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
new file mode 100644
index 0000000..b9373e5
--- /dev/null
+++ b/app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/response/DutchPayRequestDetailResponseDto.kt
@@ -0,0 +1,16 @@
+package com.ssafy.tiggle.data.model.dutchpay.response
+
+data class DutchPayRequestDetailResponseDto(
+ val dutchpayId: 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
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/ssafy/tiggle/data/model/UserSummaryDto.kt b/app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/response/UserSummaryDto.kt
similarity index 82%
rename from app/src/main/java/com/ssafy/tiggle/data/model/UserSummaryDto.kt
rename to app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/response/UserSummaryDto.kt
index dd65087..1a63e8f 100644
--- a/app/src/main/java/com/ssafy/tiggle/data/model/UserSummaryDto.kt
+++ b/app/src/main/java/com/ssafy/tiggle/data/model/dutchpay/response/UserSummaryDto.kt
@@ -1,4 +1,4 @@
-package com.ssafy.tiggle.data.model
+package com.ssafy.tiggle.data.model.dutchpay.response
import com.ssafy.tiggle.domain.entity.dutchpay.UserSummary
@@ -13,6 +13,4 @@ data class UserSummaryDto(
id = id,
name = name
)
-}
-
-
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ssafy/tiggle/data/repository/AuthRepositoryImpl.kt b/app/src/main/java/com/ssafy/tiggle/data/repository/AuthRepositoryImpl.kt
index b482ee3..c28d6c8 100644
--- a/app/src/main/java/com/ssafy/tiggle/data/repository/AuthRepositoryImpl.kt
+++ b/app/src/main/java/com/ssafy/tiggle/data/repository/AuthRepositoryImpl.kt
@@ -5,8 +5,8 @@ import com.google.gson.Gson
import com.ssafy.tiggle.data.datasource.local.AuthDataSource
import com.ssafy.tiggle.data.datasource.remote.AuthApiService
import com.ssafy.tiggle.data.model.BaseResponse
-import com.ssafy.tiggle.data.model.LoginRequestDto
-import com.ssafy.tiggle.data.model.SignUpRequestDto
+import com.ssafy.tiggle.data.model.auth.request.LoginRequestDto
+import com.ssafy.tiggle.data.model.auth.request.SignUpRequestDto
import com.ssafy.tiggle.domain.entity.auth.UserSignUp
import com.ssafy.tiggle.domain.repository.AuthRepository
import javax.inject.Inject
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 66d94b8..074f678 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
@@ -3,6 +3,7 @@ package com.ssafy.tiggle.data.repository
import com.ssafy.tiggle.data.datasource.remote.DutchPayApiService
import com.ssafy.tiggle.data.model.dutchpay.request.DutchPayRequestDto
import com.ssafy.tiggle.domain.entity.dutchpay.DutchPayRequest
+import com.ssafy.tiggle.domain.entity.dutchpay.DutchPayRequestDetail
import com.ssafy.tiggle.domain.repository.DutchPayRepository
import javax.inject.Inject
import javax.inject.Singleton
@@ -21,9 +22,9 @@ class DutchPayRepositoryImpl @Inject constructor(
message = request.message,
payMore = request.payMore
)
-
+
val response = dutchPayApiService.createDutchPayRequest(requestDto)
-
+
if (response.isSuccessful) {
Result.success(Unit)
} else {
@@ -33,4 +34,37 @@ class DutchPayRepositoryImpl @Inject constructor(
Result.failure(e)
}
}
+
+ override suspend fun getDutchPayRequestDetail(dutchPayId: Long): Result {
+ return try {
+ val response = dutchPayApiService.getDutchPayRequestDetail(dutchPayId)
+
+ if (response.isSuccessful) {
+ val responseData = response.body()?.data
+ if (responseData != null) {
+ val detail = DutchPayRequestDetail(
+ dutchPayId = responseData.dutchpayId,
+ title = responseData.title,
+ message = responseData.message,
+ requesterName = responseData.requesterName,
+ participantCount = responseData.participantCount,
+ totalAmount = responseData.totalAmount,
+ requestedAt = responseData.requestedAt,
+ myAmount = responseData.myAmount,
+ originalAmount = responseData.originalAmount,
+ tiggleAmount = responseData.tiggleAmount,
+ payMoreDefault = responseData.payMoreDefault,
+ isCreator = responseData.creator
+ )
+ Result.success(detail)
+ } else {
+ Result.failure(Exception("응답 데이터가 없습니다"))
+ }
+ } else {
+ Result.failure(Exception("더치페이 상세 조회 실패: ${response.code()}"))
+ }
+ } catch (e: Exception) {
+ Result.failure(e)
+ }
+ }
}
diff --git a/app/src/main/java/com/ssafy/tiggle/domain/entity/dutchpay/DutchPayRequestDetail.kt b/app/src/main/java/com/ssafy/tiggle/domain/entity/dutchpay/DutchPayRequestDetail.kt
new file mode 100644
index 0000000..38a0cd5
--- /dev/null
+++ b/app/src/main/java/com/ssafy/tiggle/domain/entity/dutchpay/DutchPayRequestDetail.kt
@@ -0,0 +1,16 @@
+package com.ssafy.tiggle.domain.entity.dutchpay
+
+data class DutchPayRequestDetail(
+ val dutchPayId: 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 isCreator: Boolean
+)
diff --git a/app/src/main/java/com/ssafy/tiggle/domain/repository/DutchPayRepository.kt b/app/src/main/java/com/ssafy/tiggle/domain/repository/DutchPayRepository.kt
index 8efe0a4..62a0987 100644
--- a/app/src/main/java/com/ssafy/tiggle/domain/repository/DutchPayRepository.kt
+++ b/app/src/main/java/com/ssafy/tiggle/domain/repository/DutchPayRepository.kt
@@ -1,7 +1,9 @@
package com.ssafy.tiggle.domain.repository
+import com.ssafy.tiggle.domain.entity.dutchpay.DutchPayRequestDetail
import com.ssafy.tiggle.domain.entity.dutchpay.DutchPayRequest
interface DutchPayRepository {
suspend fun createDutchPayRequest(request: DutchPayRequest): Result
+ suspend fun getDutchPayRequestDetail(dutchPayId: Long): Result
}
diff --git a/app/src/main/java/com/ssafy/tiggle/domain/usecase/dutchpay/GetDutchPayRequestDetailUseCase.kt b/app/src/main/java/com/ssafy/tiggle/domain/usecase/dutchpay/GetDutchPayRequestDetailUseCase.kt
new file mode 100644
index 0000000..2ce8b47
--- /dev/null
+++ b/app/src/main/java/com/ssafy/tiggle/domain/usecase/dutchpay/GetDutchPayRequestDetailUseCase.kt
@@ -0,0 +1,13 @@
+package com.ssafy.tiggle.domain.usecase.dutchpay
+
+import com.ssafy.tiggle.domain.entity.dutchpay.DutchPayRequestDetail
+import com.ssafy.tiggle.domain.repository.DutchPayRepository
+import javax.inject.Inject
+
+class GetDutchPayRequestDetailUseCase @Inject constructor(
+ private val dutchPayRepository: DutchPayRepository
+) {
+ suspend operator fun invoke(dutchPayId: Long): Result {
+ return dutchPayRepository.getDutchPayRequestDetail(dutchPayId)
+ }
+}
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 e5d32fd..e5f8b34 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
@@ -1,9 +1,12 @@
package com.ssafy.tiggle.presentation.navigation
+import android.content.Intent
+import android.net.Uri
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.NavEntry
@@ -16,6 +19,7 @@ import com.ssafy.tiggle.presentation.ui.auth.signup.SignUpScreen
import com.ssafy.tiggle.presentation.ui.donation.DonationHistoryScreen
import com.ssafy.tiggle.presentation.ui.donation.DonationStatusScreen
import com.ssafy.tiggle.presentation.ui.dutchpay.CreateDutchPayScreen
+import com.ssafy.tiggle.presentation.ui.dutchpay.DutchpayRecieveScreen
import com.ssafy.tiggle.presentation.ui.growth.GrowthScreen
import com.ssafy.tiggle.presentation.ui.piggybank.MainAccountDetailScreen
import com.ssafy.tiggle.presentation.ui.piggybank.OpenAccountMode
@@ -29,10 +33,44 @@ import com.ssafy.tiggle.presentation.ui.shorts.ShortsScreen
* 앱의 메인 네비게이션
*/
@Composable
-fun NavigationGraph() {
+fun NavigationGraph(
+ intent: Intent?, // Nullable Intent를 받음
+ onDeepLinkHandled: () -> Unit // 딥링크 처리 완료 콜백 함수
+) {
val startDestination = Screen.Login
val navBackStack = rememberNavBackStack(startDestination)
+ LaunchedEffect(intent) {
+ val data: Uri? = intent?.data
+ android.util.Log.d(
+ "NavigationGraph",
+ "LaunchedEffect triggered - intent: $intent, data: $data"
+ )
+
+ if (data != null && data.scheme == "tiggle" && data.host == "dutchpay") {
+ android.util.Log.d("NavigationGraph", "Deep link detected: $data")
+ val dutchPayId = data.lastPathSegment
+ android.util.Log.d("NavigationGraph", "Extracted dutchPayId: $dutchPayId")
+
+ if (dutchPayId != null) {
+ try {
+ navBackStack.clear()
+ navBackStack.add(BottomScreen.PiggyBank)
+ navBackStack.add(Screen.DutchpayRecieve(dutchPayId.toLong()))
+ android.util.Log.d(
+ "NavigationGraph",
+ "Navigation successful to DutchpayRecieve($dutchPayId)"
+ )
+ } catch (e: Exception) {
+ android.util.Log.e("NavigationGraph", "Error navigating to DutchpayRecieve", e)
+ }
+ }
+ }
+ onDeepLinkHandled()
+ }
+
+
+
Scaffold(
bottomBar = {
if (navBackStack.last() is BottomScreen && navBackStack.last() != BottomScreen.Shorts)
@@ -116,17 +154,15 @@ fun NavigationGraph() {
is Screen.OpenAccount -> NavEntry(key) {
OpenAccountScreen(
- mode = key.mode,
onBackClick = { navBackStack.removeLastOrNull() },
onFinish = {
navBackStack.removeLastOrNull()
- },
+ }
)
}
is Screen.RegisterAccount -> NavEntry(key) {
RegisterAccountScreen(
- isEdit = key.isEdit,
onBackClick = { navBackStack.removeLastOrNull() },
onFinish = {
navBackStack.removeLastOrNull()
@@ -141,6 +177,11 @@ fun NavigationGraph() {
)
}
+ is Screen.DutchpayRecieve -> NavEntry(key) {
+ // key에서 dutchPayId를 직접 꺼내서 화면에 전달합니다.
+ DutchpayRecieveScreen(dutchPayId = key.dutchPayId)
+ }
+
is Screen.MainAccountDetail -> NavEntry(key) {
MainAccountDetailScreen(
accountNo = key.accountNo,
diff --git a/app/src/main/java/com/ssafy/tiggle/presentation/navigation/Screen.kt b/app/src/main/java/com/ssafy/tiggle/presentation/navigation/Screen.kt
index 3b88a94..caadc2e 100644
--- a/app/src/main/java/com/ssafy/tiggle/presentation/navigation/Screen.kt
+++ b/app/src/main/java/com/ssafy/tiggle/presentation/navigation/Screen.kt
@@ -48,5 +48,8 @@ sealed interface Screen : NavKey {
@Serializable
object DonationStatus : Screen
+
+ @Serializable
+ data class DutchpayRecieve(val dutchPayId: Long) : Screen
}
diff --git a/app/src/main/java/com/ssafy/tiggle/presentation/ui/auth/login/LoginScreen.kt b/app/src/main/java/com/ssafy/tiggle/presentation/ui/auth/login/LoginScreen.kt
index 302a9c1..5971f0e 100644
--- a/app/src/main/java/com/ssafy/tiggle/presentation/ui/auth/login/LoginScreen.kt
+++ b/app/src/main/java/com/ssafy/tiggle/presentation/ui/auth/login/LoginScreen.kt
@@ -1,6 +1,5 @@
package com.ssafy.tiggle.presentation.ui.auth.login
-import android.annotation.SuppressLint
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
@@ -17,6 +16,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
@@ -64,9 +64,9 @@ fun LoginScreen(
label = "이메일",
placeholder = "이메일을 입력해주세요",
keyboardType = KeyboardType.Email,
+ modifier = Modifier.fillMaxWidth(),
isError = uiState.emailError != null,
- errorMessage = uiState.emailError,
- modifier = Modifier.fillMaxWidth()
+ errorMessage = uiState.emailError
)
Spacer(modifier = Modifier.height(16.dp))
@@ -77,9 +77,10 @@ fun LoginScreen(
label = "비밀번호",
placeholder = "비밀번호를 입력해주세요",
isPassword = true,
+ modifier = Modifier.fillMaxWidth(),
isError = uiState.passwordError != null,
errorMessage = uiState.passwordError,
- modifier = Modifier.fillMaxWidth()
+ imeAction = ImeAction.Done
)
// 비밀번호 찾기
diff --git a/app/src/main/java/com/ssafy/tiggle/presentation/ui/components/TiggleTextField.kt b/app/src/main/java/com/ssafy/tiggle/presentation/ui/components/TiggleTextField.kt
index 92132f9..77f1550 100644
--- a/app/src/main/java/com/ssafy/tiggle/presentation/ui/components/TiggleTextField.kt
+++ b/app/src/main/java/com/ssafy/tiggle/presentation/ui/components/TiggleTextField.kt
@@ -8,7 +8,6 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
-import androidx.compose.ui.text.input.ImeAction
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -21,16 +20,16 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import androidx.compose.ui.focus.onFocusChanged
-import androidx.compose.ui.platform.LocalFocusManager
import com.ssafy.tiggle.presentation.ui.theme.TiggleBlue
import com.ssafy.tiggle.presentation.ui.theme.TiggleGrayLight
import com.ssafy.tiggle.presentation.ui.theme.TiggleGrayText
@@ -46,10 +45,11 @@ fun TiggleTextField(
placeholder: String = "",
isPassword: Boolean = false,
keyboardType: KeyboardType = KeyboardType.Text,
+ maxLines: Int = 1,
modifier: Modifier = Modifier,
isError: Boolean = false,
errorMessage: String? = null,
- maxLines: Int = 1,
+ imeAction: ImeAction = if (maxLines == 1) ImeAction.Next else ImeAction.Done,
minLines: Int = 1
) {
var isPasswordVisible by remember { mutableStateOf(false) }
@@ -106,7 +106,7 @@ fun TiggleTextField(
} else null,
keyboardOptions = KeyboardOptions(
keyboardType = keyboardType,
- imeAction = if (maxLines == 1) ImeAction.Next else ImeAction.Done
+ imeAction = imeAction
),
singleLine = maxLines == 1,
maxLines = maxLines,
@@ -136,7 +136,7 @@ fun TiggleTextField(
@Composable
private fun TiggleTextFieldPreview() {
var text by remember { mutableStateOf("") }
-
+
TiggleTextField(
value = text,
onValueChange = { text = it },
@@ -150,7 +150,7 @@ private fun TiggleTextFieldPreview() {
@Composable
private fun TiggleTextFieldPasswordPreview() {
var password by remember { mutableStateOf("") }
-
+
TiggleTextField(
value = password,
onValueChange = { password = it },
@@ -164,7 +164,7 @@ private fun TiggleTextFieldPasswordPreview() {
@Composable
private fun TiggleTextFieldErrorPreview() {
var text by remember { mutableStateOf("invalid@") }
-
+
TiggleTextField(
value = text,
onValueChange = { text = it },
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 184eff3..e689e39 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
@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
@@ -101,7 +102,9 @@ fun UserPicker(
colors = CardDefaults.cardColors(containerColor = Color.White),
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
) {
- LazyColumn {
+ LazyColumn(
+ modifier = Modifier.heightIn(max = 400.dp)
+ ) {
items(filteredUsers, key = { it.id }) { user ->
UserRow(
user = user,
diff --git a/app/src/main/java/com/ssafy/tiggle/presentation/ui/donation/DonationHistoryScreen.kt b/app/src/main/java/com/ssafy/tiggle/presentation/ui/donation/DonationHistoryScreen.kt
index 1a2fb54..b389d94 100644
--- a/app/src/main/java/com/ssafy/tiggle/presentation/ui/donation/DonationHistoryScreen.kt
+++ b/app/src/main/java/com/ssafy/tiggle/presentation/ui/donation/DonationHistoryScreen.kt
@@ -188,7 +188,7 @@ private fun DonationHistoryItem(
)
Spacer(modifier = Modifier.height(4.dp))
Text(
- text = donation.donatedAt,
+ text = Formatter.formatDateTime(donation.donatedAt),
fontSize = 12.sp,
color = TiggleGrayText,
style = AppTypography.bodySmall
diff --git a/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/DutchPayRequestDetailUiState.kt b/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/DutchPayRequestDetailUiState.kt
new file mode 100644
index 0000000..4f40fc1
--- /dev/null
+++ b/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/DutchPayRequestDetailUiState.kt
@@ -0,0 +1,9 @@
+package com.ssafy.tiggle.presentation.ui.dutchpay
+
+import com.ssafy.tiggle.domain.entity.dutchpay.DutchPayRequestDetail
+
+data class DutchPayRequestDetailUiState(
+ val isLoading: Boolean = false,
+ val dutchPayDetail: DutchPayRequestDetail? = null,
+ val errorMessage: String? = null
+)
diff --git a/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/DutchPayRequestDetailViewModel.kt b/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/DutchPayRequestDetailViewModel.kt
new file mode 100644
index 0000000..caa97de
--- /dev/null
+++ b/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/DutchPayRequestDetailViewModel.kt
@@ -0,0 +1,50 @@
+package com.ssafy.tiggle.presentation.ui.dutchpay
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.ssafy.tiggle.domain.usecase.dutchpay.GetDutchPayRequestDetailUseCase
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class DutchPayRequestDetailViewModel @Inject constructor(
+ private val getDutchPayRequestDetailUseCase: GetDutchPayRequestDetailUseCase
+) : ViewModel() {
+
+ private val _uiState = MutableStateFlow(DutchPayRequestDetailUiState())
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ fun loadDutchPayDetail(dutchPayId: Long) {
+ _uiState.update { it.copy(isLoading = true, errorMessage = null) }
+
+ viewModelScope.launch {
+ getDutchPayRequestDetailUseCase(dutchPayId)
+ .onSuccess { detail ->
+ _uiState.update {
+ it.copy(
+ isLoading = false,
+ dutchPayDetail = detail,
+ errorMessage = null
+ )
+ }
+ }
+ .onFailure { exception ->
+ _uiState.update {
+ it.copy(
+ isLoading = false,
+ errorMessage = exception.message ?: "더치페이 정보를 불러오는데 실패했습니다."
+ )
+ }
+ }
+ }
+ }
+
+ fun clearErrorMessage() {
+ _uiState.update { it.copy(errorMessage = null) }
+ }
+}
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
new file mode 100644
index 0000000..034a903
--- /dev/null
+++ b/app/src/main/java/com/ssafy/tiggle/presentation/ui/dutchpay/DutchpayRecieveScreen.kt
@@ -0,0 +1,362 @@
+package com.ssafy.tiggle.presentation.ui.dutchpay
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.hilt.navigation.compose.hiltViewModel
+import com.ssafy.tiggle.core.utils.Formatter
+import com.ssafy.tiggle.domain.entity.dutchpay.DutchPayRequestDetail
+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.components.TiggleSwitchRow
+import com.ssafy.tiggle.presentation.ui.theme.AppTypography
+import com.ssafy.tiggle.presentation.ui.theme.TiggleBlue
+
+@Composable
+fun DutchpayRecieveScreen(
+ dutchPayId: Long,
+ onBackClick: () -> Unit = {},
+ onPaymentClick: () -> Unit = {},
+ viewModel: DutchPayRequestDetailViewModel = hiltViewModel()
+) {
+ val uiState by viewModel.uiState.collectAsState()
+
+ LaunchedEffect(dutchPayId) {
+ viewModel.loadDutchPayDetail(dutchPayId)
+ }
+
+ TiggleScreenLayout(
+ onBackClick = onBackClick,
+ bottomButton = {
+ uiState.dutchPayDetail?.let { detail ->
+ if (!detail.isCreator) {
+ TiggleButton(
+ text = "송금하기",
+ onClick = onPaymentClick,
+ enabled = !uiState.isLoading,
+ isLoading = false,
+ variant = TiggleButtonVariant.Primary
+ )
+ }
+ }
+ }
+ ) {
+ when {
+ uiState.isLoading -> {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator(color = TiggleBlue)
+ }
+ }
+
+ else -> {
+ uiState.dutchPayDetail?.let { detail ->
+ DutchPayPaymentContent(
+ detail = detail
+ )
+ }
+ }
+ }
+ }
+
+ // 에러 다이얼로그 표시
+ uiState.errorMessage?.let { errorMessage ->
+ AlertDialog(
+ onDismissRequest = { viewModel.clearErrorMessage() },
+ title = {
+ Text("오류")
+ },
+ text = {
+ Text(errorMessage)
+ },
+ confirmButton = {
+ TextButton(
+ onClick = { viewModel.clearErrorMessage() }
+ ) {
+ Text("송금하기")
+ }
+ }
+ )
+ }
+}
+
+@Composable
+private fun DutchPayPaymentContent(
+ detail: DutchPayRequestDetail
+) {
+ var payMoreEnabled by remember { mutableStateOf(detail.payMoreDefault) }
+
+ // 내가 낼 금액 계산
+ val myPaymentAmount = if (payMoreEnabled) {
+ detail.originalAmount + detail.tiggleAmount
+ } else {
+ detail.originalAmount
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Spacer(modifier = Modifier.height(16.dp))
+
+ // 요청자 정보
+ Text(
+ text = "${detail.requesterName}님이",
+ style = AppTypography.headlineSmall,
+ textAlign = TextAlign.Center
+ )
+ Text(
+ text = "더치페이를 요청했습니다",
+ style = AppTypography.headlineSmall,
+ textAlign = TextAlign.Center
+ )
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ // 전달된 메시지 (있을 경우)
+ if (detail.message.isNotBlank()) {
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ colors = CardDefaults.cardColors(containerColor = Color.Gray.copy(alpha = 0.1f)),
+ elevation = CardDefaults.cardElevation(defaultElevation = 0.dp),
+ shape = RoundedCornerShape(8.dp)
+ ) {
+ Column(
+ modifier = Modifier.padding(16.dp)
+ ) {
+ Text(
+ text = "📤 전달된 메시지",
+ style = AppTypography.bodySmall,
+ color = Color.Gray
+ )
+ Spacer(modifier = Modifier.height(4.dp))
+ Text(
+ text = detail.message,
+ style = AppTypography.bodyMedium
+ )
+ }
+ }
+ Spacer(modifier = Modifier.height(16.dp))
+ }
+
+ // 요청 정보
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ colors = CardDefaults.cardColors(containerColor = Color.White),
+ elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
+ shape = RoundedCornerShape(12.dp)
+ ) {
+ Column(
+ modifier = Modifier.padding(16.dp)
+ ) {
+ Text(
+ text = "요청 정보",
+ style = AppTypography.bodyLarge.copy(fontWeight = FontWeight.Bold)
+ )
+
+ Spacer(modifier = Modifier.height(12.dp))
+
+ DetailRow(label = "요청자", value = detail.requesterName)
+ Spacer(modifier = Modifier.height(8.dp))
+ DetailRow(label = "참여 인원", value = "${detail.participantCount}명")
+ Spacer(modifier = Modifier.height(8.dp))
+ DetailRow(label = "총 금액", value = Formatter.formatCurrency(detail.totalAmount))
+ Spacer(modifier = Modifier.height(8.dp))
+ DetailRow(label = "요청 일시", value = Formatter.formatDateTime(detail.requestedAt))
+ }
+ }
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ // 내가 내는 금액
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ colors = CardDefaults.cardColors(containerColor = TiggleBlue.copy(alpha = 0.1f)),
+ elevation = CardDefaults.cardElevation(defaultElevation = 0.dp),
+ shape = RoundedCornerShape(12.dp)
+ ) {
+ Column(
+ modifier = Modifier.padding(20.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = "내가 내는 금액",
+ style = AppTypography.bodyMedium,
+ color = Color.Gray
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = Formatter.formatCurrency(myPaymentAmount),
+ style = AppTypography.headlineLarge.copy(
+ fontSize = 32.sp,
+ fontWeight = FontWeight.Bold,
+ color = TiggleBlue
+ )
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ HorizontalDivider(color = Color.Gray.copy(alpha = 0.2f))
+
+ Spacer(modifier = Modifier.height(12.dp))
+
+ DetailRow(label = "원래 금액", value = Formatter.formatCurrency(detail.originalAmount))
+
+ if (payMoreEnabled && detail.tiggleAmount > 0) {
+ Spacer(modifier = Modifier.height(8.dp))
+ DetailRow(
+ label = "티끌",
+ value = "+${Formatter.formatCurrency(detail.tiggleAmount)}",
+ valueColor = TiggleBlue
+ )
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ // 돈 더내고 잔돈 기부하기 스위치
+ TiggleSwitchRow(
+ title = "돈 더내고 잔돈 기부하기",
+ subtitle = "자투리 금액을 티끌 저금통에 적립",
+ checked = payMoreEnabled,
+ onCheckedChange = { payMoreEnabled = it }
+ )
+
+ Spacer(modifier = Modifier.height(40.dp))
+ }
+}
+
+@Composable
+private fun DetailRow(
+ label: String,
+ value: String,
+ valueColor: Color = Color.Black
+) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.Top
+ ) {
+ Text(
+ text = label,
+ style = AppTypography.bodyMedium,
+ color = Color.Gray,
+ modifier = Modifier.weight(1f)
+ )
+ Text(
+ text = value,
+ style = AppTypography.bodyMedium.copy(
+ fontWeight = FontWeight.Medium
+ ),
+ color = valueColor,
+ textAlign = TextAlign.End,
+ modifier = Modifier.weight(1f)
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun PreviewDutchPayPayment() {
+ val sampleDetail = DutchPayRequestDetail(
+ dutchPayId = 9L,
+ title = "어제 먹은 치킨 정산",
+ message = "오늘 치킨 먹은 금액입니다!",
+ requesterName = "최지원",
+ participantCount = 3,
+ totalAmount = 50000L,
+ requestedAt = "2025.08.20 14:32",
+ myAmount = 17000L,
+ originalAmount = 16666L,
+ tiggleAmount = 334L,
+ payMoreDefault = true,
+ isCreator = false
+ )
+
+ TiggleScreenLayout(
+ onBackClick = {},
+ bottomButton = {
+ TiggleButton(
+ text = "송금하기",
+ onClick = {},
+ enabled = true,
+ isLoading = false,
+ variant = TiggleButtonVariant.Primary
+ )
+ }
+ ) {
+ DutchPayPaymentContent(detail = sampleDetail)
+ }
+}
+
+
+@Preview(showBackground = true)
+@Composable
+private fun PreviewNoTiggleDutchPayPayment() {
+ val sampleDetail = DutchPayRequestDetail(
+ dutchPayId = 9L,
+ title = "어제 먹은 치킨 정산",
+ message = "오늘 치킨 먹은 금액입니다!",
+ requesterName = "최지원",
+ participantCount = 3,
+ totalAmount = 50000L,
+ requestedAt = "2025.08.20 14:32",
+ myAmount = 17000L,
+ originalAmount = 16666L,
+ tiggleAmount = 334L,
+ payMoreDefault = false,
+ isCreator = false
+ )
+
+ TiggleScreenLayout(
+ onBackClick = {},
+ bottomButton = {
+ TiggleButton(
+ text = "송금하기",
+ onClick = {},
+ enabled = true,
+ isLoading = false,
+ variant = TiggleButtonVariant.Primary
+ )
+ }
+ ) {
+ DutchPayPaymentContent(detail = sampleDetail)
+ }
+}