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 @@ -6,5 +6,5 @@ interface ElderIdRepository {
suspend fun updateElderIds(elderIdMap: Map<Int, String>)
suspend fun updateElderId(elderId: Int, name: String)
suspend fun clearElderIds()
suspend fun getElderIds(): Flow<Map<Int, String>>
fun getElderIds(): Flow<Map<Int, String>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ class ElderIdRepositoryImpl @Inject constructor(@ApplicationContext private val
context.elderIdsDataStore.updateData { it.copy(elderIds = elderIds) }
}

override suspend fun getElderIds(): Flow<Map<Int, String>> {
override fun getElderIds(): Flow<Map<Int, String>> {
val preferences = context.elderIdsDataStore.data.map { it.elderIds }
return preferences
}
} // Flow 자체가 비동기 스트림인데 suspend를 또 붙였다 해서 수정했습니다. 문제 시 다시 원상복구 해놓겠습니다

override suspend fun clearElderIds() {
context.elderIdsDataStore.updateData {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import com.konkuk.medicarecall.data.dto.response.CallTimeResponseDto
import com.konkuk.medicarecall.data.dto.response.EldersInfoResponseDto
import com.konkuk.medicarecall.data.dto.response.EldersSubscriptionResponseDto
import com.konkuk.medicarecall.data.repository.EldersInfoRepository
import com.konkuk.medicarecall.ui.common.util.formatAsDate
import com.konkuk.medicarecall.ui.model.ElderData
import com.konkuk.medicarecall.ui.type.ElderResidenceType
import com.konkuk.medicarecall.ui.type.GenderType
import com.konkuk.medicarecall.ui.type.RelationshipType
import com.konkuk.medicarecall.ui.common.util.formatAsDate
import retrofit2.HttpException
import javax.inject.Inject

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
Expand All @@ -46,7 +45,6 @@ import com.konkuk.medicarecall.ui.feature.login.carecall.component.TimePickerBot
import com.konkuk.medicarecall.ui.feature.login.carecall.component.TimeSettingItem
import com.konkuk.medicarecall.ui.feature.login.carecall.viewmodel.CallTimeViewModel
import com.konkuk.medicarecall.ui.feature.login.info.component.LoginBackButton
import com.konkuk.medicarecall.ui.feature.settings.viewmodel.EldersInfoViewModel
import com.konkuk.medicarecall.ui.model.CallTimes
import com.konkuk.medicarecall.ui.theme.MediCareCallTheme
import com.konkuk.medicarecall.ui.type.CTAButtonType
Expand All @@ -65,61 +63,30 @@ fun CallTimeScreen(
modifier: Modifier = Modifier,
onBack: () -> Unit = {},
navigateToPayment: () -> Unit = {},
eldersInfoViewModel: EldersInfoViewModel = hiltViewModel(),
callTimeViewModel: CallTimeViewModel = hiltViewModel(),
) {
LaunchedEffect(Unit) { eldersInfoViewModel.ensureLoaded() }
val elderMap = callTimeViewModel.elderIds
// Map<Int, String> -> viewmodel에서 직접 가져옴
val isLoading = callTimeViewModel.isLoading.value

val isLoading = eldersInfoViewModel.isLoading.value
val error = eldersInfoViewModel.error.value
val nameIdList = eldersInfoViewModel.elderNameIdMapList

when {
isLoading -> {
Box(
Modifier
.fillMaxSize()
.background(MediCareCallTheme.colors.bg)
.systemBarsPadding(),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator(
color = MediCareCallTheme.colors.main,
modifier = Modifier.align(Alignment.Center),
)
}
return
}

error != null -> {
Column(
Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Text("어르신 정보를 불러오지 못했어요.\n잠시 후 다시 시도해 주세요.")
Spacer(Modifier.height(12.dp))
CTAButton(
type = CTAButtonType.GREEN,
text = "다시 시도",
onClick = { eldersInfoViewModel.refresh() },
)
}
return
}

nameIdList.isEmpty() -> {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("등록된 어르신이 없습니다.")
}
return
if (isLoading) {
Box(
Modifier
.fillMaxSize()
.background(MediCareCallTheme.colors.bg),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator(
color = MediCareCallTheme.colors.main,
)
}
return
}

val scrollState = rememberScrollState() // 스크롤 상태
var showBottomSheet by remember { mutableStateOf(false) } // 하단 시트 제어
val elderNames = nameIdList.map { it.keys.first() } // 어르신 이름 리스트
val elderIds = nameIdList.map { it.values.first() } // 어르신 아이디 리스트
val elderNames = elderMap.values.toList()
val elderIds = elderMap.keys.toList()

var selectedIndex by remember { mutableIntStateOf(0) } // 선택된 어르신 인덱스
val selectedId = elderIds.getOrNull(selectedIndex) ?: 0 // 선택된 어르신 아이디
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.konkuk.medicarecall.data.repository.ElderIdRepository
import com.konkuk.medicarecall.data.repository.EldersInfoRepository
Comment on lines +8 to +9
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

사용하지 않는 import 제거 필요

파이프라인 오류: EldersInfoRepository import가 사용되지 않습니다.

 import com.konkuk.medicarecall.data.repository.ElderIdRepository
-import com.konkuk.medicarecall.data.repository.EldersInfoRepository
 import com.konkuk.medicarecall.data.repository.SetCallRepository
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import com.konkuk.medicarecall.data.repository.ElderIdRepository
import com.konkuk.medicarecall.data.repository.EldersInfoRepository
import com.konkuk.medicarecall.data.repository.ElderIdRepository
🧰 Tools
🪛 GitHub Actions: Android CI

[error] 9-9: Detekt: Unused import. [NoUnusedImports]

🤖 Prompt for AI Agents
In
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/viewmodel/CallTimeViewModel.kt
around lines 8-9, remove the unused import of
com.konkuk.medicarecall.data.repository.EldersInfoRepository; keep only the
imports actually referenced (e.g., ElderIdRepository) and run your IDE's
"Optimize Imports" or Kotlin lint to ensure no other unused imports remain so
the pipeline error is resolved.

import com.konkuk.medicarecall.data.repository.SetCallRepository
import com.konkuk.medicarecall.ui.model.CallTimes
import dagger.hilt.android.lifecycle.HiltViewModel
Expand All @@ -15,12 +17,37 @@ import javax.inject.Inject

@HiltViewModel
class CallTimeViewModel @Inject constructor(
private val setCallRepo: SetCallRepository,
private val setCallRepository: SetCallRepository,
private val elderIdRepository: ElderIdRepository,
) : ViewModel() {
val timeMap = mutableStateMapOf<Int, CallTimes>()
val isLoading = mutableStateOf(false)
val lastError = mutableStateOf<Throwable?>(null)

// Flow -> State 로 뱐환해서 보관
private val _elderIds = mutableStateOf<Map<Int, String>>(emptyMap())
val elderIds get() = _elderIds.value // UI에서 접근할 값

init {
observeElderIds()
}

// suspend + Flow 안전하게 처리하는 함수
private fun observeElderIds() {
viewModelScope.launch {
try {
elderIdRepository.getElderIds()
.collect { result ->
_elderIds.value = result
Log.d("CallTimeViewModel", "elderIds 업데이트: $result")
}
} catch (e: Exception) {
Log.e("CallTimeViewModel", "elderIds 수집 실패", e)
lastError.value = e
}
}
}

fun setTimes(id: Int, times: CallTimes) {
timeMap[id] = times
}
Expand All @@ -46,7 +73,7 @@ class CallTimeViewModel @Inject constructor(
val jobs = elderIds.map { id ->
val times = timeMap[id] ?: error("'$id'의 시간이 비어있습니다.")
async {
setCallRepo.saveForElder(id, times).getOrThrow()
setCallRepository.saveForElder(id, times).getOrThrow()
Log.d("CallTimeViewModel", "Saved call times for id:$id")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import com.konkuk.medicarecall.R
import com.konkuk.medicarecall.ui.common.component.CTAButton
import com.konkuk.medicarecall.ui.feature.login.info.component.LoginBackButton
import com.konkuk.medicarecall.ui.feature.login.payment.component.PaymentPriceItem
import com.konkuk.medicarecall.ui.feature.settings.viewmodel.EldersInfoViewModel
import com.konkuk.medicarecall.ui.feature.login.payment.viewmodel.PaymentViewModel
import com.konkuk.medicarecall.ui.theme.MediCareCallTheme
import com.konkuk.medicarecall.ui.type.CTAButtonType
import java.text.NumberFormat
Expand All @@ -46,11 +46,14 @@ fun PaymentScreen(
onBack: () -> Unit,
modifier: Modifier = Modifier,
navigateToNaverPay: () -> Unit = {},
elderInfoViewModel: EldersInfoViewModel = hiltViewModel(),
paymentViewModel: PaymentViewModel = hiltViewModel(),
) {
val scrollState = rememberScrollState()
val elderMap = paymentViewModel.elderMap
val elders = elderMap.values.toList()
var isClicked by remember { mutableStateOf(false) }
val elders = elderInfoViewModel.eldersInfoList.map { it.name }
val totalAmount = paymentViewModel.getTotalPrice() // Assuming 29,000 is the monthly fee per elder

Comment on lines +52 to +56
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

isClicked 로컬 상태가 더 이상 갱신되지 않아 결제 UI가 항상 비활성 상태입니다.

지금 구조에서는:

  • ButtononClickpaymentViewModel.togglePaymentSelect()만 호출하고
  • isClicked는 어디에서도 변경되지 않아서 항상 false인 상태로 남습니다.
  • 하지만 실제 UI에서는 isClicked를 이용해 네이버페이 버튼 border 색, CTA 버튼 타입, navigateToNaverPay() 호출 여부를 모두 결정하고 있어서, 사용자가 아무리 눌러도 결제 버튼이 활성화되지 않는 문제가 생깁니다.

PaymentViewModel.isPaymentSelected를 단일 소스로 사용하도록 바꾸면 자연스럽게 해결됩니다. 예시는 다음과 같습니다(Detekt의 NoMultipleSpaces 경고까지 함께 정리):

-    val elderMap = paymentViewModel.elderMap
-    val elders = elderMap.values.toList()
-    var isClicked by remember { mutableStateOf(false) }
-    val totalAmount = paymentViewModel.getTotalPrice()  // Assuming 29,000 is the monthly fee per elder
+    val elderMap = paymentViewModel.elderMap
+    val elders = elderMap.values.toList()
+    val isClicked by paymentViewModel.isPaymentSelected
+    val totalAmount = paymentViewModel.getTotalPrice() // Assuming 29,000 is the monthly fee per elder
@@
-            Button(
+            Button(
                 modifier = modifier
                     .fillMaxWidth()
                     .padding(vertical = 18.dp),
-                onClick = { paymentViewModel.togglePaymentSelect() },
+                onClick = { paymentViewModel.togglePaymentSelect() },
@@
             Spacer(modifier = modifier.weight(1f))
             CTAButton(
                 type = if (isClicked) CTAButtonType.GREEN else CTAButtonType.DISABLED,
                 text = "결제하기",
                 onClick = { if (isClicked) navigateToNaverPay() },
             )

이렇게 바꾸면:

  • 네이버페이 버튼 클릭 시 ViewModel의 선택 상태가 바뀌고
  • 그 상태를 isClicked로 그대로 바라보면서 border 색/CTA 활성화 여부가 함께 갱신됩니다.
  • 아울러 getTotalPrice() 줄에서 두 칸 공백을 한 칸으로 줄여 Detekt: NoMultipleSpaces 경고도 같이 해결됩니다.

Also applies to: 139-166

🧰 Tools
🪛 GitHub Actions: Android CI

[error] 55-56: Detekt: Unnecessary long whitespace. [NoMultipleSpaces]

🤖 Prompt for AI Agents
In
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/payment/screen/PaymentScreen.kt
around lines 52-56 (and similarly where applicable at 139-166), remove the local
mutableState isClicked and instead read the selection state from
PaymentViewModel (e.g., collectAsState()/observeAsState on
paymentViewModel.isPaymentSelected) so the UI reacts to ViewModel changes;
update the Button onClick to only call paymentViewModel.togglePaymentSelect()
(no local state mutation), use the collected ViewModel state for border
color/CTA enablement and to decide navigateToNaverPay(), and fix the double
space in the getTotalPrice() line to eliminate the Detekt NoMultipleSpaces
warning.

Column(
modifier = modifier
.fillMaxSize()
Expand Down Expand Up @@ -124,8 +127,6 @@ fun PaymentScreen(
color = MediCareCallTheme.colors.black,
)
Spacer(modifier = modifier.weight(1f))
val totalAmount =
elders.size * 29000 // Assuming 29,000 is the monthly fee per elder
val formatted = NumberFormat.getNumberInstance(Locale.KOREA).format(totalAmount)
val displayText = "₩$formatted/월"
Text(
Expand All @@ -139,7 +140,7 @@ fun PaymentScreen(
modifier = modifier
.fillMaxWidth()
.padding(vertical = 18.dp),
onClick = { if (isClicked) isClicked = false else isClicked = true },
onClick = { paymentViewModel.togglePaymentSelect() },
colors = ButtonDefaults.buttonColors(
containerColor = MediCareCallTheme.colors.bg,
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.konkuk.medicarecall.ui.feature.login.payment.viewmodel

import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.konkuk.medicarecall.data.repository.ElderIdRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class PaymentViewModel @Inject constructor(
private val elderIdRepository: ElderIdRepository,
) : ViewModel() {

// 결제 대상 어르신 목록 (로컬 DataStore 기준)
private val _elderMap = mutableStateOf<Map<Int, String>>(emptyMap())
val elderMap get() = _elderMap.value

// 결제 버튼 선택 여부
val isPaymentSelected = mutableStateOf(false)

// 월 요금 (상수)
private val pricePerElder = 29_000

init {
observeElders()
}

// 어르신 목록 Flow → State
private fun observeElders() {
viewModelScope.launch {
elderIdRepository.getElderIds()
.collect { result ->
_elderMap.value = result
}
}
}

// 총 결제 금액
fun getTotalPrice(): Int {
return elderMap.size * pricePerElder
}

// 네이버페이 선택 토글
fun togglePaymentSelect() {
isPaymentSelected.value = !isPaymentSelected.value
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,64 +18,43 @@ class EldersInfoViewModel @Inject constructor(
private val eldersInfoRepository: EldersInfoRepository,
private val elderIdRepository: ElderIdRepository,
) : ViewModel() {

var eldersInfoList by mutableStateOf<List<EldersInfoResponseDto>>(emptyList())
private set

val isLoading = mutableStateOf(false)
val error = mutableStateOf<Throwable?>(null)

var elderNameIdMapList = elderIdRepository.getElderIds()

var errorMessage by mutableStateOf<String?>(null)
private set

init {
ensureLoaded()
}

fun ensureLoaded() {
if (eldersInfoList.isEmpty() && !isLoading.value) {
if (eldersInfoList.isEmpty()) {
loadEldersInfo()
}
}

fun refresh() = loadEldersInfo(force = true)
fun refresh() = loadEldersInfo()

private fun loadEldersInfo(force: Boolean = false) {
private fun loadEldersInfo() {
if (isLoading.value) return
isLoading.value = true
Log.d("EldersInfoViewModel", "loadEldersInfo() 호출 (force=$force)")

viewModelScope.launch {
eldersInfoRepository.getElders()
.onSuccess { list ->
Log.d("EldersInfoViewModel", "노인 개인 정보 불러오기 성공: ${list.size}개")
eldersInfoList = list

// 이름→ID 매핑(순서 유지)
val mapped = list.map { mapOf(it.name to it.elderId) }
elderNameIdMapList = mapped

// ElderIdRepository 동기화
// NOTE: 중복 적재를 피하려면 ElderIdRepository에 replaceAll(...)을 추가하는 걸 추천.
val repoCurrent = elderIdRepository.getElderIds()
if (force || repoCurrent.isEmpty()) {
// 간단 동기화(초기 1회 or refresh 시)
mapped.forEach { m ->
val e = m.entries.first()
elderIdRepository.addElderId(e.key, e.value)
}
}
// 서버 → DataStore 전체 동기화
val mapped = list.associate { it.elderId to it.name }
elderIdRepository.updateElderIds(mapped)

error.value = null
errorMessage = null
}
.onFailure {
error.value = it
errorMessage = "노인 개인 정보를 불러오지 못했습니다."
Log.e("EldersInfoViewModel", "노인 개인 정보 로딩 실패: ${it.message}", it)
Log.e("EldersInfoViewModel", "load 실패", it)
}

isLoading.value = false
}
}
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[versions]
agp = "8.13.0"
agp = "8.13.1"
kotlin = "2.0.21"
coreKtx = "1.10.1"
junit = "4.13.2"
Expand Down
Loading