From 3c7f652f381c7aa43a20f18d941f818bd01b983b Mon Sep 17 00:00:00 2001 From: lunaticbum Date: Wed, 29 Apr 2026 17:56:16 +0900 Subject: [PATCH] ... --- src/main/kotlin/network/KisOverseasService.kt | 2 +- src/main/kotlin/ui/TradingDecisionLog.kt | 87 +++++++++++-------- 2 files changed, 51 insertions(+), 38 deletions(-) diff --git a/src/main/kotlin/network/KisOverseasService.kt b/src/main/kotlin/network/KisOverseasService.kt index 87997fd..e5f8397 100644 --- a/src/main/kotlin/network/KisOverseasService.kt +++ b/src/main/kotlin/network/KisOverseasService.kt @@ -197,7 +197,7 @@ class OverseasFinancialService( suspend fun fetchKeyMetrics(symbol: String): Result { return try { val response: List = client.get("$baseUrl/key-metrics-ttm/$symbol") { - parameter("apikey", apiKey) + parameter("apikey", "FzvO_2P679KGRDUVx3u7rkMvCvcqiynu") }.body() if (response.isNotEmpty()) { diff --git a/src/main/kotlin/ui/TradingDecisionLog.kt b/src/main/kotlin/ui/TradingDecisionLog.kt index 38b22c0..128f94d 100644 --- a/src/main/kotlin/ui/TradingDecisionLog.kt +++ b/src/main/kotlin/ui/TradingDecisionLog.kt @@ -3,12 +3,10 @@ package ui import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.gestures.awaitEachGesture -import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.gestures.waitForUpOrCancellation import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn @@ -32,9 +30,12 @@ import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.PointerType +import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.isSecondaryPressed +import androidx.compose.ui.input.pointer.onPointerEvent import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInParent import androidx.compose.ui.onExternalDrag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction @@ -79,6 +80,7 @@ fun TradingDecisionLog() { webSocketConnect = AutoTradingManager.webSocketConnect } val scrollState = rememberScrollState() + val filterScrollState = rememberScrollState() // [핵심] 원본 로그에서 필터 조건에 맞는 리스트만 산출 val filteredLogs = TradingLogStore.decisionLogs.filter { log -> @@ -131,6 +133,7 @@ fun TradingDecisionLog() { .fillMaxWidth() .horizontalScroll(scrollState), horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically ) { filterOptions.forEach { option -> val isSelected = selectedFilters.contains(option) @@ -175,7 +178,8 @@ fun TradingDecisionLog() { if (newFilters.isEmpty()) newFilters.add("전체") selectedFilters = newFilters }, - label = option + label = option, + scrollState = scrollState ) } } @@ -787,65 +791,74 @@ fun SettingSwitchField( } } } -@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class) +@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class) @Composable fun FilterChipWithRightClick( label: String, isSelected: Boolean, onClick: () -> Unit, - onClear: () -> Unit + onClear: () -> Unit, + scrollState: ScrollState // 스크롤 제어를 위해 추가 ) { var showMenu by remember { mutableStateOf(false) } + val coroutineScope = rememberCoroutineScope() - Box { + var componentOffset by remember { mutableStateOf(0f) } + var componentWidth by remember { mutableStateOf(0) } // 칩 너비 추가 + + + Box( + modifier = Modifier.onGloballyPositioned { coordinates -> + componentOffset = coordinates.positionInParent().x + componentWidth = coordinates.size.width // 칩의 실제 너비 저장 + } + ) { Surface( modifier = Modifier .padding(end = 6.dp) .pointerInput(label) { - awaitEachGesture { - val down = awaitFirstDown() - // 💡 마우스 우클릭 검사 - if (down.type == PointerType.Mouse && currentEvent.buttons.isSecondaryPressed) { - down.consume() - showMenu = true - } else { - // 💡 터치/좌클릭 시 롱클릭 판별 - val up = withTimeoutOrNull(viewConfiguration.longPressTimeoutMillis) { - waitForUpOrCancellation() - } - if (up == null) { - showMenu = true // 롱클릭 - } else { - up.consume() - onClick() // 일반 클릭 + detectTapGestures( + onTap = { + onClick() + coroutineScope.launch { + // 부모(Row)의 전체 너비 가져오기 + val containerWidth = scrollState.maxValue + scrollState.viewportSize + val viewportWidth = scrollState.viewportSize + + // 🎯 중앙 정렬 공식: + // (칩의 절대 위치) - (화면 절반) + (칩 너비의 절반) + val targetScroll = (scrollState.value + componentOffset + (componentWidth / 2)) - (viewportWidth / 2) + + // 0과 최대 스크롤 범위 사이로 제한하여 부드럽게 이동 + scrollState.animateScrollTo( + targetScroll.toInt().coerceIn(0, scrollState.maxValue) + ) } } - } + ) + } + // 마우스 우클릭 별도 감지 + .onPointerEvent(PointerEventType.Press) { event -> + if (event.buttons.isSecondaryPressed) showMenu = true }, - shape = RoundedCornerShape(16.dp), + shape = RoundedCornerShape(8.dp), color = if (isSelected) Color(0xFF0E62CF) else Color(0xFFF0F2F5), border = BorderStroke(1.dp, if (isSelected) Color(0xFF0E62CF) else Color.LightGray) ) { Text( text = label, - modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp), + modifier = Modifier.padding(horizontal = 6.dp, vertical = 6.dp), fontSize = 11.sp, - color = if (isSelected) Color.White else Color.Black, - fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal + color = if (isSelected) Color.White else Color.Black ) } - // 💡 우클릭 시 나타나는 메뉴 - DropdownMenu( - expanded = showMenu, - onDismissRequest = { showMenu = false } - ) { + DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) { DropdownMenuItem(onClick = { - onClear() // 실제 삭제 수행 + onClear() showMenu = false }) { - val menuText = if (label == "전체") "전체 로그 초기화" else "'$label' 관련 로그 삭제" - Text(menuText, color = Color.Red, fontSize = 12.sp) + Text(if (label == "전체") "전체 로그 초기화" else "'$label' 삭제", color = Color.Red) } } }