feat: UI 일괄 수정 (5,6,7,8번)

5. Onboarding 간소화: 4단계→1단계 (Max Volume만)
   Step 1(자세)/2(위치)/4(최적화) 제거, Continue 바로 완료

6. 자동 reconnect: 앱 시작 시 마지막 기기에 자동 연결
   SharedPreferences에 address+name 저장, HomeView에서 autoConnectLastDevice()

7. Demo 모드: DeviceScanView에서 Demo 버튼 제거

8. Android 뒤로가기: BackHandler로 화면별 네비게이션 매핑
   Placement→Home, Monitoring→Placement, History/Diary→Monitoring 등

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-28 11:41:22 +09:00
parent cc00c25a66
commit 227480af4b
5 changed files with 46 additions and 43 deletions
@@ -2,6 +2,7 @@ package com.example.medilightv2android
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.Crossfade
@@ -60,6 +61,23 @@ fun MedilightApp(modifier: Modifier = Modifier) {
val context = LocalContext.current
val appState = remember { AppState(context) }
// Android 뒤로가기 = 앱 내 뒤로가기
BackHandler(enabled = appState.currentScreen != AppScreen.HOME) {
when (appState.currentScreen) {
AppScreen.SENSOR_SELECT -> appState.currentScreen = AppScreen.HOME
AppScreen.DEVICE_SCAN -> appState.currentScreen = AppScreen.SENSOR_SELECT
AppScreen.PIEZO_PERSONALIZATION -> appState.currentScreen = AppScreen.DEVICE_SCAN
AppScreen.PLACEMENT_GUIDE -> appState.goHome()
AppScreen.PIEZO_MONITORING -> appState.currentScreen = AppScreen.PLACEMENT_GUIDE
AppScreen.VIVAMYO_MONITORING -> appState.goHome()
AppScreen.MEASUREMENT_HISTORY -> appState.currentScreen = AppScreen.PIEZO_MONITORING
AppScreen.VOIDING_DIARY -> appState.currentScreen = AppScreen.PIEZO_MONITORING
AppScreen.REMINDER_SETTINGS -> appState.currentScreen = AppScreen.PIEZO_MONITORING
AppScreen.URINE_CAMERA -> appState.currentScreen = AppScreen.PIEZO_MONITORING
else -> appState.currentScreen = AppScreen.HOME
}
}
Crossfade(
targetState = appState.currentScreen,
animationSpec = tween(300),
@@ -221,6 +221,17 @@ class BleManager private constructor(private val context: Context) {
handler.postDelayed(timeout, 10000)
}
/** 저장된 마지막 기기에 자동 연결 시도 */
fun autoConnectLastDevice() {
if (isConnected.value) return
val prefs = context.getSharedPreferences("medilight_prefs", Context.MODE_PRIVATE)
val address = prefs.getString("last_device_address", null) ?: return
val name = prefs.getString("last_device_name", null) ?: return
debugLogger.info("AUTO_CONNECT attempting $name ($address)")
lastConnectedAddress = address
scheduleAutoReconnect()
}
fun disconnect() {
isUserDisconnect = true
cancelAutoReconnect()
@@ -614,7 +625,10 @@ class BleManager private constructor(private val context: Context) {
try {
val prefs = context.getSharedPreferences("medilight_prefs", Context.MODE_PRIVATE)
prefs.edit().putString("last_device_address", gatt.device.address).apply()
prefs.edit()
.putString("last_device_address", gatt.device.address)
.putString("last_device_name", connectedDeviceName.value)
.apply()
} catch (_: Exception) {}
gatt.discoverServices()
@@ -249,16 +249,6 @@ fun DeviceScanView(appState: AppState) {
}
}
// Demo mode button
TextButton(
onClick = { appState.enterDemoMode() },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp)
) {
Text("Demo Mode (No Device)", fontSize = 14.sp, color = MlSecondaryText)
}
// Scan button
Button(
onClick = {
@@ -8,6 +8,9 @@ import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalContext
import com.example.medilightv2android.ble.BleManager
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -22,6 +25,12 @@ import com.example.medilightv2android.ui.theme.*
@Composable
fun HomeView(appState: AppState) {
val context = LocalContext.current
// 앱 시작 시 마지막 기기에 자동 연결 시도
LaunchedEffect(Unit) {
BleManager.getInstance(context).autoConnectLastDevice()
}
Column(
modifier = Modifier
.fillMaxSize()
@@ -37,7 +37,7 @@ fun PiezoPersonalizationView(appState: AppState) {
val handler = remember { Handler(Looper.getMainLooper()) }
var currentStep by remember { mutableIntStateOf(1) }
var optimizationComplete by remember { mutableStateOf(false) }
val totalSteps = 4
val totalSteps = 1
fun startOptimization() {
bleManager.onPiezoDataReceived = {
@@ -88,43 +88,15 @@ fun PiezoPersonalizationView(appState: AppState) {
Spacer(modifier = Modifier.height(24.dp))
// Step content
// Max Volume만 표시
when (currentStep) {
1 -> PostureStep(appState)
2 -> SensorPositionStep(appState)
3 -> MaxVolumeStep(appState)
4 -> {
LaunchedEffect(Unit) { startOptimization() }
OptimizationStep(optimizationComplete)
}
1 -> MaxVolumeStep(appState)
}
Spacer(modifier = Modifier.weight(1f))
// Navigation button
if (currentStep < 4) {
Button(
onClick = { currentStep++ },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 32.dp)
.padding(bottom = 32.dp)
.height(56.dp),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent),
contentPadding = PaddingValues()
) {
Box(
modifier = Modifier.fillMaxSize().background(
Brush.horizontalGradient(listOf(MlTeal, MlPrimary)),
RoundedCornerShape(16.dp)
),
contentAlignment = Alignment.Center
) {
Text("Next", fontSize = 18.sp, fontWeight = FontWeight.SemiBold, color = Color.White)
}
}
} else if (optimizationComplete) {
if (currentStep <= totalSteps) {
Button(
onClick = { appState.personalizationComplete() },
modifier = Modifier