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

View File

@ -3,12 +3,10 @@ package ui
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border 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.detectTapGestures
import androidx.compose.foundation.gestures.waitForUpOrCancellation
import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
@ -32,9 +30,12 @@ import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color 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.isSecondaryPressed
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.input.pointer.pointerInput 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.onExternalDrag
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
@ -79,6 +80,7 @@ fun TradingDecisionLog() {
webSocketConnect = AutoTradingManager.webSocketConnect webSocketConnect = AutoTradingManager.webSocketConnect
} }
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
val filterScrollState = rememberScrollState()
// [핵심] 원본 로그에서 필터 조건에 맞는 리스트만 산출 // [핵심] 원본 로그에서 필터 조건에 맞는 리스트만 산출
val filteredLogs = TradingLogStore.decisionLogs.filter { log -> val filteredLogs = TradingLogStore.decisionLogs.filter { log ->
@ -131,6 +133,7 @@ fun TradingDecisionLog() {
.fillMaxWidth() .fillMaxWidth()
.horizontalScroll(scrollState), .horizontalScroll(scrollState),
horizontalArrangement = Arrangement.spacedBy(4.dp), horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically
) { ) {
filterOptions.forEach { option -> filterOptions.forEach { option ->
val isSelected = selectedFilters.contains(option) val isSelected = selectedFilters.contains(option)
@ -175,7 +178,8 @@ fun TradingDecisionLog() {
if (newFilters.isEmpty()) newFilters.add("전체") if (newFilters.isEmpty()) newFilters.add("전체")
selectedFilters = newFilters 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 @Composable
fun FilterChipWithRightClick( fun FilterChipWithRightClick(
label: String, label: String,
isSelected: Boolean, isSelected: Boolean,
onClick: () -> Unit, onClick: () -> Unit,
onClear: () -> Unit onClear: () -> Unit,
scrollState: ScrollState // 스크롤 제어를 위해 추가
) { ) {
var showMenu by remember { mutableStateOf(false) } 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( Surface(
modifier = Modifier modifier = Modifier
.padding(end = 6.dp) .padding(end = 6.dp)
.pointerInput(label) { .pointerInput(label) {
awaitEachGesture { detectTapGestures(
val down = awaitFirstDown() onTap = {
// 💡 마우스 우클릭 검사 onClick()
if (down.type == PointerType.Mouse && currentEvent.buttons.isSecondaryPressed) { coroutineScope.launch {
down.consume() // 부모(Row)의 전체 너비 가져오기
showMenu = true val containerWidth = scrollState.maxValue + scrollState.viewportSize
} else { val viewportWidth = scrollState.viewportSize
// 💡 터치/좌클릭 시 롱클릭 판별
val up = withTimeoutOrNull(viewConfiguration.longPressTimeoutMillis) { // 🎯 중앙 정렬 공식:
waitForUpOrCancellation() // (칩의 절대 위치) - (화면 절반) + (칩 너비의 절반)
} val targetScroll = (scrollState.value + componentOffset + (componentWidth / 2)) - (viewportWidth / 2)
if (up == null) {
showMenu = true // 롱클릭 // 0과 최대 스크롤 범위 사이로 제한하여 부드럽게 이동
} else { scrollState.animateScrollTo(
up.consume() targetScroll.toInt().coerceIn(0, scrollState.maxValue)
onClick() // 일반 클릭 )
} }
} }
} )
}
// 마우스 우클릭 별도 감지
.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), color = if (isSelected) Color(0xFF0E62CF) else Color(0xFFF0F2F5),
border = BorderStroke(1.dp, if (isSelected) Color(0xFF0E62CF) else Color.LightGray) border = BorderStroke(1.dp, if (isSelected) Color(0xFF0E62CF) else Color.LightGray)
) { ) {
Text( Text(
text = label, text = label,
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp), modifier = Modifier.padding(horizontal = 6.dp, vertical = 6.dp),
fontSize = 11.sp, fontSize = 11.sp,
color = if (isSelected) Color.White else Color.Black, color = if (isSelected) Color.White else Color.Black
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal
) )
} }
// 💡 우클릭 시 나타나는 메뉴 DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false }
) {
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
onClear() // 실제 삭제 수행 onClear()
showMenu = false showMenu = false
}) { }) {
val menuText = if (label == "전체") "전체 로그 초기화" else "'$label' 관련 로그 삭제" Text(if (label == "전체") "전체 로그 초기화" else "'$label' 삭제", color = Color.Red)
Text(menuText, color = Color.Red, fontSize = 12.sp)
} }
} }
} }