164 lines
7.5 KiB
Kotlin
164 lines
7.5 KiB
Kotlin
|
|
package ui
|
||
|
|
|
||
|
|
import androidx.compose.foundation.border
|
||
|
|
import androidx.compose.foundation.layout.*
|
||
|
|
import androidx.compose.foundation.lazy.LazyColumn
|
||
|
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||
|
|
import androidx.compose.material.*
|
||
|
|
import androidx.compose.material.icons.Icons
|
||
|
|
import androidx.compose.material.icons.filled.FolderOpen
|
||
|
|
import androidx.compose.runtime.*
|
||
|
|
import androidx.compose.ui.Alignment
|
||
|
|
import androidx.compose.ui.DragData
|
||
|
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||
|
|
import androidx.compose.ui.Modifier
|
||
|
|
import androidx.compose.ui.graphics.Color
|
||
|
|
import androidx.compose.ui.onExternalDrag
|
||
|
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||
|
|
import androidx.compose.ui.unit.dp
|
||
|
|
import androidx.compose.ui.unit.sp
|
||
|
|
import kotlinx.coroutines.launch
|
||
|
|
import model.AppConfig
|
||
|
|
import network.KisAuthService
|
||
|
|
import org.jetbrains.exposed.sql.deleteAll
|
||
|
|
import org.jetbrains.exposed.sql.insert
|
||
|
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||
|
|
import javax.swing.JFileChooser
|
||
|
|
import javax.swing.filechooser.FileNameExtensionFilter
|
||
|
|
import androidx.compose.ui.unit.dp
|
||
|
|
import androidx.compose.ui.window.Dialog // 파일 선택기용
|
||
|
|
|
||
|
|
|
||
|
|
@OptIn(ExperimentalComposeUiApi::class)
|
||
|
|
@Composable
|
||
|
|
fun SettingsScreen(
|
||
|
|
initialConfig: AppConfig, // 모델 경로가 포함된 확장된 AppConfig 필요
|
||
|
|
onAuthSuccess: (AppConfig, String) -> Unit
|
||
|
|
) {
|
||
|
|
val scope = rememberCoroutineScope()
|
||
|
|
val authService = remember { KisAuthService() }
|
||
|
|
|
||
|
|
// 화면 입력 상태값
|
||
|
|
var appKey by remember { mutableStateOf(initialConfig.appKey) }
|
||
|
|
var secretKey by remember { mutableStateOf(initialConfig.secretKey) }
|
||
|
|
var accountNo by remember { mutableStateOf(initialConfig.accountNo) }
|
||
|
|
var isSimulation by remember { mutableStateOf(initialConfig.isSimulation) }
|
||
|
|
var modelPath by remember { mutableStateOf(initialConfig.modelPath ?: "") } // AI 모델 경로
|
||
|
|
|
||
|
|
var statusMessage by remember { mutableStateOf("설정 정보를 입력하세요.") }
|
||
|
|
var isLoading by remember { mutableStateOf(false) }
|
||
|
|
|
||
|
|
LazyColumn(modifier = Modifier.fillMaxSize().padding(24.dp)) {
|
||
|
|
item {
|
||
|
|
Text("API 및 계좌 설정", style = MaterialTheme.typography.h6)
|
||
|
|
OutlinedTextField(value = appKey, onValueChange = { appKey = it }, label = { Text("App Key") }, modifier = Modifier.fillMaxWidth())
|
||
|
|
OutlinedTextField(value = secretKey, onValueChange = { secretKey = it }, label = { Text("Secret Key") }, modifier = Modifier.fillMaxWidth(), visualTransformation = PasswordVisualTransformation())
|
||
|
|
OutlinedTextField(value = accountNo, onValueChange = { accountNo = it }, label = { Text("계좌번호") }, modifier = Modifier.fillMaxWidth())
|
||
|
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||
|
|
Checkbox(checked = isSimulation, onCheckedChange = { isSimulation = it })
|
||
|
|
Text("모의투자 서버 사용")
|
||
|
|
}
|
||
|
|
|
||
|
|
Divider(Modifier.padding(vertical = 16.dp))
|
||
|
|
|
||
|
|
// --- 추가된 AI 모델 설정 섹션 ---
|
||
|
|
Text("AI 모델 설정 (Gemma-2-9b)", style = MaterialTheme.typography.h6)
|
||
|
|
Spacer(modifier = Modifier.height(8.dp))
|
||
|
|
|
||
|
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||
|
|
OutlinedTextField(
|
||
|
|
value = modelPath,
|
||
|
|
onValueChange = { modelPath = it },
|
||
|
|
label = { Text("GGUF 모델 경로") },
|
||
|
|
modifier = Modifier.weight(1f),
|
||
|
|
placeholder = { Text("파일을 선택하거나 드래그하세요") }
|
||
|
|
)
|
||
|
|
IconButton(onClick = {
|
||
|
|
val chooser = JFileChooser().apply {
|
||
|
|
fileFilter = FileNameExtensionFilter("GGUF 모델", "gguf")
|
||
|
|
}
|
||
|
|
if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
|
||
|
|
modelPath = chooser.selectedFile.absolutePath
|
||
|
|
}
|
||
|
|
}) {
|
||
|
|
Icon(Icons.Default.FolderOpen, contentDescription = "파일 선택")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 드래그 앤 드롭 영역
|
||
|
|
Box(
|
||
|
|
modifier = Modifier
|
||
|
|
.fillMaxWidth()
|
||
|
|
.height(80.dp)
|
||
|
|
.padding(top = 8.dp)
|
||
|
|
.border(1.dp, Color.LightGray, RoundedCornerShape(8.dp))
|
||
|
|
.onExternalDrag(onDrop = { state ->
|
||
|
|
val data = state.dragData
|
||
|
|
if (data is DragData.FilesList) {
|
||
|
|
val path = data.readFiles().firstOrNull()?.removePrefix("file:")
|
||
|
|
if (path?.endsWith(".gguf") == true) modelPath = path
|
||
|
|
}
|
||
|
|
}),
|
||
|
|
contentAlignment = Alignment.Center
|
||
|
|
) {
|
||
|
|
Text("여기에 .gguf 파일을 드래그하여 놓으세요", fontSize = 12.sp, color = Color.Gray)
|
||
|
|
}
|
||
|
|
|
||
|
|
Spacer(modifier = Modifier.height(24.dp))
|
||
|
|
|
||
|
|
// 저장 및 접속 버튼
|
||
|
|
Button(
|
||
|
|
modifier = Modifier.fillMaxWidth().height(50.dp),
|
||
|
|
enabled = !isLoading,
|
||
|
|
onClick = {
|
||
|
|
isLoading = true
|
||
|
|
scope.launch {
|
||
|
|
// 1. 새로운 설정 객체 생성 (순서 주의: isSimulation 다음 modelPath)
|
||
|
|
val config = AppConfig(
|
||
|
|
appKey = appKey.trim(),
|
||
|
|
secretKey = secretKey.trim(),
|
||
|
|
accountNo = accountNo.trim(),
|
||
|
|
isSimulation = isSimulation,
|
||
|
|
modelPath = modelPath
|
||
|
|
)
|
||
|
|
|
||
|
|
transaction {
|
||
|
|
ConfigTable.deleteAll()
|
||
|
|
ConfigTable.insert {
|
||
|
|
it[ConfigTable.appKey] = config.appKey
|
||
|
|
it[ConfigTable.secretKey] = config.secretKey
|
||
|
|
it[ConfigTable.accountNo] = config.accountNo
|
||
|
|
it[ConfigTable.isSimulation] = config.isSimulation
|
||
|
|
it[ConfigTable.modelPath] = config.modelPath
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
statusMessage = "인증 토큰 발급 시도 중..."
|
||
|
|
authService.fetchAccessToken(appKey, secretKey, isSimulation)
|
||
|
|
.onSuccess { response ->
|
||
|
|
statusMessage = "✅ 인증 성공!"
|
||
|
|
onAuthSuccess(config, response.access_token)
|
||
|
|
}.onFailure {
|
||
|
|
statusMessage = "❌ 인증 실패(정보 저장됨): ${it.localizedMessage}"
|
||
|
|
}
|
||
|
|
isLoading = false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
) {
|
||
|
|
if (isLoading) {
|
||
|
|
// [수정된 프로그래스 바] size -> Modifier.size
|
||
|
|
CircularProgressIndicator(
|
||
|
|
modifier = Modifier.size(20.dp),
|
||
|
|
color = Color.White,
|
||
|
|
strokeWidth = 2.dp
|
||
|
|
)
|
||
|
|
} else {
|
||
|
|
Text("설정 저장 및 접속 시작")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Spacer(modifier = Modifier.height(16.dp))
|
||
|
|
Text(statusMessage, color = if (statusMessage.contains("✅")) Color.Green else Color.Gray)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|