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:
2026-06-24 14:55:16 +09:00
parent 15993aa839
commit 280c5671b0
2 changed files with 110 additions and 1 deletions
@@ -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)
}
}