feat: 10건 UI 피드백 + Auto fail 팝업 + 텍스트 정리
#1 Sensor Detached 상단 문구 삭제 (urgency 메시지 항상 유지) #2 Max Volume 5초 간격 갱신 (displayMaxVolumeMl) #3 Fill/Level → Current Measurement/Fill #4 detach 시 팝업→Placement 이동 (showRealignPrompt) #5 Auto 2ch 이상 미수신 연속 5회 → 팝업 (바로 이동 아님) #6 팝업 텍스트 "Sensor Position Issue" + 안내 문구 #7 Step2 진입 시 "Checking left/right balance..." #8 detach 후 waitingForStart=true → Start 버튼 표시 #9 초기/재시작 안내 시 화살표 제거 (directionIcon="") #10 Raise/Lower 텍스트 → "Raise the sensor up" 등 자연어 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+23
-10
@@ -81,6 +81,8 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
var lastVolumeMl by remember { mutableStateOf(0.0) }
|
||||
var wasDetached by remember { mutableStateOf(false) }
|
||||
var maxVolumeMl by remember { mutableStateOf(0.0) }
|
||||
var displayMaxVolumeMl by remember { mutableStateOf(0.0) } // 5초 간격 갱신용
|
||||
var lastMaxUpdateMs by remember { mutableStateOf(0L) }
|
||||
var isSpotInProgress by remember { mutableStateOf(false) }
|
||||
val spotVolumes = remember { mutableListOf<Double>() }
|
||||
val measureDoneSignal = remember { mutableStateOf(kotlinx.coroutines.CompletableDeferred<Unit>()) }
|
||||
@@ -330,9 +332,18 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
}
|
||||
|
||||
// Auto 연속 실패 → UI에서 처리
|
||||
val autoFailed = isAutoMeasuring && (finalRawVol == null || finalRawVol <= 0 || !allChannelsReceived)
|
||||
// 채널 2개 이상 미수신 또는 BV 계산 불가
|
||||
val centerWallsFinal = (0..3).count { allWalls[it] != null }
|
||||
val autoFailed = isAutoMeasuring && (centerWallsFinal < 2 || finalRawVol == null || finalRawVol <= 0)
|
||||
val autoSucceeded = isAutoMeasuring && finalRawVol != null && finalRawVol > 0 && allChannelsReceived
|
||||
|
||||
// displayMaxVolumeMl 5초 간격 갱신
|
||||
val nowMs = System.currentTimeMillis()
|
||||
if (maxVolumeMl != displayMaxVolumeMl && nowMs - lastMaxUpdateMs >= 5000) {
|
||||
displayMaxVolumeMl = maxVolumeMl
|
||||
lastMaxUpdateMs = nowMs
|
||||
}
|
||||
|
||||
if (lastVolumeMl > 0 && !isSpotInProgress) {
|
||||
val maxVol = appState.piezoSettings.maxVolume.toDouble()
|
||||
val pct = if (maxVol > 0) (lastVolumeMl / maxVol).coerceAtMost(1.0) else 0.0
|
||||
@@ -343,9 +354,10 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
if (autoSucceeded) autoFailCount = 0
|
||||
if (autoFailed) {
|
||||
autoFailCount++
|
||||
if (autoFailCount >= 3) {
|
||||
if (autoFailCount >= 5) {
|
||||
isAutoMeasuring = false
|
||||
autoVolumeWindow.clear()
|
||||
autoFailCount = 0
|
||||
showRealignPrompt = true
|
||||
}
|
||||
}
|
||||
@@ -843,9 +855,9 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
Arrangement.spacedBy(if (isCompact) 32.dp else 40.dp)
|
||||
) {
|
||||
|
||||
// Status badge (미부착 시 경고)
|
||||
val statusText = if (wasDetached) "Sensor detached" else urgency.message
|
||||
val statusColor = if (wasDetached) Color.Red else urgency.accentColor
|
||||
// Status badge (항상 urgency 메시지)
|
||||
val statusText = urgency.message
|
||||
val statusColor = urgency.accentColor
|
||||
Text(
|
||||
text = statusText,
|
||||
fontSize = 14.sp,
|
||||
@@ -901,7 +913,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
val volFontSize = if (appState.isDevMode) 18.sp else 24.sp
|
||||
Text(
|
||||
if (maxVolumeMl > 0) "${"%.0f".format(maxVolumeMl)} ml" else "${appState.estimatedVolume} ml",
|
||||
if (displayMaxVolumeMl > 0) "${"%.0f".format(displayMaxVolumeMl)} ml" else "${appState.estimatedVolume} ml",
|
||||
fontSize = volFontSize,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = urgency.accentColor,
|
||||
@@ -915,8 +927,9 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
val fillPct = if (maxVol > 0 && maxVolumeMl > 0) (maxVolumeMl / maxVol * 100).coerceAtMost(100.0) else 0.0
|
||||
val levelVal = if (maxVol > 0 && maxVolumeMl > 0) ((maxVolumeMl / maxVol) * BladderLevel.MAX_LEVEL).toInt().coerceIn(0, BladderLevel.MAX_LEVEL) else 0
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
val currentMeasurement = if (lastVolumeMl > 0) "%.0f".format(lastVolumeMl) else "—"
|
||||
InfoCard("Measurement", currentMeasurement, "ml", urgency.accentColor, Modifier.weight(1f))
|
||||
InfoCard("Fill", if (fillPct > 0) "%.0f".format(fillPct) else "—", "%", urgency.accentColor, Modifier.weight(1f))
|
||||
InfoCard("Level", if (levelVal > 0) "$levelVal/${BladderLevel.MAX_LEVEL}" else "—", "", urgency.accentColor, Modifier.weight(1f))
|
||||
}
|
||||
|
||||
// Control buttons: Void/Catheterize / Auto / Spot
|
||||
@@ -988,7 +1001,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
attempts++
|
||||
measure()
|
||||
while (isMeasuring) { kotlinx.coroutines.delay(100) }
|
||||
kotlinx.coroutines.delay(800)
|
||||
kotlinx.coroutines.delay(100) // throttle(800ms)이 간격 보장
|
||||
}
|
||||
Log.d("PiezoMonitor", "SPOT attempts=$attempts results=${spotVolumes.size}")
|
||||
if (spotVolumes.size >= 3) {
|
||||
@@ -1313,8 +1326,8 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
if (showRealignPrompt) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showRealignPrompt = false },
|
||||
title = { Text("Sensor Alignment Required") },
|
||||
text = { Text("Measurement failed multiple times. Please re-align the sensor.") },
|
||||
title = { Text("Sensor Position Issue") },
|
||||
text = { Text("The sensor may have shifted from the optimal position. Please re-run Sensor Alignment to ensure accurate measurements.") },
|
||||
confirmButton = {
|
||||
Button(onClick = {
|
||||
showRealignPrompt = false
|
||||
|
||||
+82
-47
@@ -61,6 +61,7 @@ fun PlacementGuideView(appState: AppState) {
|
||||
var isScanning by remember { mutableStateOf(false) }
|
||||
var isContinuousMode by remember { mutableStateOf(false) } // Start 버튼으로 시작
|
||||
var waitingForStart by remember { mutableStateOf(true) } // 초기 안내 상태
|
||||
var startPressedAtMs by remember { mutableStateOf(0L) }
|
||||
val channelDetected = remember { mutableStateListOf(false, false, false, false, false, false) }
|
||||
val channelUrineLen = remember { mutableStateListOf(0, 0, 0, 0, 0, 0) }
|
||||
val isSweepMode = com.example.medilightv2android.managers.GreenZoneConstants.placementGuideMode ==
|
||||
@@ -75,7 +76,8 @@ fun PlacementGuideView(appState: AppState) {
|
||||
var scanCount by remember { mutableIntStateOf(0) }
|
||||
var isLocked by remember { mutableStateOf(false) }
|
||||
var greenLockedAtMs by remember { mutableStateOf(0L) } // GREEN 진입 시각
|
||||
val greenHoldMs = 4000L // GREEN 후 최소 4초 유지
|
||||
val greenHoldMs = 7000L // GREEN 후 최소 7초 유지
|
||||
var greenFailCount by remember { mutableIntStateOf(0) } // GREEN 이탈 디바운스
|
||||
var lastChannelData by remember { mutableStateOf<List<PiezoChannelData>>(emptyList()) }
|
||||
var lastWallResults by remember { mutableStateOf<List<Pair<Int, Int>?>>(List(6) { null }) }
|
||||
var debounceCount by remember { mutableIntStateOf(0) }
|
||||
@@ -354,7 +356,7 @@ fun PlacementGuideView(appState: AppState) {
|
||||
modifier = Modifier
|
||||
.size(width = 192.dp, height = 120.dp)
|
||||
.align(Alignment.Center)
|
||||
.offset(x = 0.dp, y = 110.dp),
|
||||
.offset(x = 0.dp, y = 95.dp),
|
||||
contentScale = androidx.compose.ui.layout.ContentScale.Fit,
|
||||
alpha = 0.95f
|
||||
)
|
||||
@@ -574,10 +576,13 @@ fun PlacementGuideView(appState: AppState) {
|
||||
val detachResult = if (fullCh.size >= 6) DetachmentDetection.detect(buffers) else null
|
||||
|
||||
if (detachResult != null && detachResult.isDetached) {
|
||||
directionHint = if (isSweepMode) "Sensor detached. Place near navel"
|
||||
else "Sensor detached. Place above pubic bone"
|
||||
directionIcon = "exclamationmark.triangle"
|
||||
directionHint = "Sensor detached. Place above pubic bone, then press Start"
|
||||
directionIcon = "" // 화살표 없음
|
||||
placementScore = 0
|
||||
isContinuousMode = false
|
||||
waitingForStart = true
|
||||
placementPhase = PlacementPhase.VERTICAL
|
||||
isLocked = false
|
||||
if (lastLedMode != 4) { bleManager.sendLedMode(4); lastLedMode = 4 }
|
||||
bleManager.debugLogger.warn("DETACHED mean=${"%.1f".format(detachResult.meanAbs)} std=${"%.1f".format(detachResult.stdVal)}")
|
||||
Log.w("PlacementGuide", "DETACHED mean=${
|
||||
@@ -621,11 +626,22 @@ fun PlacementGuideView(appState: AppState) {
|
||||
val now = System.currentTimeMillis()
|
||||
val elapsed = now - lastHintChangeMs
|
||||
// GREEN 진입/이탈 및 phase 전환은 즉시, 같은 phase 내 힌트만 디바운스 (1.5초)
|
||||
// GREEN hold 체크 (디바운스보다 먼저)
|
||||
val greenHeld = isLocked && (now - greenLockedAtMs < greenHoldMs)
|
||||
|
||||
val wasGreen = isLocked
|
||||
val isNowGreen = guideResult.isPass
|
||||
val greenChanged = wasGreen != isNowGreen
|
||||
// GREEN hold 중에는 이탈 무시
|
||||
val greenChanged = if (greenHeld) {
|
||||
!wasGreen && isNowGreen // 진입만 허용, 이탈은 무시
|
||||
} else {
|
||||
wasGreen != isNowGreen
|
||||
}
|
||||
val phaseChanged = guideResult.nextPhase != placementPhase
|
||||
if (greenChanged || phaseChanged || (debounceCount >= 2 && elapsed >= 1500)) {
|
||||
// Start 후 5초간 같은 phase 내 힌트는 즉시, phase 전환은 3초 유지
|
||||
val justStarted = (now - startPressedAtMs) < 5000
|
||||
val hintUpdate = justStarted && !phaseChanged // 같은 step 내 힌트만 즉시
|
||||
if (hintUpdate || greenChanged || (phaseChanged && !greenHeld && debounceCount >= 2 && elapsed >= 3000) || (!phaseChanged && !greenHeld && debounceCount >= 2 && elapsed >= 1500)) {
|
||||
directionHint = guideResult.hint
|
||||
directionIcon = guideResult.icon
|
||||
placementScore = guideResult.score
|
||||
@@ -634,29 +650,36 @@ fun PlacementGuideView(appState: AppState) {
|
||||
if (!guideResult.cv.isNaN()) lastCv = guideResult.cv
|
||||
}
|
||||
|
||||
// GREEN hold: 한번 optimal 진입 후 5초간 유지 (미세 변화 무시)
|
||||
val greenHeld = isLocked && (now - greenLockedAtMs < greenHoldMs)
|
||||
// GREEN hold 중이면 힌트 강제 유지
|
||||
if (greenHeld) {
|
||||
directionHint = "Optimal position!"
|
||||
directionIcon = "checkmark.circle"
|
||||
placementPhase = PlacementPhase.GREEN
|
||||
if (!guideResult.cv.isNaN()) lastCv = guideResult.cv
|
||||
}
|
||||
|
||||
if (guideResult.isPass || greenHeld) {
|
||||
if (!isLocked) greenLockedAtMs = now // 최초 진입 시각 기록
|
||||
if (guideResult.isPass) {
|
||||
if (!isLocked) greenLockedAtMs = now
|
||||
isLocked = true
|
||||
greenFailCount = 0 // 성공 → 실패 카운트 리셋
|
||||
if (lastLedMode != 6) { bleManager.sendLedMode(6); lastLedMode = 6 }
|
||||
// GREEN hold 중이면 힌트도 optimal 유지
|
||||
if (greenHeld && !guideResult.isPass) {
|
||||
bleManager.debugLogger.info("PLACEMENT_GREEN cv=${"%.3f".format(guideResult.cv)} lr_dev=${"%.3f".format(guideResult.lrDev)} score=${guideResult.score} otsu=${"%.0f".format(analyzer.lastOtsuThreshold)}")
|
||||
} else if (isLocked && !greenHeld) {
|
||||
// hold 지남 + 실패 → 3회 연속 실패해야 이탈
|
||||
greenFailCount++
|
||||
if (greenFailCount < 3) {
|
||||
// 아직 이탈 안 함 — GREEN 유지
|
||||
directionHint = "Optimal position!"
|
||||
directionIcon = "checkmark.circle"
|
||||
placementPhase = PlacementPhase.GREEN
|
||||
}
|
||||
bleManager.debugLogger.info("PLACEMENT_GREEN cv=${"%.3f".format(guideResult.cv)} lr_dev=${"%.3f".format(guideResult.lrDev)} score=${guideResult.score} otsu=${"%.0f".format(analyzer.lastOtsuThreshold)}")
|
||||
} else {
|
||||
val wasGreenBefore = isLocked
|
||||
isLocked = false
|
||||
greenLockedAtMs = 0L
|
||||
if (lastLedMode != 5) { bleManager.sendLedMode(5); lastLedMode = 5 }
|
||||
// GREEN에서 이탈 → 스캔 중지, 재시작 대기
|
||||
if (wasGreenBefore) {
|
||||
} else {
|
||||
// 3회 연속 실패 → 진짜 이탈
|
||||
isLocked = false
|
||||
greenLockedAtMs = 0L
|
||||
greenFailCount = 0
|
||||
if (lastLedMode != 5) { bleManager.sendLedMode(5); lastLedMode = 5 }
|
||||
directionHint = "Position lost. Place sensor above pubic bone, then press Start"
|
||||
directionIcon = "arrow.up"
|
||||
directionIcon = "" // 화살표 없음
|
||||
placementScore = 10
|
||||
placementPhase = PlacementPhase.VERTICAL
|
||||
lastHintChangeMs = now
|
||||
@@ -666,7 +689,11 @@ fun PlacementGuideView(appState: AppState) {
|
||||
maxCenterSeen = 0; stableAtMax = 0; boundaryHit = false
|
||||
sweepLastDetected = 0; sweepEverSawAll = false
|
||||
rTracker.reset()
|
||||
bleManager.debugLogger.info("PLACEMENT_GREEN_EXIT after 3 consecutive fails")
|
||||
}
|
||||
} else {
|
||||
// GREEN 아닌 상태에서 실패
|
||||
if (lastLedMode != 5) { bleManager.sendLedMode(5); lastLedMode = 5 }
|
||||
bleManager.debugLogger.info("PLACEMENT_${guideResult.phase.name} score=${guideResult.score} otsu=${"%.0f".format(analyzer.lastOtsuThreshold)} hint=${guideResult.hint}")
|
||||
}
|
||||
}
|
||||
@@ -716,6 +743,23 @@ fun PlacementGuideView(appState: AppState) {
|
||||
onClick = {
|
||||
waitingForStart = false
|
||||
isContinuousMode = true
|
||||
// 전체 상태 리셋
|
||||
debounceCount = 0
|
||||
lastHintChangeMs = 0L
|
||||
scanCount = 0
|
||||
startPressedAtMs = System.currentTimeMillis()
|
||||
placementPhase = PlacementPhase.VERTICAL
|
||||
isLocked = false
|
||||
greenLockedAtMs = 0L
|
||||
greenFailCount = 0
|
||||
placementScore = 0
|
||||
maxCenterSeen = 0; stableAtMax = 0; boundaryHit = false
|
||||
sweepLastDetected = 0; sweepEverSawAll = false
|
||||
for (i in 0 until 6) { channelDetected[i] = false; channelUrineLen[i] = 0 }
|
||||
lastWallResults = List(6) { null }
|
||||
rTracker.reset()
|
||||
currentRMm = null; bestRMm = 0.0
|
||||
sagittalArgMaxCh = null; lateralOffsetMm = null
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -1151,7 +1195,7 @@ private fun computePlacementGuide(
|
||||
val detectedCount = detected.count { it }
|
||||
|
||||
if (detectedCount == 0) {
|
||||
return PlacementGuideResult("Raise the sensor ↑", "arrow.up", 0,
|
||||
return PlacementGuideResult("Raise the sensor up", "arrow.up", 0,
|
||||
phase = PlacementPhase.VERTICAL, nextPhase = PlacementPhase.VERTICAL)
|
||||
}
|
||||
|
||||
@@ -1209,7 +1253,7 @@ private fun computePlacementGuide(
|
||||
val lrHint = when {
|
||||
l4 > 0 && l5 > 0 && l4 > l5 -> "Move left ←"
|
||||
l4 > 0 && l5 > 0 && l5 > l4 -> "Move right →"
|
||||
else -> "Checking lateral..."
|
||||
else -> "Checking left/right balance..."
|
||||
}
|
||||
val lrIcon = when {
|
||||
l4 > 0 && l5 > 0 && l4 > l5 -> "arrow.left"
|
||||
@@ -1245,13 +1289,13 @@ private fun computePlacementGuide(
|
||||
val hint: String; val icon: String
|
||||
when {
|
||||
(ch0det || ch1det) && !ch2det && !ch3det -> {
|
||||
hint = "Raise the sensor ↑"; icon = "arrow.up"
|
||||
hint = "Raise the sensor up"; icon = "arrow.up"
|
||||
}
|
||||
(ch3det || ch2det) && !ch0det && !ch1det -> {
|
||||
hint = "Lower the sensor ↓"; icon = "arrow.down"
|
||||
hint = "Lower the sensor down"; icon = "arrow.down"
|
||||
}
|
||||
else -> {
|
||||
hint = "Raise the sensor ↑"; icon = "arrow.up"
|
||||
hint = "Raise the sensor up"; icon = "arrow.up"
|
||||
}
|
||||
}
|
||||
return PlacementGuideResult(hint, icon, centerDetected * 10,
|
||||
@@ -1295,17 +1339,12 @@ private fun computePlacementGuide(
|
||||
val ch4ok = detected.getOrElse(4) { false }
|
||||
val ch5ok = detected.getOrElse(5) { false }
|
||||
|
||||
// 좌우도 이미 균등하면 LATERAL 건너뛰고 바로 GREEN
|
||||
if (ch4ok && ch5ok && kotlin.math.abs(l4 - l5) <= 5) {
|
||||
return PlacementGuideResult("Position OK! Final check...",
|
||||
"checkmark.circle", 65,
|
||||
phase = PlacementPhase.VERTICAL, nextPhase = PlacementPhase.GREEN)
|
||||
}
|
||||
// 항상 LATERAL 단계를 거침 (Step 1→2→3 순서 강제)
|
||||
|
||||
val lrHint = when {
|
||||
l4 > 0 && l5 > 0 && l4 > l5 -> "Move left ←"
|
||||
l4 > 0 && l5 > 0 && l5 > l4 -> "Move right →"
|
||||
else -> "Checking lateral..."
|
||||
else -> "Checking left/right balance..."
|
||||
}
|
||||
val lrIcon = when {
|
||||
l4 > 0 && l5 > 0 && l4 > l5 -> "arrow.left"
|
||||
@@ -1326,21 +1365,17 @@ private fun computePlacementGuide(
|
||||
val l5 = urineLens.getOrElse(5) { 0 }
|
||||
val ch4ok = detected.getOrElse(4) { false }
|
||||
val ch5ok = detected.getOrElse(5) { false }
|
||||
if (ch4ok && ch5ok && kotlin.math.abs(l4 - l5) <= 5) {
|
||||
return PlacementGuideResult("Position OK! Final check...",
|
||||
"checkmark.circle", 60,
|
||||
phase = PlacementPhase.VERTICAL, nextPhase = PlacementPhase.GREEN)
|
||||
}
|
||||
return PlacementGuideResult("Checking lateral...",
|
||||
// 항상 LATERAL 단계를 거침
|
||||
return PlacementGuideResult("Checking left/right balance...",
|
||||
"checkmark.circle", 50,
|
||||
phase = PlacementPhase.VERTICAL, nextPhase = PlacementPhase.LATERAL)
|
||||
} else if (gradient > 0) {
|
||||
val intensity = if (gradient > 0.8) "Lower ↓" else "Lower slightly ↓"
|
||||
val intensity = if (gradient > 0.8) "Lower the sensor down" else "Lower the sensor slightly"
|
||||
return PlacementGuideResult(intensity, "arrow.down",
|
||||
(20 + centerDetected * 5).coerceAtMost(35),
|
||||
phase = PlacementPhase.VERTICAL, nextPhase = PlacementPhase.VERTICAL)
|
||||
} else {
|
||||
val intensity = if (gradient < -0.8) "Raise ↑" else "Raise slightly ↑"
|
||||
val intensity = if (gradient < -0.8) "Raise the sensor up" else "Raise the sensor slightly"
|
||||
return PlacementGuideResult(intensity, "arrow.up",
|
||||
(20 + centerDetected * 5).coerceAtMost(35),
|
||||
phase = PlacementPhase.VERTICAL, nextPhase = PlacementPhase.VERTICAL)
|
||||
@@ -1507,10 +1542,10 @@ private fun computePlacementGuide(
|
||||
adjustHint = "Move right →"; adjustIcon = "arrow.right"
|
||||
}
|
||||
gradient > 0.3 -> {
|
||||
adjustHint = "Lower ↓"; adjustIcon = "arrow.down"
|
||||
adjustHint = "Lower the sensor down"; adjustIcon = "arrow.down"
|
||||
}
|
||||
gradient < -0.3 -> {
|
||||
adjustHint = "Raise ↑"; adjustIcon = "arrow.up"
|
||||
adjustHint = "Raise the sensor up"; adjustIcon = "arrow.up"
|
||||
}
|
||||
else -> {
|
||||
// 방향 애매 + CV 미달 → ch별 len 분석으로 방향 제시
|
||||
@@ -1521,10 +1556,10 @@ private fun computePlacementGuide(
|
||||
// 최소 채널이 최대의 60% 미만 → 약한 쪽으로 안내
|
||||
if (minCh <= 1) {
|
||||
// ch0/ch1 약함 → 위로
|
||||
adjustHint = "Raise slightly ↑"; adjustIcon = "arrow.up"
|
||||
adjustHint = "Raise the sensor slightly"; adjustIcon = "arrow.up"
|
||||
} else {
|
||||
// ch2/ch3 약함 → 아래로
|
||||
adjustHint = "Lower slightly ↓"; adjustIcon = "arrow.down"
|
||||
adjustHint = "Lower the sensor slightly"; adjustIcon = "arrow.down"
|
||||
}
|
||||
} else {
|
||||
adjustHint = "Fine-tune position"; adjustIcon = "checkmark.circle"
|
||||
|
||||
Reference in New Issue
Block a user