...
This commit is contained in:
@@ -528,7 +528,7 @@ object RagService {
|
||||
var finalDecision = "HOLD"
|
||||
var finalReason = ""
|
||||
|
||||
var grade = AutoTradingManager.getInvestmentGrade(tempDecision, totalScore, finalConfidence, finScore100)
|
||||
var grade = AutoTradingManager.getInvestmentGrade(tempDecision, totalScore, finalConfidence, finScore100, tempDecision.isWatering)
|
||||
|
||||
var assistantReason = ""
|
||||
|
||||
|
||||
@@ -196,7 +196,8 @@ object AutoTradingManager {
|
||||
ts: TradingDecision,
|
||||
totalScore: Double,
|
||||
confidence: Double,
|
||||
finScore100: Double
|
||||
finScore100: Double,
|
||||
isWatering : Boolean = false
|
||||
): InvestmentGrade {
|
||||
val minScore = KisSession.config.getValues(ConfigIndex.MIN_PURCHASE_SCORE_INDEX)
|
||||
val minConfidence = minScore
|
||||
@@ -266,7 +267,7 @@ object AutoTradingManager {
|
||||
rawGrade
|
||||
}
|
||||
|
||||
// 4. 사용자가 UI에서 스위치로 꺼둔(비활성화) 등급인지 체크하여 매수 차단
|
||||
// 4. 사용자가 UI에서 스위치로 꺼둔(비활성화) 등급인지 체크
|
||||
val isEnabled = when (finalGrade) {
|
||||
InvestmentGrade.LEVEL_5_STRONG_RECOMMEND -> KisSession.tradeConfig.useGrade5
|
||||
InvestmentGrade.LEVEL_4_BALANCED_RECOMMEND -> KisSession.tradeConfig.useGrade4
|
||||
@@ -276,7 +277,8 @@ object AutoTradingManager {
|
||||
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(
|
||||
|
||||
@@ -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(
|
||||
columns = GridCells.Fixed(6), // 💡 2와 3의 최소공배수인 6열로 통합!
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(3.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(3.dp),
|
||||
modifier = Modifier.fillMaxSize().background(Color.White) // fillMaxWidth() 대신 전체 채우기
|
||||
) {
|
||||
// ==========================================
|
||||
@@ -295,7 +295,7 @@ fun TradingDecisionLog() {
|
||||
Text(
|
||||
"💰 거래 기본 설정",
|
||||
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,
|
||||
)
|
||||
|
||||
// 🌟 [수정] GridItemSpan(3) -> GridItemSpan(2)로 변경하여 3열 배치
|
||||
items(defaults.size, span = { GridItemSpan(2) }) { index ->
|
||||
val configKey = defaults.get(index)
|
||||
var localText by remember(configKey) { mutableStateOf(KisSession.config.getValues(configKey)?.toString() ?: "") }
|
||||
|
||||
val saveAction = {
|
||||
var newValue = localText.toDoubleOrNull() ?: 0.0
|
||||
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")) {
|
||||
// 🌟 1. 초기값 계산
|
||||
val displayText = if (configKey.label.contains("PROFIT")) {
|
||||
"${(localText.toDoubleOrNull() ?: 1.0) * KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)}"
|
||||
} else {
|
||||
localText
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
value = text,
|
||||
onValueChange = { localText = it },
|
||||
label = { Text(configKey.label) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged { focusState -> if (!focusState.isFocused) saveAction() },
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done, keyboardType = KeyboardType.Decimal),
|
||||
keyboardActions = KeyboardActions(onDone = { saveAction() }),
|
||||
singleLine = true
|
||||
// 🌟 2. 직접 구현했던 OutlinedTextField를 SettingInputField로 깔끔하게 교체
|
||||
SettingInputField(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 2.dp), // 살짝 여백 추가
|
||||
label = configKey.label,
|
||||
initialValue = displayText,
|
||||
onSave = { newValueStr ->
|
||||
localText = newValueStr
|
||||
var newValue = newValueStr.toDoubleOrNull() ?: 0.0
|
||||
if (configKey.label.contains("PROFIT")) {
|
||||
newValue = newValue / KisSession.config.getValues(ConfigIndex.PROFIT_INDEX)
|
||||
}
|
||||
KisSession.config.setValues(configKey, newValue)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// --- 🛡️ 수익 및 리스크 관리 섹션 ---
|
||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||
Column {
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
Divider(color = Color.LightGray, thickness = 1.dp)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
Text(
|
||||
text = "🛡️ 수익 및 리스크 관리",
|
||||
style = MaterialTheme.typography.subtitle2,
|
||||
fontWeight = FontWeight.ExtraBold,
|
||||
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) }) {
|
||||
SettingInputField(
|
||||
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(
|
||||
"💰 매수 정책 및 기대 수익률",
|
||||
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) }) {
|
||||
// 🌟 [핵심] 제목 텍스트 대신 사용 여부 스위치 부착
|
||||
SettingSwitchField(
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 10.dp, bottom = 8.dp),
|
||||
label = "$common (LEVEL_$levelNum) 자동매수",
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 0.dp),
|
||||
label = "$common (LEVEL_$levelNum) 자동매수(활성화시 매수 진행)",
|
||||
initialChecked = when (levelNum) {
|
||||
5 -> tradeConfig.useGrade5
|
||||
4 -> tradeConfig.useGrade4
|
||||
@@ -452,8 +445,7 @@ fun TradingDecisionLog() {
|
||||
1 -> tradeConfig.useGrade1 = isChecked
|
||||
}
|
||||
KisSession.saveTradeConfig() // 즉시 저장
|
||||
},
|
||||
helperText = "체크 해제 시 해당 등급 판정이 나와도 매수하지 않습니다."
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -461,16 +453,9 @@ fun TradingDecisionLog() {
|
||||
items(items.size, span = { GridItemSpan(2) }) { index ->
|
||||
val configKey = items.get(index)
|
||||
var localText by remember(configKey) { mutableStateOf(KisSession.config.getValues(configKey)?.toString() ?: "") }
|
||||
var labelText by remember(configKey) { mutableStateOf("") }
|
||||
|
||||
val saveAction = {
|
||||
var oldValue = KisSession.config.getValues(configKey)
|
||||
var newValue = localText.toDoubleOrNull() ?: 0.0
|
||||
KisSession.config.setValues(configKey, newValue)
|
||||
}
|
||||
|
||||
// labelText 업데이트 로직
|
||||
labelText = if (configKey.name.contains("PROFIT")) {
|
||||
// 🌟 1. 라벨 텍스트 계산
|
||||
val 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)}"
|
||||
} else if (configKey.name.contains("ALLOCATIONRATE")) {
|
||||
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} 호가 매수"
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
value = localText,
|
||||
onValueChange = { localText = it },
|
||||
label = { Text(labelText) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged { focusState -> if (!focusState.isFocused) saveAction() },
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done, keyboardType = KeyboardType.Decimal),
|
||||
keyboardActions = KeyboardActions(onDone = { saveAction() }),
|
||||
singleLine = true
|
||||
// 🌟 2. 길고 복잡했던 코드를 SettingInputField 하나로 압축
|
||||
SettingInputField(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 2.dp, vertical = 0.dp),
|
||||
label = labelText,
|
||||
initialValue = localText,
|
||||
onSave = { newValueStr ->
|
||||
localText = newValueStr // 라벨 문구 실시간 업데이트용
|
||||
val newValue = newValueStr.toDoubleOrNull() ?: 0.0
|
||||
KisSession.config.setValues(configKey, newValue)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -495,60 +480,54 @@ fun TradingDecisionLog() {
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
// 🌟 [수정] 자동 시작/종료를 한 줄로
|
||||
Row(horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||
TimeInputRow(
|
||||
modifier = Modifier.weight(1f),
|
||||
label = "자동 시작",
|
||||
initialTime = tradeConfig.auto_start_time
|
||||
) {
|
||||
tradeConfig.auto_start_time = it
|
||||
KisSession.saveTradeConfig()
|
||||
}
|
||||
TimeInputRow(
|
||||
modifier = Modifier.weight(1f),
|
||||
label = "자동 종료",
|
||||
initialTime = tradeConfig.auto_end_time
|
||||
) {
|
||||
tradeConfig.auto_end_time = it
|
||||
KisSession.saveTradeConfig()
|
||||
}
|
||||
Column(modifier = Modifier.padding(12.dp)) {
|
||||
Row(horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||
TimeInputRow(
|
||||
modifier = Modifier.weight(1f),
|
||||
label = "자동 시작",
|
||||
initialTime = tradeConfig.auto_start_time
|
||||
) {
|
||||
tradeConfig.auto_start_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) {
|
||||
TimeInputRow(
|
||||
modifier = Modifier.weight(1f),
|
||||
label = "매수 시작",
|
||||
initialTime = tradeConfig.start_buy_time
|
||||
) {
|
||||
tradeConfig.start_buy_time = it
|
||||
KisSession.saveTradeConfig()
|
||||
}
|
||||
TimeInputRow(
|
||||
modifier = Modifier.weight(1f),
|
||||
label = "매수 종료",
|
||||
initialTime = tradeConfig.end_buy_time
|
||||
) {
|
||||
tradeConfig.end_buy_time = it
|
||||
KisSession.saveTradeConfig()
|
||||
}
|
||||
// 🌟 [수정] 매수 시작/종료를 한 줄로
|
||||
Row(horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||
TimeInputRow(
|
||||
modifier = Modifier.weight(1f),
|
||||
label = "매수 시작",
|
||||
initialTime = tradeConfig.start_buy_time
|
||||
) {
|
||||
tradeConfig.start_buy_time = it
|
||||
KisSession.saveTradeConfig()
|
||||
}
|
||||
TimeInputRow(
|
||||
modifier = Modifier.weight(1f),
|
||||
label = "매수 종료",
|
||||
initialTime = tradeConfig.end_buy_time
|
||||
) {
|
||||
tradeConfig.end_buy_time = it
|
||||
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) {
|
||||
SettingInputField(
|
||||
modifier = Modifier.weight(1.0f, true).padding(end = 4.dp),
|
||||
@@ -616,7 +595,7 @@ fun TradingDecisionLog() {
|
||||
|
||||
Row(horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||
SettingSwitchField (
|
||||
modifier = Modifier.weight(1.0f, true),
|
||||
modifier = Modifier.weight(1.0f, true),
|
||||
label = "장 전 대체마켓 매도",
|
||||
initialChecked = tradeConfig.before_nxt,
|
||||
onCheckedChange = {
|
||||
@@ -655,7 +634,7 @@ fun TradingDecisionLog() {
|
||||
tradeConfig.tlg_id = it
|
||||
KisSession.saveTradeConfig()
|
||||
},
|
||||
helperText = "본인의 텔레그램 아뒤"
|
||||
helperText = "본인의 텔레그램 아뒤"
|
||||
)
|
||||
SettingSwitchField(
|
||||
label = "물타기",
|
||||
@@ -684,11 +663,9 @@ fun TradingDecisionLog() {
|
||||
},
|
||||
helperText = "이것보다 작아야 삼"
|
||||
)
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||
SettingInputField(
|
||||
modifier = Modifier.weight(1.0f, true),
|
||||
label = "물타기 기준 최소 보유 수량",
|
||||
label = "물타기 최소 보유",
|
||||
initialValue = (tradeConfig.lowerAverageTargetCount).toString(),
|
||||
onSave = {
|
||||
tradeConfig.lowerAverageTargetCount = it.toInt()
|
||||
@@ -996,30 +973,31 @@ fun CsvDropZone(
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun SettingInputField(
|
||||
modifier: Modifier? = null,
|
||||
label: String,
|
||||
initialValue: String, // 💡 value -> initialValue 로 변경
|
||||
initialValue: String,
|
||||
placeholder: String = "",
|
||||
helperText: String = "",
|
||||
onSave: (String) -> Unit // 💡 타자 칠 때마다가 아니라, 완료 시 저장하도록 콜백 변경
|
||||
onSave: (String) -> Unit
|
||||
) {
|
||||
// 💡 화면에 즉시 글자를 그려주기 위한 로컬 상태 (핵심 해결책)
|
||||
var localText by remember { mutableStateOf(initialValue) }
|
||||
// 💡 [수정 2] remember(initialValue)로 키를 지정하여 외부 설정 변경 시 UI 즉각 갱신
|
||||
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(
|
||||
value = localText,
|
||||
onValueChange = { localText = it }, // 타자 칠 때 화면 즉시 반영
|
||||
onValueChange = { localText = it },
|
||||
label = { Text(label, fontWeight = FontWeight.Bold) },
|
||||
placeholder = { Text(placeholder) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged { focusState ->
|
||||
// 💡 포커스를 잃었을 때 (다른 칸을 클릭했을 때) 저장
|
||||
if (!focusState.isFocused) {
|
||||
onSave(localText)
|
||||
}
|
||||
@@ -1030,22 +1008,17 @@ fun SettingInputField(
|
||||
keyboardType = KeyboardType.Decimal
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
// 💡 모바일 키보드나 키보드에서 엔터(Done) 쳤을 때 저장
|
||||
onDone = {
|
||||
onSave(localText)
|
||||
}
|
||||
onDone = { onSave(localText) }
|
||||
)
|
||||
)
|
||||
|
||||
if (helperText.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = helperText,
|
||||
color = Color.Gray,
|
||||
fontSize = 11.sp,
|
||||
modifier = Modifier.padding(start = 4.dp)
|
||||
)
|
||||
}
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1060,40 +1033,102 @@ fun SettingSwitchField(
|
||||
helperText: String = "",
|
||||
onCheckedChange: (Boolean) -> Unit
|
||||
) {
|
||||
// 💡 스위치 애니메이션을 즉각 보여주기 위한 로컬 상태
|
||||
var localChecked by remember { mutableStateOf(initialChecked) }
|
||||
// 💡 [수정 2] remember(initialChecked)로 키를 지정하여 외부 변경 시 동기화
|
||||
var localChecked by remember(initialChecked) { mutableStateOf(initialChecked) }
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
modifier = modifier.height(80.dp), // 80.dp 통일
|
||||
verticalArrangement = Arrangement.Top
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp)
|
||||
.height(52.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
fontWeight = FontWeight.Bold,
|
||||
// 💡 [수정 3] 글씨가 너무 길면 한 줄로 유지하고 끝을 말줄임표(...)로 처리
|
||||
maxLines = 1,
|
||||
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Switch(
|
||||
checked = localChecked,
|
||||
onCheckedChange = { isChecked ->
|
||||
localChecked = isChecked // 화면 스위치 즉시 변경
|
||||
onCheckedChange(isChecked) // 실제 DB/설정 저장 트리거
|
||||
localChecked = isChecked
|
||||
onCheckedChange(isChecked)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (helperText.isNotEmpty()) {
|
||||
Text(
|
||||
text = helperText,
|
||||
color = Color.Gray,
|
||||
fontSize = 11.sp,
|
||||
modifier = Modifier.padding(start = 2.dp)
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = if (helperText.isNotEmpty()) helperText else " ",
|
||||
color = Color.Gray,
|
||||
fontSize = 11.sp,
|
||||
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)
|
||||
@Composable
|
||||
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