This commit is contained in:
lunaticbum 2026-04-29 17:56:16 +09:00
parent 9fefdc22f2
commit 3c7f652f38
2 changed files with 51 additions and 38 deletions

View File

@ -197,7 +197,7 @@ class OverseasFinancialService(
suspend fun fetchKeyMetrics(symbol: String): Result<KeyMetrics> {
return try {
val response: List<KeyMetrics> = client.get("$baseUrl/key-metrics-ttm/$symbol") {
parameter("apikey", apiKey)
parameter("apikey", "FzvO_2P679KGRDUVx3u7rkMvCvcqiynu")
}.body()
if (response.isNotEmpty()) {

View File

@ -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)
}
}
}