146 lines
5.7 KiB
Kotlin
146 lines
5.7 KiB
Kotlin
// src/main/kotlin/ui/BalanceSection.kt
|
|
package ui
|
|
|
|
import androidx.compose.foundation.clickable
|
|
import androidx.compose.foundation.layout.*
|
|
import androidx.compose.foundation.lazy.LazyColumn
|
|
import androidx.compose.foundation.lazy.items
|
|
import androidx.compose.material.*
|
|
import androidx.compose.runtime.*
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.text.font.FontWeight
|
|
import androidx.compose.ui.unit.dp
|
|
import androidx.compose.ui.unit.sp
|
|
import model.UnifiedBalance
|
|
import network.KisTradeService
|
|
|
|
@Composable
|
|
fun BalanceSection(
|
|
tradeService: KisTradeService,
|
|
onStockSelect: (code: String, name: String, isDomestic: Boolean) -> Unit
|
|
) {
|
|
var balanceData by remember { mutableStateOf<UnifiedBalance?>(null) }
|
|
var isLoading by remember { mutableStateOf(false) }
|
|
|
|
// 화면 진입 시 및 갱신 시 데이터 로드
|
|
LaunchedEffect(Unit) {
|
|
isLoading = true
|
|
tradeService.fetchIntegratedBalance().onSuccess {
|
|
balanceData = it
|
|
}.onFailure {
|
|
println("❌ 잔고 로드 실패: ${it.localizedMessage}")
|
|
}
|
|
isLoading = false
|
|
}
|
|
|
|
Column(modifier = Modifier.fillMaxSize()) {
|
|
Text(
|
|
text = "나의 자산",
|
|
style = MaterialTheme.typography.h6,
|
|
fontWeight = FontWeight.Bold,
|
|
modifier = Modifier.padding(bottom = 8.dp)
|
|
)
|
|
|
|
// 1. 자산 요약 카드
|
|
BalanceSummaryCard(balanceData)
|
|
|
|
Spacer(modifier = Modifier.height(16.dp))
|
|
|
|
// 2. 통합 보유 종목 리스트
|
|
Text(
|
|
text = "보유 종목",
|
|
style = MaterialTheme.typography.subtitle1,
|
|
fontWeight = FontWeight.Bold,
|
|
modifier = Modifier.padding(vertical = 8.dp)
|
|
)
|
|
|
|
if (isLoading) {
|
|
Box(Modifier.fillMaxSize(), contentAlignment = androidx.compose.ui.Alignment.Center) {
|
|
CircularProgressIndicator()
|
|
}
|
|
} else {
|
|
LazyColumn(modifier = Modifier.weight(1f, true)) {
|
|
items(balanceData?.holdings ?: emptyList()) { holding ->
|
|
UnifiedStockItemRow(holding) {
|
|
onStockSelect(holding.code, holding.name, holding.isDomestic)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun BalanceSummaryCard(summary: UnifiedBalance?) {
|
|
Card(
|
|
elevation = 2.dp,
|
|
shape = androidx.compose.foundation.shape.RoundedCornerShape(8.dp),
|
|
modifier = Modifier.fillMaxWidth(),
|
|
backgroundColor = androidx.compose.ui.graphics.Color.White
|
|
) {
|
|
Column(modifier = Modifier.padding(16.dp)) {
|
|
Text("총 평가 자산", style = MaterialTheme.typography.caption, color = androidx.compose.ui.graphics.Color.Gray)
|
|
Text(
|
|
text = "${summary?.totalAsset ?: "0"} 원",
|
|
style = MaterialTheme.typography.h5,
|
|
fontWeight = FontWeight.Bold
|
|
)
|
|
|
|
val rate = summary?.totalProfitRate?.toDoubleOrNull() ?: 0.0
|
|
val color = if (rate > 0) androidx.compose.ui.graphics.Color.Red
|
|
else if (rate < 0) androidx.compose.ui.graphics.Color.Blue
|
|
else androidx.compose.ui.graphics.Color.DarkGray
|
|
|
|
Text(
|
|
text = "수익률: ${if (rate > 0) "+" else ""}$rate%",
|
|
color = color,
|
|
style = MaterialTheme.typography.body2,
|
|
fontWeight = FontWeight.Medium
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun UnifiedStockItemRow(holding: model.UnifiedStockHolding, onClick: () -> Unit) {
|
|
Card(
|
|
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp).clickable { onClick() },
|
|
elevation = 1.dp
|
|
) {
|
|
Row(modifier = Modifier.padding(12.dp), verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) {
|
|
Column(modifier = Modifier.weight(1f)) {
|
|
Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) {
|
|
// 국내/해외 구분 배지
|
|
Surface(
|
|
color = if (holding.isDomestic) androidx.compose.ui.graphics.Color(0xFFE3F2FD)
|
|
else androidx.compose.ui.graphics.Color(0xFFF3E5F5),
|
|
shape = androidx.compose.foundation.shape.RoundedCornerShape(4.dp)
|
|
) {
|
|
Text(
|
|
text = if (holding.isDomestic) "국내" else "해외",
|
|
modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp),
|
|
fontSize = 10.sp,
|
|
color = if (holding.isDomestic) androidx.compose.ui.graphics.Color.Blue
|
|
else androidx.compose.ui.graphics.Color(0xFF7B1FA2)
|
|
)
|
|
}
|
|
Spacer(Modifier.width(4.dp))
|
|
Text(holding.name, fontWeight = FontWeight.Bold, maxLines = 1)
|
|
}
|
|
Text(holding.code, style = MaterialTheme.typography.caption, color = androidx.compose.ui.graphics.Color.Gray)
|
|
}
|
|
|
|
Column(horizontalAlignment = androidx.compose.ui.Alignment.End) {
|
|
Text("${holding.currentPrice} 원")
|
|
val rate = holding.profitRate.toDoubleOrNull() ?: 0.0
|
|
Text(
|
|
text = "${if (rate > 0) "▲" else if (rate < 0) "▼" else ""}${holding.profitRate}%",
|
|
color = if (rate > 0) androidx.compose.ui.graphics.Color.Red
|
|
else if (rate < 0) androidx.compose.ui.graphics.Color.Blue
|
|
else androidx.compose.ui.graphics.Color.DarkGray,
|
|
fontSize = 12.sp
|
|
)
|
|
}
|
|
}
|
|
}
|
|
} |