Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,34 @@ fun NavController.navigateToHome(navOptions: NavOptions) {
}

fun NavGraphBuilder.homeNavGraph(
navigateToMealDetailScreen: () -> Unit,
navigateToMedicineDetailScreen: () -> Unit,
navigateToSleepDetailScreen: () -> Unit,
navigateToStateHealthDetailScreen: () -> Unit,
navigateToStateMentalDetailScreen: () -> Unit,
navigateToGlucoseDetailScreen: () -> Unit,
navigateToMealDetailScreen: (Int) -> Unit,
navigateToMedicineDetailScreen: (Int) -> Unit,
navigateToSleepDetailScreen: (Int) -> Unit,
navigateToStateHealthDetailScreen: (Int) -> Unit,
navigateToStateMentalDetailScreen: (Int) -> Unit,
navigateToGlucoseDetailScreen: (Int) -> Unit,
navigateToAlarmScreen: () -> Unit,
) {
composable<MainTabRoute.Home> { backStackEntry ->
HomeScreen(
navigateToMealDetailScreen = navigateToMealDetailScreen,
navigateToMedicineDetailScreen = navigateToMedicineDetailScreen,
navigateToSleepDetailScreen = navigateToSleepDetailScreen,
navigateToStateHealthDetailScreen = navigateToStateHealthDetailScreen,
navigateToStateMentalDetailScreen = navigateToStateMentalDetailScreen,
navigateToGlucoseDetailScreen = navigateToGlucoseDetailScreen,
navigateToMealDetailScreen = { elderId ->
navigateToMealDetailScreen(elderId)
},
navigateToMedicineDetailScreen = { elderId ->
navigateToMedicineDetailScreen(elderId)
},
navigateToSleepDetailScreen = { elderId ->
navigateToSleepDetailScreen(elderId)
},
navigateToStateHealthDetailScreen = { elderId ->
navigateToStateHealthDetailScreen(elderId)
},
navigateToStateMentalDetailScreen = { elderId ->
navigateToStateMentalDetailScreen(elderId)
},
navigateToGlucoseDetailScreen = { elderId ->
navigateToGlucoseDetailScreen(elderId)
},
mainBackStackEntry = backStackEntry,
navigateToAlarm = navigateToAlarmScreen,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,12 @@ import kotlinx.coroutines.launch
fun HomeScreen(
modifier: Modifier = Modifier,
homeViewModel: HomeViewModel = hiltViewModel(),
navigateToMealDetailScreen: () -> Unit,
navigateToMedicineDetailScreen: () -> Unit,
navigateToSleepDetailScreen: () -> Unit,
navigateToStateHealthDetailScreen: () -> Unit,
navigateToStateMentalDetailScreen: () -> Unit,
navigateToGlucoseDetailScreen: () -> Unit,
navigateToMealDetailScreen: (Int) -> Unit,
navigateToMedicineDetailScreen: (Int) -> Unit,
navigateToSleepDetailScreen: (Int) -> Unit,
navigateToStateHealthDetailScreen: (Int) -> Unit,
navigateToStateMentalDetailScreen: (Int) -> Unit,
navigateToGlucoseDetailScreen: (Int) -> Unit,
mainBackStackEntry: NavBackStackEntry,
navigateToAlarm: () -> Unit,
) {
Expand Down Expand Up @@ -117,12 +117,13 @@ fun HomeScreen(
homeViewModel.selectElder(selectedName)
dropdownOpened = false
},
navigateToMealDetailScreen = navigateToMealDetailScreen,
navigateToMedicineDetailScreen = navigateToMedicineDetailScreen,
navigateToSleepDetailScreen = navigateToSleepDetailScreen,
navigateToStateHealthDetailScreen = navigateToStateHealthDetailScreen,
navigateToStateMentalDetailScreen = navigateToStateMentalDetailScreen,
navigateToGlucoseDetailScreen = navigateToGlucoseDetailScreen,
navigateToMealDetailScreen = { selectedElderId?.let(navigateToMealDetailScreen) },
navigateToMedicineDetailScreen = { selectedElderId?.let(navigateToMedicineDetailScreen) },
navigateToSleepDetailScreen = { selectedElderId?.let(navigateToSleepDetailScreen) },
navigateToStateHealthDetailScreen = { selectedElderId?.let(navigateToStateHealthDetailScreen) },
navigateToStateMentalDetailScreen = { selectedElderId?.let(navigateToStateMentalDetailScreen) },
navigateToGlucoseDetailScreen = { selectedElderId?.let(navigateToGlucoseDetailScreen) },
Comment on lines +120 to +125
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

selectedElderId가 null일 때 동작 확인 필요

selectedElderId?.let(...) 패턴을 사용해 elderId가 null이면 네비게이션이 조용히 실패합니다. 사용자가 컨테이너를 클릭했는데 아무 반응이 없으면 혼란스러울 수 있습니다.

다음을 확인해주세요:

  • selectedElderId가 실제로 null이 될 수 있는 시나리오가 있나요?
  • null일 때 컨테이너들이 비활성화되거나 사용자에게 피드백이 제공되나요?

확인을 위해 다음 스크립트를 실행해보세요:


🏁 Script executed:

#!/bin/bash
# HomeViewModel에서 selectedElderId의 초기값과 null 가능성 확인
rg -n -A5 -B5 'selectedElderId.*StateFlow|StateFlow.*selectedElderId' --type=kt

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Search for selectedElderId declaration in HomeViewModel
rg -n -A5 -B5 'selectedElderId' app/src/main/java/com/konkuk/medicarecall --type=kotlin || \
rg -n -A5 -B5 'selectedElderId' app/src/main/java/com/konkuk/medicarecall -g '*.kt'

Length of output: 33097


🏁 Script executed:

#!/bin/bash
# Find HomeViewModel file
fd -e 'kt' -path '*HomeViewModel*' app/src/main/java/com/konkuk/medicarecall

Length of output: 168


🏁 Script executed:

#!/bin/bash
# Search for container onClick callbacks around the referenced lines
rg -n -B2 -A2 'onClick.*navigateToMeal|onClick.*navigateTo' app/src/main/java/com/konkuk/medicarecall -g '*.kt' | head -50

Length of output: 6472



🏁 Script executed:

#!/bin/bash
# Check if containers have any conditional enable/disable logic
rg -n -B5 -A5 'HomeMealContainer|HomeMedicineContainer|HomeSleepContainer|HomeStateHealthContainer|HomeStateMentalContainer|HomeGlucoseLevelContainer' app/src/main/java/com/konkuk/medicarecall/ui/feature/home/screen/HomeScreen.kt -g '*.kt' | grep -A5 -B5 'enabled\|isEnabled\|if.*selectedElderId'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check fetchElderList timing and completion
rg -n -A10 'fun fetchElderList' app/src/main/java/com/konkuk/medicarecall/ui/feature/home/viewmodel/HomeViewModel.kt

Length of output: 566


선택된 어르신이 없는 상태에서 컨테이너 클릭 시 사용자 피드백 부재

selectedElderId는 앱 시작 시 null로 초기화되며, fetchElderList() 비동기 작업 완료 후에 첫 번째 어르신으로 자동 설정됩니다. 이 로딩 구간 동안 사용자가 컨테이너를 클릭하면 { selectedElderId?.let(...) } 패턴으로 인해 네비게이션이 조용히 실패합니다. 현재 컨테이너들은 selectedElderId 상태와 무관하게 항상 활성화되어 있어 사용자에게 피드백이 없습니다.

해결 방안:

  • 로딩 중(homeUiState.isLoading == true)일 때 컨테이너를 비활성화하거나
  • 선택된 어르신이 없을 때 컨테이너에 시각적 비활성화 처리(alpha, 그레이아웃 등)를 추가
🤖 Prompt for AI Agents
In
app/src/main/java/com/konkuk/medicarecall/ui/feature/home/screen/HomeScreen.kt
around lines 120-125, the click lambdas silently no-op when selectedElderId is
null causing no user feedback; update the container composables to: 1) derive an
enabled flag (e.g. enabled = !homeUiState.isLoading && selectedElderId != null),
2) pass navigation lambdas only when enabled (or wrap them to show a
Snackbar/Toast when disabled), and 3) apply a visual disabled state (alpha or
gray-out) and disable click handling (use enabled parameter or clickable(enabled
= enabled)) so taps during loading/no-selection provide visible and/or explicit
feedback.


navigateToAlarm = navigateToAlarm,

snackbarHostState = snackbarHostState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import androidx.lifecycle.compose.LifecycleEventEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.konkuk.medicarecall.R
import com.konkuk.medicarecall.ui.common.component.TopAppBar
import com.konkuk.medicarecall.ui.feature.home.viewmodel.HomeViewModel
import com.konkuk.medicarecall.ui.feature.homedetail.glucoselevel.component.GlucoseGraph
import com.konkuk.medicarecall.ui.feature.homedetail.glucoselevel.component.GlucoseListItem
import com.konkuk.medicarecall.ui.feature.homedetail.glucoselevel.component.GlucoseStatusItem
Expand All @@ -55,40 +54,43 @@ import java.time.LocalDate
@Composable
fun GlucoseDetailScreen(
modifier: Modifier = Modifier,
elderId: Int,
onBack: () -> Unit,
glucoseViewModel: GlucoseViewModel = hiltViewModel(),
) {
val scrollState = rememberScrollState()
val uiState by glucoseViewModel.uiState.collectAsStateWithLifecycle()

// 어르신 선택 상태(selectedElderId) 관리
val homeViewModel: HomeViewModel = hiltViewModel()
val viewModel: GlucoseViewModel = hiltViewModel()

val uiState by viewModel.uiState.collectAsStateWithLifecycle()

// 선택된 어르신 ID를 구독 (null 가능)
val elderId by homeViewModel.selectedElderId.collectAsStateWithLifecycle()

// 페이지 카운터
val counter = remember {
mutableStateMapOf(
GlucoseTiming.BEFORE_MEAL to 0,
GlucoseTiming.AFTER_MEAL to 0,
)
}

val coroutineScope = rememberCoroutineScope()

// 로딩 요청 중복 방지를 위한 플래그
val isRequestingMore = remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope()

// 데이터 새로고침 로직
val refreshData = remember(viewModel) {
val refreshData = remember(glucoseViewModel) {
{
elderId?.let { id ->
counter[GlucoseTiming.BEFORE_MEAL] = 0
counter[GlucoseTiming.AFTER_MEAL] = 0
viewModel.getGlucoseData(id, 0, GlucoseTiming.BEFORE_MEAL, true)
viewModel.getGlucoseData(id, 0, GlucoseTiming.AFTER_MEAL, true)
}
counter[GlucoseTiming.BEFORE_MEAL] = 0
counter[GlucoseTiming.AFTER_MEAL] = 0

glucoseViewModel.getGlucoseData(
elderId = elderId,
counter = 0,
type = GlucoseTiming.BEFORE_MEAL,
isRefresh = true,
)
glucoseViewModel.getGlucoseData(
elderId = elderId,
counter = 0,
type = GlucoseTiming.AFTER_MEAL,
isRefresh = true,
)
}
}

Expand All @@ -105,16 +107,25 @@ fun GlucoseDetailScreen(
// 무한 스크롤 (더 빠른 트리거와 중복 요청 방지)
LaunchedEffect(scrollState.value, scrollState.maxValue) {
Log.d("scroll", "value: ${scrollState.value}, max: ${scrollState.maxValue}, isLoading: ${uiState.isLoading}, hasNext: ${uiState.hasNext}")

// 더 일찍 트리거 (500dp 전에 미리 로딩)
// 스크롤이 거의 끝까지 왔을 때만 다음 페이지 불러오기
val shouldLoad = scrollState.value > scrollState.maxValue - 500 || scrollState.maxValue <= 100

if (shouldLoad && elderId != null && !uiState.isLoading && uiState.hasNext && !isRequestingMore.value) {
if (shouldLoad &&
!uiState.isLoading &&
uiState.hasNext &&
!isRequestingMore.value
) {
isRequestingMore.value = true
val currentTiming = uiState.selectedTiming
val currentPage = counter.getValue(currentTiming)
Log.d("scroll", "Loading page ${currentPage + 1} for $currentTiming")
viewModel.getGlucoseData(elderId!!, currentPage + 1, currentTiming, false)

glucoseViewModel.getGlucoseData(
elderId = elderId,
counter = currentPage + 1, // 다음 페이지 요청
type = currentTiming,
isRefresh = false,
)

counter[currentTiming] = currentPage + 1
}
}
Expand All @@ -134,13 +145,11 @@ fun GlucoseDetailScreen(

// '공복'/'식후' 버튼
onTimingChange = { newTiming ->
viewModel.updateTiming(newTiming)
coroutineScope.launch {
scrollState.scrollTo(0)
}
glucoseViewModel.updateTiming(newTiming)
coroutineScope.launch { scrollState.scrollTo(0) }
},
// 그래프 점
onPointClick = { newIndex -> viewModel.onClickDots(newIndex) },
onPointClick = glucoseViewModel::onClickDots,
scrollState = scrollState,
onBack = onBack,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.konkuk.medicarecall.ui.feature.homedetail.meal.screen

import android.util.Log
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
Expand Down Expand Up @@ -28,7 +27,6 @@ import com.konkuk.medicarecall.ui.feature.calendar.DateSelector
import com.konkuk.medicarecall.ui.feature.calendar.WeeklyCalendar
import com.konkuk.medicarecall.ui.feature.calendar.viewmodel.CalendarUiState
import com.konkuk.medicarecall.ui.feature.calendar.viewmodel.CalendarViewModel
import com.konkuk.medicarecall.ui.feature.home.viewmodel.HomeViewModel
import com.konkuk.medicarecall.ui.feature.homedetail.meal.component.MealDetailCard
import com.konkuk.medicarecall.ui.feature.homedetail.meal.viewmodel.MealUiState
import com.konkuk.medicarecall.ui.feature.homedetail.meal.viewmodel.MealViewModel
Expand All @@ -38,33 +36,30 @@ import java.time.LocalDate
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun MealDetailScreen(
elderId: Int,
onBack: () -> Unit,
homeViewModel: HomeViewModel = hiltViewModel(),
calendarViewModel: CalendarViewModel = hiltViewModel(),
mealViewModel: MealViewModel = hiltViewModel(),
) {
// 재진입 시 오늘로 초기화
LifecycleEventEffect(Lifecycle.Event.ON_RESUME) {
calendarViewModel.resetToToday()
}

// 날짜만 Observe
val selectedDate by calendarViewModel.selectedDate.collectAsStateWithLifecycle()
val elderId by homeViewModel.selectedElderId.collectAsStateWithLifecycle()
val meals by mealViewModel.meals.collectAsStateWithLifecycle()

// 날짜/어르신 변경 시마다 로드
LaunchedEffect(elderId, selectedDate) {
Log.d("MED_UI", "LaunchedEffect: elderId=$elderId, date=$selectedDate")
elderId?.let { mealViewModel.loadMealsForDate(it, selectedDate) }
mealViewModel.loadMealsForDate(elderId, selectedDate)
}

val meals by mealViewModel.meals.collectAsStateWithLifecycle()

MealDetailScreenLayout(
onBack = onBack,
selectedDate = selectedDate,
meals = meals,
weekDates = calendarViewModel.getCurrentWeekDates(),
onDateSelected = { calendarViewModel.selectDate(it) },
onDateSelected = calendarViewModel::selectDate,
onMonthClick = { /* 모달 열기 */ },
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import com.konkuk.medicarecall.ui.feature.calendar.DateSelector
import com.konkuk.medicarecall.ui.feature.calendar.WeeklyCalendar
import com.konkuk.medicarecall.ui.feature.calendar.viewmodel.CalendarUiState
import com.konkuk.medicarecall.ui.feature.calendar.viewmodel.CalendarViewModel
import com.konkuk.medicarecall.ui.feature.home.viewmodel.HomeViewModel
import com.konkuk.medicarecall.ui.feature.homedetail.medicine.component.MedicineDetailCard
import com.konkuk.medicarecall.ui.feature.homedetail.medicine.viewmodel.DoseStatus
import com.konkuk.medicarecall.ui.feature.homedetail.medicine.viewmodel.DoseStatusItem
Expand All @@ -40,8 +39,8 @@ import java.time.LocalDate
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun MedicineDetailScreen(
elderId: Int,
onBack: () -> Unit,
homeViewModel: HomeViewModel = hiltViewModel(),
calendarViewModel: CalendarViewModel = hiltViewModel(),
medicineViewModel: MedicineViewModel = hiltViewModel(),
) {
Expand All @@ -51,7 +50,6 @@ fun MedicineDetailScreen(
}

val selectedDate by calendarViewModel.selectedDate.collectAsStateWithLifecycle()
val elderId by homeViewModel.selectedElderId.collectAsStateWithLifecycle()

// 날짜/어르신 변경 시마다 로드
LaunchedEffect(elderId, selectedDate) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.konkuk.medicarecall.ui.feature.homedetail.navigation
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.toRoute
import com.konkuk.medicarecall.ui.feature.homedetail.glucoselevel.screen.GlucoseDetailScreen
import com.konkuk.medicarecall.ui.feature.homedetail.meal.screen.MealDetailScreen
import com.konkuk.medicarecall.ui.feature.homedetail.medicine.screen.MedicineDetailScreen
Expand All @@ -11,62 +12,84 @@ import com.konkuk.medicarecall.ui.feature.homedetail.statehealth.screen.StateHea
import com.konkuk.medicarecall.ui.feature.homedetail.statemental.screen.StateMentalDetailScreen
import com.konkuk.medicarecall.ui.navigation.Route

fun NavController.navigateToMealDetailScreen() {
navigate(Route.MealDetail)
fun NavController.navigateToMealDetailScreen(elderId: Int) {
navigate(Route.MealDetail(elderId))
}

fun NavController.navigateToMedicineDetailScreen() {
navigate(Route.MedicineDetail)
fun NavController.navigateToMedicineDetailScreen(elderId: Int) {
navigate(Route.MedicineDetail(elderId))
}

fun NavController.navigateToSleepDetailScreen() {
navigate(Route.SleepDetail)
fun NavController.navigateToSleepDetailScreen(elderId: Int) {
navigate(Route.SleepDetail(elderId))
}

fun NavController.navigateToStateHealthDetailScreen() {
navigate(Route.StateHealthDetail)
fun NavController.navigateToStateHealthDetailScreen(elderId: Int) {
navigate(Route.StateHealthDetail(elderId))
}

fun NavController.navigateToStateMentalDetailScreen() {
navigate(Route.StateMentalDetail)
fun NavController.navigateToStateMentalDetailScreen(elderId: Int) {
navigate(Route.StateMentalDetail(elderId))
}

fun NavController.navigateToGlucoseDetailScreen() {
navigate(Route.GlucoseDetail)
fun NavController.navigateToGlucoseDetailScreen(elderId: Int) {
navigate(Route.GlucoseDetail(elderId))
}

fun NavGraphBuilder.homeDetailNavGraph(
popBackStack: () -> Unit,
) {
// 홈 상세 화면_식사 화면
composable<Route.MealDetail> {
composable<Route.MealDetail> { backStackEntry ->
val route = backStackEntry.toRoute<Route.MealDetail>()
MealDetailScreen(
elderId = route.elderId,
onBack = popBackStack,
)
}

// 홈 상세 화면_복용 화면
composable<Route.MedicineDetail> {
MedicineDetailScreen(onBack = popBackStack)
composable<Route.MedicineDetail> { backStackEntry ->
val route = backStackEntry.toRoute<Route.MedicineDetail>()
MedicineDetailScreen(
elderId = route.elderId,
onBack = popBackStack,
)
}

// 홈 상세 화면_수면 화면
composable<Route.SleepDetail> {
SleepDetailScreen(onBack = popBackStack)
composable<Route.SleepDetail> { backStackEntry ->
val route = backStackEntry.toRoute<Route.SleepDetail>()
SleepDetailScreen(
elderId = route.elderId,
onBack = popBackStack,
)
}

// 홈 상세 화면_건강 징후 화면
composable<Route.StateHealthDetail> {
StateHealthDetailScreen(onBack = popBackStack)
composable<Route.StateHealthDetail> { backStackEntry ->
val route = backStackEntry.toRoute<Route.StateHealthDetail>()
StateHealthDetailScreen(
elderId = route.elderId,
onBack = popBackStack,
)
}

// 홈 상세 화면_심리 상태 화면
composable<Route.StateMentalDetail> {
StateMentalDetailScreen(onBack = popBackStack)
composable<Route.StateMentalDetail> { backStackEntry ->
val route = backStackEntry.toRoute<Route.StateMentalDetail>()
StateMentalDetailScreen(
elderId = route.elderId,
onBack = popBackStack,
)
}

// 홈 상세 화면_혈당 화면
composable<Route.GlucoseDetail> {
GlucoseDetailScreen(onBack = popBackStack)
composable<Route.GlucoseDetail> { backStackEntry ->
val route = backStackEntry.toRoute<Route.GlucoseDetail>()
GlucoseDetailScreen(
elderId = route.elderId,
onBack = popBackStack,
)
}
}
Loading