fix(dev): 슬라이더 변경이 앱 재시작 시 사라지던 문제 — SharedPreferences 영속화
증상: Dev settings 의 walking/posture 임계 슬라이더를 조정해도 다음 앱 실행
시 default 로 리셋. 사용자: "save해도 변경이 안되는 것 같음".
원인: GreenZoneConstants 의 임계들은 @Volatile var (메모리 변수). 슬라이더가
즉시 값을 GreenZoneConstants 에 쓰지만 SharedPreferences 등 영속
저장소엔 안 들어감 → 앱 재시작 시 default 로 다시 초기화.
수정:
1) PostureTuningPrefs.kt 신규
- loadAll(context): SharedPreferences → GreenZoneConstants 로드
- saveAll(context): 현재 GreenZoneConstants → SharedPreferences 저장
- resetAll(context): defaults 로 복원
- 11개 키 (pitch lying / acc min/max / poll ms / walking 7개)
2) PiezoMonitoring LaunchedEffect(Unit) 에서 loadAll 호출
- 앱 진입 시 저장된 사용자 튜닝 값 자동 로드
3) 11개 slider 모두 onValueChange 마지막에 saveAll 호출
- 슬라이더 변경 즉시 영속 저장 (별도 save 버튼 불필요)
4) Reset Thresholds 버튼 추가 (Verification 섹션 위)
- resetAll 호출 → defaults 로 복원 + prefs 저장
- Toast 로 안내 ("close & reopen Settings to refresh sliders" —
slider 의 remember state 는 reopen 해야 갱신됨)
테스트:
1. Dev settings → Start Wmean = 25 변경 → 앱 종료
2. 앱 재실행 → Dev settings 다시 열기 → Start Wmean = 25 유지 확인 ✓
3. 분류기도 25 값으로 동작
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* PostureTuningPrefs — Posture / Walking detector 임계값 SharedPreferences 영속화.
|
||||||
|
*
|
||||||
|
* GreenZoneConstants 의 임계값들은 @Volatile var (메모리) 라 앱 재시작 시 default 리셋.
|
||||||
|
* 사용자가 dev slider 로 튜닝한 값을 유지하려면 prefs 에 저장 + 시작 시 로드.
|
||||||
|
*
|
||||||
|
* 사용:
|
||||||
|
* loadAll(context) — 앱/화면 시작 시 한 번 호출 → SharedPreferences 값 → GreenZoneConstants
|
||||||
|
* saveAll(context) — slider 변경 시 호출 → 현재 GreenZoneConstants → SharedPreferences
|
||||||
|
* resetAll(context) — 사용자가 reset 누름 → defaults 로 복원
|
||||||
|
*/
|
||||||
|
package com.example.medilightv2android.services
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.example.medilightv2android.managers.GreenZoneConstants
|
||||||
|
|
||||||
|
object PostureTuningPrefs {
|
||||||
|
|
||||||
|
private const val PREF = "posture_tuning_prefs"
|
||||||
|
|
||||||
|
// Defaults — GreenZoneConstants 의 초기값과 일치 유지
|
||||||
|
private const val D_PITCH_LYING = 25f
|
||||||
|
private const val D_ACC_MIN = 0.8f
|
||||||
|
private const val D_ACC_MAX = 1.2f
|
||||||
|
private const val D_POLL_MS = 1000L
|
||||||
|
private const val D_WALK_PITCH = 45f
|
||||||
|
private const val D_WALK_START_WMEAN = 15f
|
||||||
|
private const val D_WALK_START_WSTD = 2f
|
||||||
|
private const val D_WALK_KEEP_WMEAN = 2f
|
||||||
|
private const val D_WALK_START_HOLD_MS = 6000L
|
||||||
|
private const val D_WALK_KEEP_HOLD_MS = 2000L
|
||||||
|
private const val D_WALK_WINDOW_MS = 1500L
|
||||||
|
|
||||||
|
fun loadAll(context: Context) {
|
||||||
|
val p = context.applicationContext.getSharedPreferences(PREF, Context.MODE_PRIVATE)
|
||||||
|
GreenZoneConstants.pitchLyingThreshold = p.getFloat("pitchLying", D_PITCH_LYING)
|
||||||
|
GreenZoneConstants.postureAccMagMin = p.getFloat("accMin", D_ACC_MIN)
|
||||||
|
GreenZoneConstants.postureAccMagMax = p.getFloat("accMax", D_ACC_MAX)
|
||||||
|
GreenZoneConstants.posturePollIntervalMs = p.getLong("pollMs", D_POLL_MS)
|
||||||
|
GreenZoneConstants.walkingMinPitchDeg = p.getFloat("wPitch", D_WALK_PITCH)
|
||||||
|
GreenZoneConstants.walkingStartWmean = p.getFloat("wStartWmean", D_WALK_START_WMEAN)
|
||||||
|
GreenZoneConstants.walkingStartWstd = p.getFloat("wStartWstd", D_WALK_START_WSTD)
|
||||||
|
GreenZoneConstants.walkingKeepWmean = p.getFloat("wKeepWmean", D_WALK_KEEP_WMEAN)
|
||||||
|
GreenZoneConstants.walkingStartHoldMs = p.getLong("wStartHoldMs", D_WALK_START_HOLD_MS)
|
||||||
|
GreenZoneConstants.walkingKeepHoldMs = p.getLong("wKeepHoldMs", D_WALK_KEEP_HOLD_MS)
|
||||||
|
GreenZoneConstants.walkingWindowMs = p.getLong("wWindowMs", D_WALK_WINDOW_MS)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveAll(context: Context) {
|
||||||
|
context.applicationContext.getSharedPreferences(PREF, Context.MODE_PRIVATE).edit().apply {
|
||||||
|
putFloat("pitchLying", GreenZoneConstants.pitchLyingThreshold)
|
||||||
|
putFloat("accMin", GreenZoneConstants.postureAccMagMin)
|
||||||
|
putFloat("accMax", GreenZoneConstants.postureAccMagMax)
|
||||||
|
putLong("pollMs", GreenZoneConstants.posturePollIntervalMs)
|
||||||
|
putFloat("wPitch", GreenZoneConstants.walkingMinPitchDeg)
|
||||||
|
putFloat("wStartWmean", GreenZoneConstants.walkingStartWmean)
|
||||||
|
putFloat("wStartWstd", GreenZoneConstants.walkingStartWstd)
|
||||||
|
putFloat("wKeepWmean", GreenZoneConstants.walkingKeepWmean)
|
||||||
|
putLong("wStartHoldMs", GreenZoneConstants.walkingStartHoldMs)
|
||||||
|
putLong("wKeepHoldMs", GreenZoneConstants.walkingKeepHoldMs)
|
||||||
|
putLong("wWindowMs", GreenZoneConstants.walkingWindowMs)
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetAll(context: Context) {
|
||||||
|
GreenZoneConstants.pitchLyingThreshold = D_PITCH_LYING
|
||||||
|
GreenZoneConstants.postureAccMagMin = D_ACC_MIN
|
||||||
|
GreenZoneConstants.postureAccMagMax = D_ACC_MAX
|
||||||
|
GreenZoneConstants.posturePollIntervalMs = D_POLL_MS
|
||||||
|
GreenZoneConstants.walkingMinPitchDeg = D_WALK_PITCH
|
||||||
|
GreenZoneConstants.walkingStartWmean = D_WALK_START_WMEAN
|
||||||
|
GreenZoneConstants.walkingStartWstd = D_WALK_START_WSTD
|
||||||
|
GreenZoneConstants.walkingKeepWmean = D_WALK_KEEP_WMEAN
|
||||||
|
GreenZoneConstants.walkingStartHoldMs = D_WALK_START_HOLD_MS
|
||||||
|
GreenZoneConstants.walkingKeepHoldMs = D_WALK_KEEP_HOLD_MS
|
||||||
|
GreenZoneConstants.walkingWindowMs = D_WALK_WINDOW_MS
|
||||||
|
saveAll(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
-1
@@ -712,7 +712,9 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Posture event 로깅 — 검증용 CSV (앱 진입 시 새 파일)
|
// Posture event 로깅 — 검증용 CSV (앱 진입 시 새 파일)
|
||||||
|
// + 사용자 튜닝 임계값 SharedPreferences 에서 로드
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
com.example.medilightv2android.services.PostureTuningPrefs.loadAll(context)
|
||||||
com.example.medilightv2android.services.PostureEventLogger.startSession(context)
|
com.example.medilightv2android.services.PostureEventLogger.startSession(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1892,7 +1894,8 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
Slider(
|
Slider(
|
||||||
value = pitchThrVal,
|
value = pitchThrVal,
|
||||||
onValueChange = { pitchThrVal = it; GreenZoneConstants.pitchLyingThreshold = it },
|
onValueChange = { pitchThrVal = it; GreenZoneConstants.pitchLyingThreshold = it
|
||||||
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context) },
|
||||||
valueRange = 10f..70f,
|
valueRange = 10f..70f,
|
||||||
steps = 11,
|
steps = 11,
|
||||||
colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary)
|
colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary)
|
||||||
@@ -1968,6 +1971,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
onValueChange = {
|
onValueChange = {
|
||||||
pollIntervalVal = it
|
pollIntervalVal = it
|
||||||
GreenZoneConstants.posturePollIntervalMs = (it * 1000).toLong()
|
GreenZoneConstants.posturePollIntervalMs = (it * 1000).toLong()
|
||||||
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
||||||
},
|
},
|
||||||
valueRange = 1f..15f, // 1초 ~ 15초
|
valueRange = 1f..15f, // 1초 ~ 15초
|
||||||
steps = 0,
|
steps = 0,
|
||||||
@@ -1987,6 +1991,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
Slider(value = walkPitchVal, onValueChange = {
|
Slider(value = walkPitchVal, onValueChange = {
|
||||||
walkPitchVal = it; GreenZoneConstants.walkingMinPitchDeg = it
|
walkPitchVal = it; GreenZoneConstants.walkingMinPitchDeg = it
|
||||||
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
||||||
}, valueRange = 20f..70f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
}, valueRange = 20f..70f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
||||||
|
|
||||||
var walkStartWmean by remember { mutableStateOf(GreenZoneConstants.walkingStartWmean) }
|
var walkStartWmean by remember { mutableStateOf(GreenZoneConstants.walkingStartWmean) }
|
||||||
@@ -1997,6 +2002,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
Slider(value = walkStartWmean, onValueChange = {
|
Slider(value = walkStartWmean, onValueChange = {
|
||||||
walkStartWmean = it; GreenZoneConstants.walkingStartWmean = it
|
walkStartWmean = it; GreenZoneConstants.walkingStartWmean = it
|
||||||
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
||||||
}, valueRange = 1f..20f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
}, valueRange = 1f..20f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
||||||
|
|
||||||
var walkStartWstd by remember { mutableStateOf(GreenZoneConstants.walkingStartWstd) }
|
var walkStartWstd by remember { mutableStateOf(GreenZoneConstants.walkingStartWstd) }
|
||||||
@@ -2007,6 +2013,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
Slider(value = walkStartWstd, onValueChange = {
|
Slider(value = walkStartWstd, onValueChange = {
|
||||||
walkStartWstd = it; GreenZoneConstants.walkingStartWstd = it
|
walkStartWstd = it; GreenZoneConstants.walkingStartWstd = it
|
||||||
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
||||||
}, valueRange = 0.5f..10f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
}, valueRange = 0.5f..10f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
||||||
|
|
||||||
var walkKeepWmean by remember { mutableStateOf(GreenZoneConstants.walkingKeepWmean) }
|
var walkKeepWmean by remember { mutableStateOf(GreenZoneConstants.walkingKeepWmean) }
|
||||||
@@ -2017,6 +2024,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
Slider(value = walkKeepWmean, onValueChange = {
|
Slider(value = walkKeepWmean, onValueChange = {
|
||||||
walkKeepWmean = it; GreenZoneConstants.walkingKeepWmean = it
|
walkKeepWmean = it; GreenZoneConstants.walkingKeepWmean = it
|
||||||
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
||||||
}, valueRange = 0.5f..30f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
}, valueRange = 0.5f..30f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
||||||
|
|
||||||
// Walking 시간 변수 (sec)
|
// Walking 시간 변수 (sec)
|
||||||
@@ -2028,6 +2036,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
Slider(value = walkStartHold, onValueChange = {
|
Slider(value = walkStartHold, onValueChange = {
|
||||||
walkStartHold = it; GreenZoneConstants.walkingStartHoldMs = (it * 1000).toLong()
|
walkStartHold = it; GreenZoneConstants.walkingStartHoldMs = (it * 1000).toLong()
|
||||||
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
||||||
}, valueRange = 0.2f..5f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
}, valueRange = 0.2f..5f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
||||||
|
|
||||||
var walkKeepHold by remember { mutableStateOf(GreenZoneConstants.walkingKeepHoldMs / 1000f) }
|
var walkKeepHold by remember { mutableStateOf(GreenZoneConstants.walkingKeepHoldMs / 1000f) }
|
||||||
@@ -2038,6 +2047,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
Slider(value = walkKeepHold, onValueChange = {
|
Slider(value = walkKeepHold, onValueChange = {
|
||||||
walkKeepHold = it; GreenZoneConstants.walkingKeepHoldMs = (it * 1000).toLong()
|
walkKeepHold = it; GreenZoneConstants.walkingKeepHoldMs = (it * 1000).toLong()
|
||||||
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
||||||
}, valueRange = 0.5f..10f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
}, valueRange = 0.5f..10f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
||||||
|
|
||||||
var walkWindow by remember { mutableStateOf(GreenZoneConstants.walkingWindowMs / 1000f) }
|
var walkWindow by remember { mutableStateOf(GreenZoneConstants.walkingWindowMs / 1000f) }
|
||||||
@@ -2048,6 +2058,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
Slider(value = walkWindow, onValueChange = {
|
Slider(value = walkWindow, onValueChange = {
|
||||||
walkWindow = it; GreenZoneConstants.walkingWindowMs = (it * 1000).toLong()
|
walkWindow = it; GreenZoneConstants.walkingWindowMs = (it * 1000).toLong()
|
||||||
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
||||||
}, valueRange = 0.5f..5f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
}, valueRange = 0.5f..5f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
||||||
|
|
||||||
HorizontalDivider(modifier = Modifier.padding(vertical = dividerPadding))
|
HorizontalDivider(modifier = Modifier.padding(vertical = dividerPadding))
|
||||||
@@ -2063,6 +2074,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
Slider(value = accMagMin, onValueChange = {
|
Slider(value = accMagMin, onValueChange = {
|
||||||
accMagMin = it; GreenZoneConstants.postureAccMagMin = it
|
accMagMin = it; GreenZoneConstants.postureAccMagMin = it
|
||||||
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
||||||
}, valueRange = 0.3f..1.0f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
}, valueRange = 0.3f..1.0f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
||||||
|
|
||||||
var accMagMax by remember { mutableStateOf(GreenZoneConstants.postureAccMagMax) }
|
var accMagMax by remember { mutableStateOf(GreenZoneConstants.postureAccMagMax) }
|
||||||
@@ -2073,10 +2085,27 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
Slider(value = accMagMax, onValueChange = {
|
Slider(value = accMagMax, onValueChange = {
|
||||||
accMagMax = it; GreenZoneConstants.postureAccMagMax = it
|
accMagMax = it; GreenZoneConstants.postureAccMagMax = it
|
||||||
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
||||||
}, valueRange = 1.0f..3.0f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
}, valueRange = 1.0f..3.0f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
||||||
|
|
||||||
HorizontalDivider(modifier = Modifier.padding(vertical = dividerPadding))
|
HorizontalDivider(modifier = Modifier.padding(vertical = dividerPadding))
|
||||||
|
|
||||||
|
// Reset to defaults — 모든 threshold 를 default 로 복원 + prefs 저장.
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = {
|
||||||
|
com.example.medilightv2android.services.PostureTuningPrefs.resetAll(context)
|
||||||
|
android.widget.Toast.makeText(context,
|
||||||
|
"Defaults restored — close & reopen Settings to refresh sliders",
|
||||||
|
android.widget.Toast.LENGTH_LONG).show()
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = ButtonDefaults.outlinedButtonColors(contentColor = Color(0xFFE53935))
|
||||||
|
) {
|
||||||
|
Text("Reset Thresholds to Defaults", fontSize = 13.sp, fontWeight = FontWeight.SemiBold)
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalDivider(modifier = Modifier.padding(vertical = dividerPadding))
|
||||||
|
|
||||||
// Verification annotation buttons — ground truth marker.
|
// Verification annotation buttons — ground truth marker.
|
||||||
// 누르는 순간 timestamp 가 CSV 에 ANNOTATION row 로 기록 → 알고리즘 detection 과 대조.
|
// 누르는 순간 timestamp 가 CSV 에 ANNOTATION row 로 기록 → 알고리즘 detection 과 대조.
|
||||||
// 파일: Downloads/VesiScan_Posture/yyyy-MM-dd_HHmmss.csv
|
// 파일: Downloads/VesiScan_Posture/yyyy-MM-dd_HHmmss.csv
|
||||||
|
|||||||
Reference in New Issue
Block a user