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 android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
@@ -60,6 +61,23 @@ fun MedilightApp(modifier: Modifier = Modifier) {
val context = LocalContext.current val context = LocalContext.current
val appState = remember { AppState(context) } 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( Crossfade(
targetState = appState.currentScreen, targetState = appState.currentScreen,
animationSpec = tween(300), animationSpec = tween(300),
@@ -221,6 +221,17 @@ class BleManager private constructor(private val context: Context) {
handler.postDelayed(timeout, 10000) 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() { fun disconnect() {
isUserDisconnect = true isUserDisconnect = true
cancelAutoReconnect() cancelAutoReconnect()
@@ -614,7 +625,10 @@ class BleManager private constructor(private val context: Context) {
try { try {
val prefs = context.getSharedPreferences("medilight_prefs", Context.MODE_PRIVATE) 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) {} } catch (_: Exception) {}
gatt.discoverServices() 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 // Scan button
Button( Button(
onClick = { onClick = {
@@ -8,6 +8,9 @@ import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@@ -22,6 +25,12 @@ import com.example.medilightv2android.ui.theme.*
@Composable @Composable
fun HomeView(appState: AppState) { fun HomeView(appState: AppState) {
val context = LocalContext.current
// 앱 시작 시 마지막 기기에 자동 연결 시도
LaunchedEffect(Unit) {
BleManager.getInstance(context).autoConnectLastDevice()
}
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -37,7 +37,7 @@ fun PiezoPersonalizationView(appState: AppState) {
val handler = remember { Handler(Looper.getMainLooper()) } val handler = remember { Handler(Looper.getMainLooper()) }
var currentStep by remember { mutableIntStateOf(1) } var currentStep by remember { mutableIntStateOf(1) }
var optimizationComplete by remember { mutableStateOf(false) } var optimizationComplete by remember { mutableStateOf(false) }
val totalSteps = 4 val totalSteps = 1
fun startOptimization() { fun startOptimization() {
bleManager.onPiezoDataReceived = { bleManager.onPiezoDataReceived = {
@@ -88,43 +88,15 @@ fun PiezoPersonalizationView(appState: AppState) {
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
// Step content // Max Volume만 표시
when (currentStep) { when (currentStep) {
1 -> PostureStep(appState) 1 -> MaxVolumeStep(appState)
2 -> SensorPositionStep(appState)
3 -> MaxVolumeStep(appState)
4 -> {
LaunchedEffect(Unit) { startOptimization() }
OptimizationStep(optimizationComplete)
}
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
// Navigation button // Navigation button
if (currentStep < 4) { if (currentStep <= totalSteps) {
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) {
Button( Button(
onClick = { appState.personalizationComplete() }, onClick = { appState.personalizationComplete() },
modifier = Modifier modifier = Modifier