...
This commit is contained in:
@@ -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(
|
||||||
|
|||||||
@@ -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,14 +480,10 @@ 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)) {
|
Column(modifier = Modifier.padding(12.dp)) {
|
||||||
Text("자동 매매 시간 설정", fontWeight = FontWeight.Bold, color = Color.Blue)
|
|
||||||
Spacer(Modifier.height(8.dp))
|
|
||||||
|
|
||||||
// 🌟 [수정] 자동 시작/종료를 한 줄로
|
|
||||||
Row(horizontalArrangement = Arrangement.SpaceEvenly) {
|
Row(horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||||
TimeInputRow(
|
TimeInputRow(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
@@ -544,11 +525,9 @@ fun TradingDecisionLog() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
||||||
@@ -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,23 +1008,18 @@ 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 = helperText,
|
text = if (helperText.isNotEmpty()) helperText else " ",
|
||||||
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)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user