diff --git a/data/src/main/java/com/kusitms/data/remote/entity/response/home/AttendInfoPayload.kt b/data/src/main/java/com/kusitms/data/remote/entity/response/home/AttendInfoPayload.kt index 264d2db..f9b9d50 100644 --- a/data/src/main/java/com/kusitms/data/remote/entity/response/home/AttendInfoPayload.kt +++ b/data/src/main/java/com/kusitms/data/remote/entity/response/home/AttendInfoPayload.kt @@ -6,8 +6,7 @@ data class AttendInfoPayload( val curriculumId: Int, val curriculumName: String, val isAttended: Boolean, - val date: String, - val time: String + val date: String ) fun AttendInfoPayload.toModel() = @@ -15,6 +14,5 @@ fun AttendInfoPayload.toModel() = curriculumId = curriculumId ?: 0, curriculumName = curriculumName ?: "", isAttended = isAttended ?: false, - date = date ?: "2월 17일", - time = time ?: "오후 02:00" + date = date ?: "2월 17일" ) \ No newline at end of file diff --git a/domain/src/main/java/com/kusitms/domain/model/home/AttendCurrentModel.kt b/domain/src/main/java/com/kusitms/domain/model/home/AttendCurrentModel.kt index fcaa6fd..0411bde 100644 --- a/domain/src/main/java/com/kusitms/domain/model/home/AttendCurrentModel.kt +++ b/domain/src/main/java/com/kusitms/domain/model/home/AttendCurrentModel.kt @@ -21,5 +21,4 @@ data class AttendInfoModel( val curriculumName: String, val isAttended: Boolean, val date: String, - val time:String ) \ No newline at end of file diff --git a/presentation/src/main/java/com/kusitms/presentation/model/home/attend/AttendViewModel.kt b/presentation/src/main/java/com/kusitms/presentation/model/home/attend/AttendViewModel.kt index 8bb750b..07cb2e1 100644 --- a/presentation/src/main/java/com/kusitms/presentation/model/home/attend/AttendViewModel.kt +++ b/presentation/src/main/java/com/kusitms/presentation/model/home/attend/AttendViewModel.kt @@ -29,6 +29,7 @@ import java.time.LocalDateTime import java.time.Year import java.time.format.DateTimeFormatter import java.time.format.DateTimeParseException +import java.time.temporal.ChronoUnit import java.util.* import javax.inject.Inject @@ -45,7 +46,7 @@ class AttendViewModel @Inject constructor( private val _attendListInit = MutableStateFlow>(emptyList()) val attendListInit : StateFlow> = _attendListInit.asStateFlow() - private val _upcomingAttend = MutableStateFlow(AttendInfoModel(0, "커리큘럼이 없습니다", false, "", "")) + private val _upcomingAttend = MutableStateFlow(AttendInfoModel(0, "커리큘럼이 없습니다", false, "")) val upcomingAttend : StateFlow = _upcomingAttend.asStateFlow() private val _attendScore = MutableStateFlow(AttendModel(0, 0, 0, 0, "수료 가능한 점수에요")) @@ -135,8 +136,8 @@ class AttendViewModel @Inject constructor( _snackbarEvent.emit(AttendSnackBarEvent.Attend_success) _qrEnabled.value = false } - delay(10000L) // 다음 반복까지 10초 대기 } + delay(10000L) // 다음 반복까지 10초 대기 } } } @@ -154,24 +155,38 @@ class AttendViewModel @Inject constructor( } } - @RequiresApi(Build.VERSION_CODES.O) - fun combineDateAndTime(date: String, time: String): LocalDateTime? { - val currentYear = LocalDate.now().year - // 날짜 형식에 연도 추가 - val dateFormatter = DateTimeFormatter.ofPattern("MM월 dd일", Locale.KOREAN) - val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일 a hh:mm", Locale.KOREAN) + val timeUntilEvent = upcomingAttend + .map { attend -> calculateTimeTerm(attend.date) } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000L), // 스트림이 시작되는 조건 + initialValue = "" + ) - return try { - // 날짜와 시간 문자열을 현재 연도와 결합 - val dateTimeStr = "${currentYear}년 $date $time" - LocalDateTime.parse(dateTimeStr, dateTimeFormatter) - } catch (e: Exception) { - null // 파싱 실패 시 null 반환 + @RequiresApi(Build.VERSION_CODES.O) + fun calculateTimeTerm(date: String, durationMinutes: Long = 120): String { + if (date.isEmpty()) return "" + + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss") + val eventDate = LocalDateTime.parse(date, formatter) + val currentDate = LocalDateTime.now() + val minutesDiff = ChronoUnit.MINUTES.between(currentDate, eventDate) + + return when { + minutesDiff > 1440 -> "D-${minutesDiff / 1440}" // 하루 이상 + minutesDiff in 1..1439 -> { + val hours = minutesDiff / 60 + val minutes = minutesDiff % 60 + String.format("%02d:%02d", hours, minutes) + } // 하루 이하 + minutesDiff in -30..120 -> "Soon" // 이벤트 시작 2시간 이내 + minutesDiff <= -30 -> "Ended" // 이벤트 시작 후 30분 지남 + else -> "No Event" } } - enum class Status(val displayName: String) { + enum class Status(val displayName: String) { PRESENT("출석"), ABSENT("결석"), LATE("지각"); diff --git a/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/AttendButton.kt b/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/AttendButton.kt index 0d719ad..9a59c5b 100644 --- a/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/AttendButton.kt +++ b/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/AttendButton.kt @@ -54,7 +54,7 @@ fun AttendBtnOff( verticalArrangement = Arrangement.Center ) { Text(text = stringResource(R.string.attend_btn_attend_wait), style = KusitmsTypo.current.Text_Semibold, color = KusitmsColorPalette.current.Grey400) - Text(text = leftTime, style = KusitmsTypo.current.Text_Semibold, color = KusitmsColorPalette.current.Grey400) + Text(text = leftTime, style = KusitmsTypo.current.Caption1, color = KusitmsColorPalette.current.Grey400) } } } @@ -70,7 +70,7 @@ fun AttendBtnFailure() { onClick = {}, enabled = false ) { - Text(text = stringResource(R.string.attend_btn_attend_failure), style = KusitmsTypo.current.Text_Semibold, color = KusitmsColorPalette.current.Sub2, maxLines = 1) + Text(text = stringResource(R.string.attend_btn_attend_failure), style = KusitmsTypo.current.Caption1, color = KusitmsColorPalette.current.Sub2, maxLines = 1) } } @@ -85,6 +85,6 @@ fun AttendBtnSuccess() { onClick = {}, enabled = false ) { - Text(text = stringResource(R.string.attend_btn_attend_success), style = KusitmsTypo.current.Text_Semibold, color = KusitmsColorPalette.current.Sub1, maxLines = 1) + Text(text = stringResource(R.string.attend_btn_attend_success), style = KusitmsTypo.current.Caption1, color = KusitmsColorPalette.current.Sub1, maxLines = 1) } } \ No newline at end of file diff --git a/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/AttendScreen.kt b/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/AttendScreen.kt index 4bddeef..1b0b2d2 100644 --- a/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/AttendScreen.kt +++ b/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/AttendScreen.kt @@ -112,6 +112,7 @@ fun AttendTopBar() { } } + @RequiresApi(Build.VERSION_CODES.O) @Composable fun AttendPreColumn( @@ -119,20 +120,9 @@ fun AttendPreColumn( navController: NavHostController ) { val curri by viewModel.upcomingAttend.collectAsState() - val curriculum = curri.curriculumName ?: "커리큘럼이 없습니다" - val eventDateTime = viewModel.combineDateAndTime(curri.date, curri.time) - val currentTime = remember { mutableStateOf(LocalDateTime.now()) } - val duration = eventDateTime?.let { - Duration.between(currentTime.value, eventDateTime) - } ?: Duration.ZERO + val curriculum = curri.curriculumName + val timeUntilEvent by viewModel.timeUntilEvent.collectAsState() - // 주기적으로 현재 시간 상태 업데이트 - LaunchedEffect(key1 = Unit) { - while (true) { - currentTime.value = LocalDateTime.now() - delay(60000) - } - } Box(modifier = Modifier .fillMaxWidth() @@ -153,25 +143,35 @@ fun AttendPreColumn( .height(56.dp), verticalArrangement = Arrangement.Center ) { - Text(text = stringResource(R.string.attend_box1_title), style = KusitmsTypo.current.Caption1, color = KusitmsColorPalette.current.Main500) + Text( + text = stringResource(R.string.attend_box1_title), + style = KusitmsTypo.current.Caption1, + color = KusitmsColorPalette.current.Main500 + ) KusitmsMarginVerticalSpacer(size = 4) - Text(text = curriculum, style = KusitmsTypo.current.SubTitle1_Semibold, color = KusitmsColorPalette.current.White) + Text( + text = curriculum, + style = KusitmsTypo.current.SubTitle1_Semibold, + color = KusitmsColorPalette.current.White + ) } - if (duration.isNegative) { - val minutesAfterStart = duration.abs().toMinutes() - if (minutesAfterStart <= 30) { - // 이벤트 시작 후 30분 이내 - AttendBtnOn(navController = navController) // 여기서 정책에 따라 AttendBtnFailure로 변경 가능 - } else { - // 이벤트 시작 후 30분 초과 - AttendBtnFailure() + when { + timeUntilEvent.startsWith("D-") -> { + AttendBtnOff(timeUntilEvent) + } + timeUntilEvent.matches(Regex("\\d{2}:\\d{2}")) -> { + AttendBtnOff(timeUntilEvent) // HH:MM 형식으로 남은 시간이 표시될 때 + } + timeUntilEvent == "Soon" -> { + AttendBtnOn(navController) + } + timeUntilEvent == "Ended" -> { + if(curri.isAttended) { + AttendBtnSuccess() + } else { + AttendBtnFailure() + } } - } else if (duration.isZero || (duration.toMinutes() in 1..30)) { - // 이벤트 시작 전 30분 이내 - AttendBtnOn(navController = navController) - } else { - // 이벤트 시작까지 30분 이상 남음 - AttendBtnOff("D-${duration.toDaysPart()} ${duration.toHoursPart()}:${duration.toMinutesPart()}") } } } diff --git a/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/camera/CameraPreview.kt b/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/camera/CameraPreview.kt index 1ef963b..1ce35c5 100644 --- a/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/camera/CameraPreview.kt +++ b/presentation/src/main/java/com/kusitms/presentation/ui/home/attend/camera/CameraPreview.kt @@ -44,6 +44,9 @@ import com.kusitms.presentation.common.ui.KusitmsMarginHorizontalSpacer import com.kusitms.presentation.common.ui.theme.KusitmsColorPalette import com.kusitms.presentation.common.ui.theme.KusitmsTypo import com.kusitms.presentation.model.home.attend.AttendViewModel +import com.kusitms.presentation.ui.home.attend.AttendBtnFailure +import com.kusitms.presentation.ui.home.attend.AttendBtnOff +import com.kusitms.presentation.ui.home.attend.AttendBtnSuccess @Composable fun CameraScreen( @@ -51,7 +54,6 @@ fun CameraScreen( ) { val message by viewModel.snackbarEvent.collectAsState(initial = AttendViewModel.AttendSnackBarEvent.None) val qrEnabled by viewModel.qrEnabled.collectAsState() - ComposablePermission( permission = Manifest.permission.CAMERA, onGranted = {