atrade/src/main/kotlin/ui/StockDetailArea.kt

149 lines
5.3 KiB
Kotlin
Raw Normal View History

2026-01-10 18:16:50 +09:00
package ui
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items // 반드시 수동 import 확인
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.*
import io.ktor.client.engine.cio.CIO
// 아래 두 import가 'delegate' 에러를 해결합니다.
import androidx.compose.runtime.getValue
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.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import model.AppConfig
import model.BalanceSummary
import model.CandleData
import model.RankingStock
import model.StockHolding
import network.KisTradeService
import network.KisWebSocketManager
import kotlin.collections.isNotEmpty
@Composable
2026-01-13 16:04:25 +09:00
fun StockDetailSection(
stockCode: String,
stockName: String,
isDomestic: Boolean,
tradeService: KisTradeService,
wsManager: KisWebSocketManager
2026-01-10 18:16:50 +09:00
) {
var chartData by remember { mutableStateOf<List<CandleData>>(emptyList()) }
var isLoading by remember { mutableStateOf(false) }
var resultMessage by remember { mutableStateOf("") }
var isSuccess by remember { mutableStateOf(true) }
2026-01-13 16:04:25 +09:00
// 이전 종목 코드를 기억하기 위한 상태
var previousCode by remember { mutableStateOf("") }
// 종목 변경 시 데이터 로드 및 웹소켓 구독 관리
LaunchedEffect(stockCode) {
if (stockCode.isEmpty()) return@LaunchedEffect
2026-01-10 18:16:50 +09:00
isLoading = true
2026-01-13 16:04:25 +09:00
// 1. 웹소켓 구독 관리: 이전 종목 해제 -> 새 종목 구독
if (previousCode.isNotEmpty()) {
wsManager.unsubscribeStock(previousCode)
}
wsManager.subscribeStock(stockCode)
previousCode = stockCode
// 2. 차트 데이터 로드 (KisSession 기반으로 파라미터 간소화)
tradeService.fetchChartData(stockCode, isDomestic)
.onSuccess { data ->
println("✅ 차트 데이터 로드 성공: ${data.size}") // ${} 사용하여 정확히 출력
chartData = data
}
.onFailure { error ->
println("❌ 차트 데이터 로드 실패: ${error.localizedMessage}")
chartData = emptyList()
}
2026-01-10 18:16:50 +09:00
isLoading = false
}
2026-01-13 16:04:25 +09:00
val latestPrice by wsManager.currentPrice // 웹소켓에서 업데이트되는 현재가
2026-01-10 18:16:50 +09:00
2026-01-13 16:04:25 +09:00
LaunchedEffect(latestPrice) {
println("latestPrice $latestPrice")
2026-01-10 18:16:50 +09:00
2026-01-13 16:04:25 +09:00
if (chartData.isNotEmpty() && latestPrice != "0") {
// 마지막 캔들 정보 업데이트
val priceDouble = latestPrice.replace(",", "").toDoubleOrNull() ?: return@LaunchedEffect
val lastCandle = chartData.last()
val updatedCandle = lastCandle.copy(
stck_clpr = latestPrice,
stck_hgpr = if (priceDouble > (lastCandle.stck_hgpr.toDoubleOrNull() ?: 0.0)) latestPrice else lastCandle.stck_hgpr,
stck_lwpr = if (priceDouble < (lastCandle.stck_lwpr.toDoubleOrNull() ?: Double.MAX_VALUE)) latestPrice else lastCandle.stck_lwpr
)
chartData = chartData.dropLast(1) + updatedCandle
println("chartData.size $chartData.size")
2026-01-10 18:16:50 +09:00
}
2026-01-13 16:04:25 +09:00
}
2026-01-10 18:16:50 +09:00
2026-01-13 16:04:25 +09:00
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
// [상단] 종목명 및 상태 메시지
StockHeader(stockName, stockCode, isDomestic, resultMessage, isSuccess)
2026-01-10 18:16:50 +09:00
2026-01-13 16:04:25 +09:00
// [중앙] 캔들 차트 (Card 내부)
2026-01-10 18:16:50 +09:00
Card(
2026-01-13 16:04:25 +09:00
modifier = Modifier.fillMaxWidth().height(300.dp),
2026-01-10 18:16:50 +09:00
backgroundColor = Color(0xFF121212)
) {
if (isLoading) {
Box(contentAlignment = Alignment.Center) { CircularProgressIndicator(color = Color.White) }
} else {
2026-01-13 16:04:25 +09:00
CandleChart(data = chartData, modifier = Modifier.padding(16.dp))
2026-01-10 18:16:50 +09:00
}
}
2026-01-13 16:04:25 +09:00
Spacer(modifier = Modifier.height(12.dp))
// [중앙 하단] AI 투자 전략
2026-01-10 18:16:50 +09:00
AiAnalysisView(
2026-01-13 16:04:25 +09:00
stockName = stockName,
2026-01-10 18:16:50 +09:00
currentPrice = wsManager.currentPrice.value,
trades = wsManager.tradeLogs
)
2026-01-13 16:04:25 +09:00
Spacer(modifier = Modifier.height(12.dp))
// [하단] 실시간 체결 내역 및 주문 섹션
Row(modifier = Modifier.weight(1f)) {
// 실시간 체결 리스트
Column(modifier = Modifier.weight(1f)) {
Text("실시간 체결", style = MaterialTheme.typography.subtitle2, fontWeight = FontWeight.Bold)
RealTimeTradeList(wsManager.tradeLogs)
2026-01-10 18:16:50 +09:00
}
2026-01-13 16:04:25 +09:00
Spacer(modifier = Modifier.width(12.dp))
// 주문 섹션 (인자 간소화)
OrderSection(
stockCode = stockCode,
currentPrice = wsManager.currentPrice.value,
onOrderResult = { msg, success ->
resultMessage = msg
isSuccess = success
2026-01-10 18:16:50 +09:00
}
2026-01-13 16:04:25 +09:00
)
2026-01-10 18:16:50 +09:00
}
}
}