This commit is contained in:
2026-10-06 17:55:03 +09:00
parent dab3ab7993
commit 7a7de487a4
3 changed files with 188 additions and 198 deletions
+1 -1
View File
@@ -528,7 +528,7 @@ object RagService {
var finalDecision = "HOLD" var finalDecision = "HOLD"
var finalReason = "" var finalReason = ""
var grade = AutoTradingManager.getInvestmentGrade(tempDecision, totalScore, finalConfidence, finScore100) var grade = AutoTradingManager.getInvestmentGrade(tempDecision, totalScore, finalConfidence, finScore100, tempDecision.isWatering)
var assistantReason = "" var assistantReason = ""
@@ -196,7 +196,8 @@ object AutoTradingManager {
ts: TradingDecision, ts: TradingDecision,
totalScore: Double, totalScore: Double,
confidence: Double, confidence: Double,
finScore100: Double finScore100: Double,
isWatering : Boolean = false
): InvestmentGrade { ): InvestmentGrade {
val minScore = KisSession.config.getValues(ConfigIndex.MIN_PURCHASE_SCORE_INDEX) val minScore = KisSession.config.getValues(ConfigIndex.MIN_PURCHASE_SCORE_INDEX)
val minConfidence = minScore val minConfidence = minScore
@@ -266,7 +267,7 @@ object AutoTradingManager {
rawGrade rawGrade
} }
// 4. 사용자가 UI에서 스위치로 꺼둔(비활성화) 등급인지 체크하여 매수 차단 // 4. 사용자가 UI에서 스위치로 꺼둔(비활성화) 등급인지 체크
val isEnabled = when (finalGrade) { val isEnabled = when (finalGrade) {
InvestmentGrade.LEVEL_5_STRONG_RECOMMEND -> KisSession.tradeConfig.useGrade5 InvestmentGrade.LEVEL_5_STRONG_RECOMMEND -> KisSession.tradeConfig.useGrade5
InvestmentGrade.LEVEL_4_BALANCED_RECOMMEND -> KisSession.tradeConfig.useGrade4 InvestmentGrade.LEVEL_4_BALANCED_RECOMMEND -> KisSession.tradeConfig.useGrade4
@@ -276,7 +277,8 @@ object AutoTradingManager {
else -> false else -> false
} }
return if (isEnabled) finalGrade else InvestmentGrade.LEVEL_0_SPECULATIVE // 🌟 [핵심] 물타기 대상(isWatering == true)이면 스위치(isEnabled)가 꺼져 있어도 강제 승인(프리패스)
return if (isEnabled || isWatering) finalGrade else InvestmentGrade.LEVEL_0_SPECULATIVE
} }
fun excuteTrade( fun excuteTrade(
+182 -194
View File
@@ -280,11 +280,11 @@ fun TradingDecisionLog() {
} }
} }
} }
Column(modifier = Modifier.weight(1f).padding(6.dp).fillMaxHeight().background(Color.White)) { Column(modifier = Modifier.weight(1f).padding(3.dp).fillMaxHeight().background(Color.White)) {
LazyVerticalGrid( LazyVerticalGrid(
columns = GridCells.Fixed(6), // 💡 2와 3의 최소공배수인 6열로 통합! columns = GridCells.Fixed(6), // 💡 2와 3의 최소공배수인 6열로 통합!
horizontalArrangement = Arrangement.spacedBy(6.dp), horizontalArrangement = Arrangement.spacedBy(3.dp),
verticalArrangement = Arrangement.spacedBy(6.dp), verticalArrangement = Arrangement.spacedBy(3.dp),
modifier = Modifier.fillMaxSize().background(Color.White) // fillMaxWidth() 대신 전체 채우기 modifier = Modifier.fillMaxSize().background(Color.White) // fillMaxWidth() 대신 전체 채우기
) { ) {
// ========================================== // ==========================================
@@ -295,7 +295,7 @@ fun TradingDecisionLog() {
Text( Text(
"💰 거래 기본 설정", "💰 거래 기본 설정",
style = MaterialTheme.typography.subtitle2, style = MaterialTheme.typography.subtitle2,
modifier = Modifier.padding(top = 10.dp, bottom = 8.dp) modifier = Modifier.padding(top = 3.dp, bottom = 3.dp)
) )
} }
@@ -305,51 +305,42 @@ fun TradingDecisionLog() {
ConfigIndex.SELL_PROFIT, ConfigIndex.MAX_COUNT_INDEX, ConfigIndex.MAX_HOLDING_COUNT, ConfigIndex.SELL_PROFIT, ConfigIndex.MAX_COUNT_INDEX, ConfigIndex.MAX_HOLDING_COUNT,
) )
// 🌟 [수정] GridItemSpan(3) -> GridItemSpan(2)로 변경하여 3열 배치
items(defaults.size, span = { GridItemSpan(2) }) { index -> items(defaults.size, span = { GridItemSpan(2) }) { index ->
val configKey = defaults.get(index) val configKey = defaults.get(index)
var localText by remember(configKey) { mutableStateOf(KisSession.config.getValues(configKey)?.toString() ?: "") } var localText by remember(configKey) { mutableStateOf(KisSession.config.getValues(configKey)?.toString() ?: "") }
val saveAction = { // 🌟 1. 초기값 계산
var newValue = localText.toDoubleOrNull() ?: 0.0 val displayText = if (configKey.label.contains("PROFIT")) {
var oldValue = KisSession.config.getValues(configKey)
if (configKey.label.contains("PROFIT")) {
newValue = newValue / KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)
}
KisSession.config.setValues(configKey, newValue)
}
var text = if (configKey.label.contains("PROFIT")) {
"${(localText.toDoubleOrNull() ?: 1.0) * KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)}" "${(localText.toDoubleOrNull() ?: 1.0) * KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)}"
} else { } else {
localText localText
} }
OutlinedTextField( // 🌟 2. 직접 구현했던 OutlinedTextField를 SettingInputField로 깔끔하게 교체
value = text, SettingInputField(
onValueChange = { localText = it }, modifier = Modifier.fillMaxWidth().padding(horizontal = 2.dp), // 살짝 여백 추가
label = { Text(configKey.label) }, label = configKey.label,
modifier = Modifier initialValue = displayText,
.fillMaxWidth() onSave = { newValueStr ->
.onFocusChanged { focusState -> if (!focusState.isFocused) saveAction() }, localText = newValueStr
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done, keyboardType = KeyboardType.Decimal), var newValue = newValueStr.toDoubleOrNull() ?: 0.0
keyboardActions = KeyboardActions(onDone = { saveAction() }), if (configKey.label.contains("PROFIT")) {
singleLine = true newValue = newValue / KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)
}
KisSession.config.setValues(configKey, newValue)
}
) )
} }
// --- 🛡️ 수익 및 리스크 관리 섹션 --- // --- 🛡️ 수익 및 리스크 관리 섹션 ---
item(span = { GridItemSpan(maxLineSpan) }) { item(span = { GridItemSpan(maxLineSpan) }) {
Column { Column {
Spacer(modifier = Modifier.height(20.dp))
Divider(color = Color.LightGray, thickness = 1.dp)
Spacer(modifier = Modifier.height(10.dp))
Text( Text(
text = "🛡️ 수익 및 리스크 관리", text = "🛡️ 수익 및 리스크 관리",
style = MaterialTheme.typography.subtitle2, style = MaterialTheme.typography.subtitle2,
fontWeight = FontWeight.ExtraBold, fontWeight = FontWeight.ExtraBold,
color = Color.DarkGray, color = Color.DarkGray,
modifier = Modifier.padding(bottom = 12.dp) modifier = Modifier.padding(bottom = 3.dp)
) )
} }
} }
@@ -375,6 +366,17 @@ fun TradingDecisionLog() {
) )
} }
// 🌟 금액 입력칸도 한 줄에 3개 나란히 배치되도록 변경
item(span = { GridItemSpan(2) }) {
SettingInputField(
label = "최대 허용 손실 금액 (원)",
initialValue = KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY).toLong().toString(),
placeholder = "50000",
onSave = { KisSession.config.setValues(ConfigIndex.LOSS_MAX_MONEY, it.toDoubleOrNull() ?: 0.0) },
helperText = "1종목당 최대 손실액" // 텍스트 간소화
)
}
item(span = { GridItemSpan(2) }) { item(span = { GridItemSpan(2) }) {
SettingInputField( SettingInputField(
label = "최소 손절 라인 (%)", label = "최소 손절 라인 (%)",
@@ -395,16 +397,7 @@ fun TradingDecisionLog() {
) )
} }
// 🌟 금액 입력칸도 한 줄에 3개 나란히 배치되도록 변경
item(span = { GridItemSpan(2) }) {
SettingInputField(
label = "최대 허용 손실 금액 (원)",
initialValue = KisSession.config.getValues(ConfigIndex.LOSS_MAX_MONEY).toLong().toString(),
placeholder = "50000",
onSave = { KisSession.config.setValues(ConfigIndex.LOSS_MAX_MONEY, it.toDoubleOrNull() ?: 0.0) },
helperText = "1종목당 최대 손실액" // 텍스트 간소화
)
}
// ========================================== // ==========================================
@@ -414,7 +407,7 @@ fun TradingDecisionLog() {
Text( Text(
"💰 매수 정책 및 기대 수익률", "💰 매수 정책 및 기대 수익률",
style = MaterialTheme.typography.subtitle2, style = MaterialTheme.typography.subtitle2,
modifier = Modifier.padding(top = 10.dp, bottom = 8.dp) modifier = Modifier.padding(top = 3.dp, bottom = 3.dp)
) )
} }
@@ -434,8 +427,8 @@ fun TradingDecisionLog() {
item(span = { GridItemSpan(maxLineSpan) }) { item(span = { GridItemSpan(maxLineSpan) }) {
// 🌟 [핵심] 제목 텍스트 대신 사용 여부 스위치 부착 // 🌟 [핵심] 제목 텍스트 대신 사용 여부 스위치 부착
SettingSwitchField( SettingSwitchField(
modifier = Modifier.fillMaxWidth().padding(top = 10.dp, bottom = 8.dp), modifier = Modifier.fillMaxWidth().padding(vertical = 0.dp),
label = "$common (LEVEL_$levelNum) 자동매수", label = "$common (LEVEL_$levelNum) 자동매수(활성화시 매수 진행)",
initialChecked = when (levelNum) { initialChecked = when (levelNum) {
5 -> tradeConfig.useGrade5 5 -> tradeConfig.useGrade5
4 -> tradeConfig.useGrade4 4 -> tradeConfig.useGrade4
@@ -452,8 +445,7 @@ fun TradingDecisionLog() {
1 -> tradeConfig.useGrade1 = isChecked 1 -> tradeConfig.useGrade1 = isChecked
} }
KisSession.saveTradeConfig() // 즉시 저장 KisSession.saveTradeConfig() // 즉시 저장
}, }
helperText = "체크 해제 시 해당 등급 판정이 나와도 매수하지 않습니다."
) )
} }
@@ -461,16 +453,9 @@ fun TradingDecisionLog() {
items(items.size, span = { GridItemSpan(2) }) { index -> items(items.size, span = { GridItemSpan(2) }) { index ->
val configKey = items.get(index) val configKey = items.get(index)
var localText by remember(configKey) { mutableStateOf(KisSession.config.getValues(configKey)?.toString() ?: "") } var localText by remember(configKey) { mutableStateOf(KisSession.config.getValues(configKey)?.toString() ?: "") }
var labelText by remember(configKey) { mutableStateOf("") }
val saveAction = { // 🌟 1. 라벨 텍스트 계산
var oldValue = KisSession.config.getValues(configKey) val labelText = if (configKey.name.contains("PROFIT")) {
var newValue = localText.toDoubleOrNull() ?: 0.0
KisSession.config.setValues(configKey, newValue)
}
// labelText 업데이트 로직
labelText = if (configKey.name.contains("PROFIT")) {
getRemaining(configKey.label, common) + ": 기준율(${KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)}) * 비율(${KisSession.config.getValues(configKey)}) + 세금(${KisSession.config.getValues(ConfigIndex.TAX_INDEX)}) = ${(localText.toDoubleOrNull() ?: 0.0) * KisSession.config.getValues(ConfigIndex.PROFIT_INDEX) + KisSession.config.getValues(ConfigIndex.TAX_INDEX)}" getRemaining(configKey.label, common) + ": 기준율(${KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)}) * 비율(${KisSession.config.getValues(configKey)}) + 세금(${KisSession.config.getValues(ConfigIndex.TAX_INDEX)}) = ${(localText.toDoubleOrNull() ?: 0.0) * KisSession.config.getValues(ConfigIndex.PROFIT_INDEX) + KisSession.config.getValues(ConfigIndex.TAX_INDEX)}"
} else if (configKey.name.contains("ALLOCATIONRATE")) { } else if (configKey.name.contains("ALLOCATIONRATE")) {
getRemaining(configKey.label, common) + ": 최대 ${KisSession.config.getValues(ConfigIndex.MAX_BUDGET_INDEX) * (localText.toDoubleOrNull() ?: 0.0)}원" getRemaining(configKey.label, common) + ": 최대 ${KisSession.config.getValues(ConfigIndex.MAX_BUDGET_INDEX) * (localText.toDoubleOrNull() ?: 0.0)}원"
@@ -478,16 +463,16 @@ fun TradingDecisionLog() {
getRemaining(configKey.label, common) + ": -${localText} 호가 매수" getRemaining(configKey.label, common) + ": -${localText} 호가 매수"
} }
OutlinedTextField( // 🌟 2. 길고 복잡했던 코드를 SettingInputField 하나로 압축
value = localText, SettingInputField(
onValueChange = { localText = it }, modifier = Modifier.fillMaxWidth().padding(horizontal = 2.dp, vertical = 0.dp),
label = { Text(labelText) }, label = labelText,
modifier = Modifier initialValue = localText,
.fillMaxWidth() onSave = { newValueStr ->
.onFocusChanged { focusState -> if (!focusState.isFocused) saveAction() }, localText = newValueStr // 라벨 문구 실시간 업데이트용
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done, keyboardType = KeyboardType.Decimal), val newValue = newValueStr.toDoubleOrNull() ?: 0.0
keyboardActions = KeyboardActions(onDone = { saveAction() }), KisSession.config.setValues(configKey, newValue)
singleLine = true }
) )
} }
} }
@@ -495,60 +480,54 @@ fun TradingDecisionLog() {
} }
Column(modifier = Modifier.weight(1f).padding(8.dp).fillMaxHeight().background(Color.White).verticalScroll(rememberScrollState())) { Column(modifier = Modifier.weight(1f).padding(8.dp).fillMaxHeight().background(Color.White).verticalScroll(rememberScrollState())) {
Text("🕒 시간 및 매매 스케줄", style = MaterialTheme.typography.h6, modifier = Modifier.padding(8.dp)) Text("🕒 시간 및 매매 스케줄", style = MaterialTheme.typography.subtitle2, modifier = Modifier.padding(3.dp))
Card(elevation = 2.dp, modifier = Modifier.fillMaxWidth().padding(4.dp)) {
Column(modifier = Modifier.padding(12.dp)) {
Text("자동 매매 시간 설정", fontWeight = FontWeight.Bold, color = Color.Blue)
Spacer(Modifier.height(8.dp))
// 🌟 [수정] 자동 시작/종료를 한 줄로 Column(modifier = Modifier.padding(12.dp)) {
Row(horizontalArrangement = Arrangement.SpaceEvenly) { Row(horizontalArrangement = Arrangement.SpaceEvenly) {
TimeInputRow( TimeInputRow(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
label = "자동 시작", label = "자동 시작",
initialTime = tradeConfig.auto_start_time initialTime = tradeConfig.auto_start_time
) { ) {
tradeConfig.auto_start_time = it tradeConfig.auto_start_time = it
KisSession.saveTradeConfig() KisSession.saveTradeConfig()
}
TimeInputRow(
modifier = Modifier.weight(1f),
label = "자동 종료",
initialTime = tradeConfig.auto_end_time
) {
tradeConfig.auto_end_time = it
KisSession.saveTradeConfig()
}
} }
TimeInputRow(
modifier = Modifier.weight(1f),
label = "자동 종료",
initialTime = tradeConfig.auto_end_time
) {
tradeConfig.auto_end_time = it
KisSession.saveTradeConfig()
}
}
Divider(Modifier.padding(vertical = 8.dp)) Divider(Modifier.padding(vertical = 8.dp))
// 🌟 [수정] 매수 시작/종료를 한 줄로 // 🌟 [수정] 매수 시작/종료를 한 줄로
Row(horizontalArrangement = Arrangement.SpaceEvenly) { Row(horizontalArrangement = Arrangement.SpaceEvenly) {
TimeInputRow( TimeInputRow(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
label = "매수 시작", label = "매수 시작",
initialTime = tradeConfig.start_buy_time initialTime = tradeConfig.start_buy_time
) { ) {
tradeConfig.start_buy_time = it tradeConfig.start_buy_time = it
KisSession.saveTradeConfig() KisSession.saveTradeConfig()
} }
TimeInputRow( TimeInputRow(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
label = "매수 종료", label = "매수 종료",
initialTime = tradeConfig.end_buy_time initialTime = tradeConfig.end_buy_time
) { ) {
tradeConfig.end_buy_time = it tradeConfig.end_buy_time = it
KisSession.saveTradeConfig() KisSession.saveTradeConfig()
}
} }
} }
} }
Spacer(Modifier.height(16.dp))
Text("⚙️️ 기타 고급 설정", style = MaterialTheme.typography.h6, modifier = Modifier.padding(8.dp)) Text("⚙️️ 기타 고급 설정", style = MaterialTheme.typography.subtitle2, modifier = Modifier.padding(3.dp))
Row(horizontalArrangement = Arrangement.SpaceEvenly) { Row(horizontalArrangement = Arrangement.SpaceEvenly) {
SettingInputField( SettingInputField(
modifier = Modifier.weight(1.0f, true).padding(end = 4.dp), modifier = Modifier.weight(1.0f, true).padding(end = 4.dp),
@@ -616,7 +595,7 @@ fun TradingDecisionLog() {
Row(horizontalArrangement = Arrangement.SpaceEvenly) { Row(horizontalArrangement = Arrangement.SpaceEvenly) {
SettingSwitchField ( SettingSwitchField (
modifier = Modifier.weight(1.0f, true), modifier = Modifier.weight(1.0f, true),
label = "장 전 대체마켓 매도", label = "장 전 대체마켓 매도",
initialChecked = tradeConfig.before_nxt, initialChecked = tradeConfig.before_nxt,
onCheckedChange = { onCheckedChange = {
@@ -655,7 +634,7 @@ fun TradingDecisionLog() {
tradeConfig.tlg_id = it tradeConfig.tlg_id = it
KisSession.saveTradeConfig() KisSession.saveTradeConfig()
}, },
helperText = "본인의 텔레그램 아뒤" helperText = "본인의 텔레그램 아뒤"
) )
SettingSwitchField( SettingSwitchField(
label = "물타기", label = "물타기",
@@ -684,11 +663,9 @@ fun TradingDecisionLog() {
}, },
helperText = "이것보다 작아야 삼" helperText = "이것보다 작아야 삼"
) )
}
Row(horizontalArrangement = Arrangement.SpaceEvenly) {
SettingInputField( SettingInputField(
modifier = Modifier.weight(1.0f, true), modifier = Modifier.weight(1.0f, true),
label = "물타기 기준 최소 보유 수량", label = "물타기 최소 보유",
initialValue = (tradeConfig.lowerAverageTargetCount).toString(), initialValue = (tradeConfig.lowerAverageTargetCount).toString(),
onSave = { onSave = {
tradeConfig.lowerAverageTargetCount = it.toInt() tradeConfig.lowerAverageTargetCount = it.toInt()
@@ -996,30 +973,31 @@ fun CsvDropZone(
// } // }
// } // }
//} //}
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun SettingInputField( fun SettingInputField(
modifier: Modifier? = null, modifier: Modifier? = null,
label: String, label: String,
initialValue: String, // 💡 value -> initialValue 로 변경 initialValue: String,
placeholder: String = "", placeholder: String = "",
helperText: String = "", helperText: String = "",
onSave: (String) -> Unit // 💡 타자 칠 때마다가 아니라, 완료 시 저장하도록 콜백 변경 onSave: (String) -> Unit
) { ) {
// 💡 화면에 즉시 글자를 그려주기 위한 로컬 상태 (핵심 해결책) // 💡 [수정 2] remember(initialValue)로 키를 지정하여 외부 설정 변경 시 UI 즉각 갱신
var localText by remember { mutableStateOf(initialValue) } var localText by remember(initialValue) { mutableStateOf(initialValue) }
Column(modifier = modifier ?: Modifier.fillMaxWidth()) { Column(
modifier = (modifier ?: Modifier.fillMaxWidth()).height(80.dp), // 80.dp 통일
verticalArrangement = Arrangement.Top
) {
OutlinedTextField( OutlinedTextField(
value = localText, value = localText,
onValueChange = { localText = it }, // 타자 칠 때 화면 즉시 반영 onValueChange = { localText = it },
label = { Text(label, fontWeight = FontWeight.Bold) }, label = { Text(label, fontWeight = FontWeight.Bold) },
placeholder = { Text(placeholder) }, placeholder = { Text(placeholder) },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.onFocusChanged { focusState -> .onFocusChanged { focusState ->
// 💡 포커스를 잃었을 때 (다른 칸을 클릭했을 때) 저장
if (!focusState.isFocused) { if (!focusState.isFocused) {
onSave(localText) onSave(localText)
} }
@@ -1030,22 +1008,17 @@ fun SettingInputField(
keyboardType = KeyboardType.Decimal keyboardType = KeyboardType.Decimal
), ),
keyboardActions = KeyboardActions( keyboardActions = KeyboardActions(
// 💡 모바일 키보드나 키보드에서 엔터(Done) 쳤을 때 저장 onDone = { onSave(localText) }
onDone = {
onSave(localText)
}
) )
) )
if (helperText.isNotEmpty()) { Spacer(modifier = Modifier.height(4.dp))
Spacer(modifier = Modifier.height(4.dp)) Text(
Text( text = if (helperText.isNotEmpty()) helperText else " ",
text = helperText, color = Color.Gray,
color = Color.Gray, fontSize = 11.sp,
fontSize = 11.sp, modifier = Modifier.padding(start = 4.dp)
modifier = Modifier.padding(start = 4.dp) )
)
}
} }
} }
@@ -1060,40 +1033,102 @@ fun SettingSwitchField(
helperText: String = "", helperText: String = "",
onCheckedChange: (Boolean) -> Unit onCheckedChange: (Boolean) -> Unit
) { ) {
// 💡 스위치 애니메이션을 즉각 보여주기 위한 로컬 상태 // 💡 [수정 2] remember(initialChecked)로 키를 지정하여 외부 변경 시 동기화
var localChecked by remember { mutableStateOf(initialChecked) } var localChecked by remember(initialChecked) { mutableStateOf(initialChecked) }
Column( Column(
modifier = modifier modifier = modifier.height(80.dp), // 80.dp 통일
verticalArrangement = Arrangement.Top
) { ) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp)
.height(52.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Text( Text(
text = label, text = label,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
// 💡 [수정 3] 글씨가 너무 길면 한 줄로 유지하고 끝을 말줄임표(...)로 처리
maxLines = 1,
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
Switch( Switch(
checked = localChecked, checked = localChecked,
onCheckedChange = { isChecked -> onCheckedChange = { isChecked ->
localChecked = isChecked // 화면 스위치 즉시 변경 localChecked = isChecked
onCheckedChange(isChecked) // 실제 DB/설정 저장 트리거 onCheckedChange(isChecked)
} }
) )
} }
if (helperText.isNotEmpty()) { Spacer(modifier = Modifier.height(4.dp))
Text( Text(
text = helperText, text = if (helperText.isNotEmpty()) helperText else " ",
color = Color.Gray, color = Color.Gray,
fontSize = 11.sp, fontSize = 11.sp,
modifier = Modifier.padding(start = 2.dp) modifier = Modifier.padding(start = 2.dp)
) )
}
} }
} }
/**
* 시:분 입력을 위한 전용 컴포저블
*/
@Composable
fun TimeInputRow(
modifier: Modifier = Modifier,
label: String,
initialTime: String,
helperText: String = "HH:mm 형식",
onTimeChange: (String) -> Unit
) {
var localText by remember(initialTime) { mutableStateOf(initialTime) }
Column(
// 💡 [수정 1] 높이를 90.dp -> 80.dp로 수정하여 다른 필드들과 완벽한 줄맞춤
modifier = modifier.height(80.dp).padding(horizontal = 2.dp),
verticalArrangement = Arrangement.Top
) {
OutlinedTextField(
value = localText,
onValueChange = {
if (it.length <= 5) {
localText = it
}
},
label = { Text(label, fontWeight = FontWeight.Bold) },
placeholder = { Text("00:00") },
modifier = Modifier
.fillMaxWidth()
.onFocusChanged { focusState ->
if (!focusState.isFocused) {
onTimeChange(localText)
}
},
singleLine = true,
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done,
keyboardType = KeyboardType.Number
),
keyboardActions = KeyboardActions(
onDone = { onTimeChange(localText) }
)
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = if (helperText.isNotEmpty()) helperText else " ",
color = Color.Gray,
fontSize = 11.sp,
modifier = Modifier.padding(start = 4.dp)
)
}
}
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class) @OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class)
@Composable @Composable
fun FilterChipWithRightClick( fun FilterChipWithRightClick(
@@ -1166,50 +1201,3 @@ fun FilterChipWithRightClick(
} }
} }
} }
/**
* 시:분 입력을 위한 전용 컴포저블
*/
@Composable
fun TimeInputRow(
modifier: Modifier = Modifier, // 🌟 modifier 파라미터 추가
label: String,
initialTime: String,
onTimeChange: (String) -> Unit
) {
var localText by remember(initialTime) { mutableStateOf(initialTime) }
Row(
modifier = modifier.padding(vertical = 4.dp, horizontal = 4.dp), // 🌟 수정
verticalAlignment = Alignment.CenterVertically
) {
// 🌟 글자 크기와 너비를 약간 줄여서 한 줄에 두 개가 들어가도 예쁘게 설정
Text(label, modifier = Modifier.width(65.dp), fontSize = 12.sp, fontWeight = FontWeight.Medium)
OutlinedTextField(
value = localText,
onValueChange = {
if (it.length <= 5) {
localText = it
}
},
placeholder = { Text("00:00") },
modifier = Modifier
.weight(1f)
.onFocusChanged { focusState ->
if (!focusState.isFocused) {
onTimeChange(localText)
}
},
singleLine = true,
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done,
keyboardType = KeyboardType.Number
),
keyboardActions = KeyboardActions(
onDone = { onTimeChange(localText) }
),
textStyle = androidx.compose.ui.text.TextStyle(fontSize = 13.sp)
)
}
}