Skip to content

Conversation

@DongChyeon
Copy link
Member

@DongChyeon DongChyeon commented Aug 21, 2025

💡 Issue

🌱 Key changes

스프린트 내용
관련 디스코드 스레드

  • 다음 세션 -> 오늘의 세션 텍스트 수정
  • 세션 탭에서 v1/active-generation/sessions 사용
  • 오늘의 세션에는 TODAY, ONGOING 인 세션을 스크롤 가능한 형태로 노출
  • 이전 세션없을 때 이미지 및 문구 변경 이슈에서 BottomBarHeight가 56.dp로 고정되어있었는데 이를 동적으로 측정하도록 수정
  • 일정 탭의 세션, 일정 시간 표기 방식 수정

✅ To Reviewers

// v1/active-generation/sessions 대응을 위해 사용
fun String?.toActiveSessionProgressPhase(): ScheduleProgressPhase {
    return when (this) {
        "DONE" -> ScheduleProgressPhase.DONE
        "ONGOING" -> ScheduleProgressPhase.ONGOING
        "TODAY" -> ScheduleProgressPhase.TODAY
        "PENDING" -> ScheduleProgressPhase.PENDING
        else -> ScheduleProgressPhase.PENDING
    }
}

fun String?.toActiveSessionAttendanceStatus(): AttendanceStatus? {
    return when (this) {
        "PENDING" -> null
        "ON_TIME" -> AttendanceStatus.ATTENDED
        "LATE" -> AttendanceStatus.LATE
        "ABSENT" -> AttendanceStatus.ABSENT
        "EARLY_CHECK_OUT" -> AttendanceStatus.EARLY_LEAVE
        "EXCUSED_ABSENCE" -> AttendanceStatus.EXCUSED
        else -> null
    }
}

이전에 사용하던 v1/active-generation/sessions를 다시 재사용하면서
progressPhase, attendanceStatus가 다른 API의 응답 값과 일치하지 않아 새롭게 확장 함수를 만들었습니다.

📸 스크린샷

스크린샷
image

Summary by CodeRabbit

  • 신규 기능
    • “오늘의 세션” 섹션 도입: 오늘/진행중 세션을 자동 수집해 가로 스크롤로 제공, 스냅 및 페이지 인디케이터 지원
    • 세션 시간/기간 표기 개선: 날짜·요일·시간을 함께 표시해 가독성 향상
  • UI
    • 섹션 제목을 “오늘의 세션”으로, 빈 상태 문구를 “오늘은 예정된 세션이 없어요.”로 변경
    • 진행 상태 “DONE” 표시를 “종료”로 변경
  • 버그 수정
    • 빈 장소 값은 표시하지 않도록 처리
    • 하단 바 실측 높이를 반영해 화면 하단 콘텐츠 가림 현상 완화

@DongChyeon DongChyeon self-assigned this Aug 21, 2025
@DongChyeon DongChyeon added the Feature 기능 추가, 개발 label Aug 21, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 21, 2025

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

앱 전반에 걸쳐 “오늘의 세션” 표시와 슬라이딩 UI를 도입하고, 세션 데이터 매핑을 확장(요일/기간 필드 추가)했습니다. 세션 로딩 흐름을 단일 소스(getDateGroupedSessions)에서 파생해 오늘/진행중 세션을 계산하도록 변경하고, 새로운 active-generation 세션 API를 추가했습니다. 하단 바 높이 측정값을 CompositionLocal로 공유하도록 수정했습니다.

Changes

Cohort / File(s) Summary
App scaffold & bottom bar height context
app/src/main/java/com/yapp/app/YappApp.kt, core/ui/src/main/java/com/yapp/core/ui/component/BottomNavigationBar.kt
Scaffold를 CompositionLocalProvider로 감싸 하단 바 높이를 LocalBottomBarHeight로 제공. BottomNavigationBar는 실제 높이를 측정해 내비게이션 바 높이 제외값을 전달. 기본 Local값 56.dp→0.dp로 변경.
Data API & repository (sessions source)
core/data/.../remote/api/ScheduleApi.kt, core/data/.../data/repository/ScheduleRepositoryImpl.kt
새 API getActiveGenerationSessions() 추가(@get v1/active-generation/sessions). 레포지토리의 세션 조회/새로고침이 해당 API를 사용하도록 변경.
Remote models & mapping
core/data/.../remote/model/response/DateGroupedScheduleResponse.kt, core/data/.../remote/model/response/SessionResponse.kt
ScheduleResponse에 시작/종료 요일 필드 추가 및 endDate 비-null화. TODAY/ONGOING/DONE 등 상태 매핑 함수 추가. SessionResponse→ScheduleInfo 매핑 시 startDayOfWeek/endDayOfWeek 전달 및 active-session 매핑 사용.
Domain models
core/model/src/main/java/com/yapp/model/ScheduleInfo.kt
ScheduleInfo에 startDayOfWeek: String, endDayOfWeek: String? 추가. ScheduleProgressPhase.DONE 라벨 "완료"→"종료"로 변경.
UI utilities
core/ui/src/main/java/com/yapp/core/ui/util/DateTime.kt
formatScheduleTimeRange(...) 신규 추가(날짜/요일/시간 범위 포매팅). 내부 파서(parseDate/parseTime) 도입.
Shared UI component (Indicators)
core/ui/src/main/java/com/yapp/core/ui/component/Indicator.kt
패키지 이동(feature→core.ui) 및 가시성 internal→public. 시그니처 동일.
Home
feature/home/src/main/java/com/yapp/feature/home/HomeViewModel.kt, feature/home/src/main/java/com/yapp/feature/home/component/HomeHeader.kt
HomeHeader에서 core.ui Indicators 사용으로 import 수정. HomeViewModel에 NotFoundException 처리 추가(무동작).
Schedule state, screen, VM
feature/schedule/src/main/java/com/yapp/feature/schedule/ScheduleContract.kt, .../ScheduleScreen.kt, .../ScheduleViewModel.kt
상태의 upcomingSessionInfo를 단일 항목→List로 변경. 화면은 리스트 기반 섹션으로 렌더. ViewModel은 getDateGroupedSessions 결과에서 TODAY/ONGOING 필터링으로 오늘의 세션 리스트를 파생·동기화.
Schedule components
feature/schedule/src/main/java/com/yapp/feature/schedule/component/UpcomingSessionSection.kt, .../DateGroupedScheduleItem.kt, .../SessionItem.kt
UpcomingSessionSection을 LazyRow 스냅 기반 슬라이더로 재작성(Indicators 연동). 시간 범위 표기는 formatScheduleTimeRange로 교체. 위치 표기 조건 null→isNullOrBlank 검사로 강화.
Strings (Schedule)
feature/schedule/src/main/res/values/strings.xml
“다음 세션”→“오늘의 세션”, 빈 상태 문구 업데이트.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant UI as ScheduleScreen
  participant VM as ScheduleViewModel
  participant Repo as ScheduleRepository
  participant API as ScheduleApi

  UI->>VM: loadSessions()
  activate VM
  VM->>Repo: getDateGroupedSessions()
  activate Repo
  Repo->>API: GET /v1/active-generation/sessions
  API-->>Repo: SessionResponse
  Repo-->>VM: ScheduleList (date-grouped)
  deactivate Repo
  VM->>VM: Flatten schedules → filter (TODAY, ONGOING)
  VM-->>UI: Update state { sessions, upcomingSessionInfo=list }
  deactivate VM
  note over UI: “오늘의 세션” 슬라이더는 리스트 기반으로 렌더/스냅
Loading
sequenceDiagram
  autonumber
  participant App as YappApp
  participant Loc as CompositionLocalProvider
  participant Bar as BottomNavigationBar
  participant Desc as Descendant Composables

  App->>Loc: Provide LocalBottomBarHeight
  Loc->>Bar: Compose BottomNavigationBar
  Bar->>Bar: onGloballyPositioned → measure height
  Bar->>Loc: Provide measured Dp (excl. nav bar)
  Loc-->>Desc: LocalBottomBarHeight.current
  note over Desc: 하단 바 높이에 맞춘 레이아웃 보정
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
홈/세션 탭 API 분리 — 홈: 신규, 세션: 기존 (#205) 세션 탭 경로가 active-generation 신규 API를 사용. 홈 경로 분리 여부는 확인 불가.
세션 탭 오늘의 세션 좌우 슬라이딩 기능 (#205)
시작일/종료일 표기 수정 (#205)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
LocalBottomBarHeight 기본값 56.dp→0.dp 변경 (core/ui/.../BottomNavigationBar.kt) 이슈 #205와 무관한 하단 바 레이아웃 기본값 변경입니다.
CompositionLocalProvider로 하단 바 높이 제공 추가 (app/src/.../YappApp.kt) 세션/칩/표기와 관련 없는 전역 레이아웃 컨텍스트 도입입니다.
ScheduleProgressPhase.DONE 라벨 "완료"→"종료" (core/model/.../ScheduleInfo.kt) 이슈 요구사항에 없는 라벨 텍스트 변경입니다.
Indicators 컴포넌트의 가시성/패키지 이동 (core/ui/.../Indicator.kt) 슬라이더 구현에 활용되나, 이슈에 명시된 기능 외의 라이브러리화/공개 범위 변경입니다.

Possibly related PRs

Suggested reviewers

  • TaeseongYun
  • jinukeu
  • ashwon12

Poem

툭, 툭—슬라이드, 오늘의 빛
당근을 꾹 쥐고 나는 토끼, 찰칵!
시작과 끝, 요일도 또렷하게
새 API 길 따라 통통 뛰며
일정의 숲에 길잡이 점—●—이 반짝!

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#205-session-chip-label-update

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@DongChyeon
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Aug 21, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@DongChyeon DongChyeon added the CodeRabbit CodeRabbit 코드 리뷰 요청 label Aug 21, 2025
@DongChyeon
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Aug 21, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 20

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (18)
feature/home/src/main/java/com/yapp/feature/home/HomeViewModel.kt (2)

141-149: NotFoundException/NoScheduledSessionException를 그냥 무시하면 이전 '오늘의 세션' 정보가 화면에 남습니다

404(NotFound)나 일정 없음 예외가 발생해도 upcomingSessionnull로 초기화하지 않아, 과거 데이터가 그대로 노출될 수 있습니다. 특히 Refresh에서 직전 상태가 남는 문제가 재현될 가능성이 큽니다. 예외 시 상태를 명시적으로 초기화하세요.

적용 제안(diff):

         }.onFailure { e ->
             when (e) {
                 is InvalidTokenException -> postSideEffect(HomeSideEffect.NavigateToLogin)
-                is NoScheduledSessionException -> { }
-                is NotFoundException -> { }
+                is NoScheduledSessionException, is NotFoundException -> {
+                    reduce { copy(upcomingSession = null) }
+                }
                 else -> {
                     postSideEffect(HomeSideEffect.HandleException(e))
                     e.record()
                 }
             }
         }

127-153: 전역 isLoading 토글이 동시에 실행되는 코루틴들과 충돌하여 로딩 플래그가 흔들릴 수 있습니다

EnterHomeScreen에서 3개의 로더를 joinAll로 동시 실행하는데, 각 로더가 개별적으로 isLoading=true/false를 토글합니다. 로딩 인디케이터가 깜빡이거나 잘못된 시점에 해제될 수 있습니다. 섹션별 로딩 플래그(예: isLoadingUpcoming, isLoadingAttendance) 도입 또는 참조 카운트 방식으로 전역 로딩을 관리하는 것을 권장합니다.

feature/schedule/src/main/java/com/yapp/feature/schedule/component/SessionItem.kt (1)

57-63: 공백만 있는 위치 문자열을 숨기는 처리 — 좋습니다

isNullOrBlank()으로 변경해 공백 문자열까지 걸러낸 점 👍. 다만 위치가 비어 있을 때도 직전에 고정 Spacer(4.dp)가 항상 렌더링되어 불필요한 여백이 생길 수 있습니다. 조건부로 Spacer를 표시하는 미세 개선을 제안합니다.

적용 제안(diff):

-            Spacer(modifier = Modifier.height(4.dp))
-
-            if (!location.isNullOrBlank()) {
+            if (!location.isNullOrBlank()) {
+                Spacer(modifier = Modifier.height(4.dp))
                 IconWithText(
                     iconResId = R.drawable.icon_location,
                     text = location,
                     contentDescription = null,
                 )
             }
core/ui/src/main/java/com/yapp/core/ui/component/Indicator.kt (2)

71-116: itemCount가 0/1일 때 드래그 계산에서 예외 발생 가능(coerceIn 경계 역전) 및 불필요한 입력 처리

newPage = (dragStartPage + pageOffset).coerceIn(0, itemCount - 1)itemCount == 0일 때 coerceIn(0, -1)IllegalArgumentException를 유발할 수 있습니다. 또한 0/1개일 때 포인터 입력을 활성화할 필요가 없습니다.

안전 가드 및 경계 보정 제안(diff):

     Box(
         modifier = modifier,
         contentAlignment = Alignment.Center
     ) {
         LazyRow(
             state = lazyListState,
             userScrollEnabled = false,
             modifier = Modifier
-                .pointerInput(itemCount, currentPage) {
+                .then(
+                    if (itemCount > 1) Modifier.pointerInput(itemCount, currentPage) {
                         detectDragGesturesAfterLongPress(
                             onDragStart = { offset ->
                                 haptics.performHapticFeedback(HapticFeedbackType.LongPress)
                                 dragStartPage = currentPage
                                 dragStartX = offset.x
                             },
                             onDrag = { change, _ ->
                                 val dragDistance = change.position.x - dragStartX
                                 val pageOffset = (dragDistance / dotSpacingPx).toInt()
-                            val newPage = (dragStartPage + pageOffset).coerceIn(0, itemCount - 1)
+                            val lastIndex = (itemCount - 1).coerceAtLeast(0)
+                            val newPage = (dragStartPage + pageOffset).coerceIn(0, lastIndex)
                             if (newPage != currentPage) {
                                 haptics.performHapticFeedback(HapticFeedbackType.TextHandleMove)
                                 onPageSelect(newPage)
                             }
                             }
                         )
-                    )
+                    } else Modifier
+                )
                 .widthIn(max = (visibleDotCount * (dotSize + dotSpacing)).coerceAtLeast(60.dp)),
             horizontalArrangement = Arrangement.spacedBy(dotSpacing),
             verticalAlignment = Alignment.CenterVertically
         ) {
             items(itemCount) { i ->

또한 스크롤 애니메이션도 0개일 때 실행하지 않도록 아래 수정이 필요합니다.

-    LaunchedEffect(scrollTarget) {
+    LaunchedEffect(scrollTarget, itemCount) {
         coroutineScope.launch {
-            lazyListState.animateScrollToItem(index = scrollTarget)
+            if (itemCount > 0) {
+                lazyListState.animateScrollToItem(index = scrollTarget)
+            }
         }
     }

101-116: 포커스/접근성 개선: 점(Indicator) 개별 탭 선택과 선택 상태 semantics 추가 권장

현재 long-press+drag만 지원합니다. TalkBack 사용자는 선택이 어렵고, 키보드/트랙패드 접근도 제한됩니다. 각 점에 클릭과 selected semantics를 추가해 주세요.

적용 제안(diff):

             items(itemCount) { i ->
                 val scaleFactor = 1f - (0.1f * abs(i - currentPage)).coerceAtMost(0.4f)
                 val color = if (i == currentPage) activeColor else inactiveColor
                 Box(
                     modifier = Modifier
                         .size(if (i == currentPage) selectedDotSize else dotSize)
+                        .clickable(enabled = itemCount > 1, onClick = { onPageSelect(i) }, role = Role.Tab)
+                        .semantics {
+                            selected = (i == currentPage)
+                        }
                         .graphicsLayer {
                             scaleX = scaleFactor
                             scaleY = scaleFactor
                         }
                         .drawBehind {
                             drawCircle(color)
                         }
                 )
             }

필요한 import:

import androidx.compose.foundation.clickable
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.selected
import androidx.compose.ui.semantics.semantics
feature/home/src/main/java/com/yapp/feature/home/component/HomeHeader.kt (2)

69-75: 센터 계산식이 오프셋을 두 번 포함해 약간의 오차가 발생할 수 있습니다

center = start + end/2 대신 center = start + (end - start)/2가 지정학적으로 정확합니다. 콘텐츠 패딩이 있을 때 현재 구현은 몇 px만큼 어긋날 수 있습니다.

적용 제안(diff):

-                val center = lazyListState.layoutInfo.viewportStartOffset +
-                        lazyListState.layoutInfo.viewportEndOffset / 2
+                val info = lazyListState.layoutInfo
+                val center = info.viewportStartOffset + (info.viewportEndOffset - info.viewportStartOffset) / 2

142-151: 세션이 비어 있을 때 Indicators 호출을 건너뛰어 안전 경계 확보

sessions.size == 0인 경우에도 Indicators(itemCount=0)가 호출되면, Indicator 쪽에서 경계 조건을 방어하지 않으면 크래시 가능성이 있습니다. 헤더에서도 가드하는 것이 안전합니다.

적용 제안(diff):

-        Indicators(
-            itemCount = sessions.size,
-            onPageSelect = { index ->
-                scope.launch {
-                    lazyListState.scrollToItem(index)
-                }
-            },
-            currentPage = selectedIndex
-        )
+        if (sessions.isNotEmpty()) {
+            Indicators(
+                itemCount = sessions.size,
+                onPageSelect = { index ->
+                    scope.launch { lazyListState.scrollToItem(index) }
+                },
+                currentPage = selectedIndex
+            )
+        }
core/data/src/main/java/com/yapp/core/data/remote/model/response/SessionResponse.kt (4)

22-25: startDayOfWeek/endDayOfWeek 필드 도입 시 역직렬화 호환성 확인

신규 엔드포인트에서는 제공되겠지만, 과거/다른 엔드포인트와 혼용 시 startDayOfWeek가 누락되면 역직렬화 실패 위험(Non-null)입니다. 서버 스키마가 항상 보장되는지 확인하거나, 안전성을 위해 Nullable + 기본값 처리도 고려해 주세요.


35-41: isToday 계산과 요일(dayOfTheWeek) 산정에 firstSession 의존 — 데이터 불일치 가정 확인

같은 날짜 그룹 내 모든 세션이 동일한 relativeDays/요일을 갖는다는 서버 불변식이 전제입니다. 만약 다를 가능성이 있다면 any { relativeDays == 0 } 혹은 대표값 선정 규칙(가장 이른 시간 등)으로 보강이 필요합니다.

Apply this diff to make isToday more 견고하게 계산하는 예시:

-            isToday = firstSession.relativeDays == 0,
+            isToday = sessions.any { it.relativeDays == 0 },

41-57: 세션 정렬 보강 제안(시간 기준)

서버 정렬이 항상 보장되지 않으면 UI 일관성이 떨어질 수 있습니다. 시간(없으면 이름) 기준으로 정렬 후 매핑을 권장합니다.

다음과 같이 정렬을 추가할 수 있습니다:

-            schedules = sessions.map {
+            val sorted = sessions.sortedWith(
+                compareBy<SessionResponse.Session> { it.time ?: "00:00" }
+                    .thenBy { it.name }
+            )
+            schedules = sorted.map {
                 ScheduleInfo(

62-79: Home 변환은 기존 매핑(toSessionProgressPhase) 유지 — 의도 확인

HomeSession 모델에 대해서는 기존 매핑을 유지하고 있습니다. 화면 간 상태명이 다를 수 있으므로, 기획/QA 관점에서 표현 불일치가 문제 없음을 확인해 주세요.

feature/schedule/src/main/java/com/yapp/feature/schedule/component/DateGroupedScheduleItem.kt (2)

120-139: 프리뷰 데이터 형식/일관성 정리

상단 DateGroupedScheduleItem 인자(date="2025-05-08")와 내부 ScheduleInfo.date("2023.10.01")의 포맷/값이 달라 혼동을 유발할 수 있습니다. 포맷/요일("일")과 실제 날짜 일치 여부를 맞춰 주세요.

Apply this diff 예시(값은 임의):

-                date = "2025-05-08",
-                dayOfWeek = "일",
+                date = "2025-05-18",
+                dayOfWeek = "일",
...
-                        date = "2023.10.01",
-                        endDate = "2023.10.01"
+                        date = "2025.05.18",
+                        endDate = "2025.05.18"

174-197: 두 번째 프리뷰도 동일하게 포맷 일치 권장

위 코멘트와 동일 맥락으로 날짜 문자열 포맷과 요일을 맞추면 디자인 검증시 혼동이 줄어듭니다.

feature/schedule/src/main/java/com/yapp/feature/schedule/ScheduleViewModel.kt (4)

49-57: SESSION 탭 새로고침 경로가 이원화되어 일관성/깜빡임(flicker) 우려

RefreshTab.SESSION에서 refreshUpcomingSessionInfo(= getDateGroupedSessions, 캐시 의존 가능성)와 refreshSessions(= refreshDateGroupedSessions, 네트워크 강제)를 분리 호출합니다. 서로 다른 소스/타이밍으로 isLoading을 두 번 토글해 UI 깜빡임과 상태 불일치(세션은 최신, 오늘의 세션은 구버전)가 발생할 수 있습니다. 단일 refreshDateGroupedSessions 결과로 sessions/upcomingSessionInfo를 함께 갱신하는 쪽이 안전합니다.

다음과 같이 단일 경로로 합치는 것을 제안합니다:

                 when (intent.tab) {
                     ScheduleTab.ALL -> refreshScheduleInfo(state.selectedYear, state.selectedMonth, reduce, postSideEffect)
                     ScheduleTab.SESSION -> {
-                        refreshUpcomingSessionInfo(reduce, postSideEffect)
-                        refreshSessions(reduce, postSideEffect)
+                        refreshSessionsAndUpcoming(reduce, postSideEffect)
                     }
                 }

아래에 새 함수 제안을 추가합니다(파일 외부 코드 블록 참조).


101-117: TODAY/ONGOING 필터 중복 로직 추출 및 불필요 이중 로딩 토글 제거

  • 필터 로직이 여기와 refresh 쪽에 중복됩니다. 헬퍼로 추출해 재사용을 권장합니다.
  • onSuccess 블록에서 isLoading=false로 만들고, 함수 말미에서 다시 false로 설정하고 있어 중복입니다. finally 한 곳에서만 false로 토글하세요.

Apply this diff:

-        }.onSuccess {
+        }.onSuccess {
             val upcomingSessions = it.dates
                 .flatMap { date -> date.schedules }
-                .filter { session -> session.scheduleProgressPhase == ScheduleProgressPhase.TODAY || session.scheduleProgressPhase == ScheduleProgressPhase.ONGOING }
+                .filter { session ->
+                    session.scheduleProgressPhase in setOf(
+                        ScheduleProgressPhase.TODAY,
+                        ScheduleProgressPhase.ONGOING
+                    )
+                }
 
             reduce {
                 copy(
-                    isLoading = false, sessions = it, upcomingSessionInfo = upcomingSessions
+                    isLoading = false,
+                    sessions = it,
+                    upcomingSessionInfo = upcomingSessions
                 )
             }
         }.onFailure { e ->
             postSideEffect(ScheduleSideEffect.HandleException(e))
             e.record()
         }
-        reduce { copy(isLoading = false) }
+        // finally에서 한 번만 false로 만드는 패턴 유지 권장 (상단에서 이미 false 처리됨)

또한 정렬 일관성을 위해 upcomingSessions도 날짜/시간 기준 정렬을 고려해 주세요.

Also applies to: 122-123


153-169: refreshUpcomingSessionInfo가 getDateGroupedSessions(캐시) 호출 — 새로고침 동작 불일치

새로고침 액션에서 캐시 레이어(get…)를 쓰면 최신 데이터가 반영되지 않을 수 있습니다. refreshDateGroupedSessions 사용 또는 위 제안처럼 단일 API 결과로 sessions/upcomingSessionInfo 동시 갱신을 권장합니다. 현 구조는 isLoading 토글도 2회 발생합니다.

Apply this minimal fix if 유지할 경우:

-            scheduleRepository.getDateGroupedSessions()
+            scheduleRepository.refreshDateGroupedSessions()

단, 최종적으로는 본 함수 자체를 제거하고 refreshSessions 경로에서 함께 계산하는 구조가 더 단순합니다.


175-189: refreshSessions에서 upcomingSessionInfo 동기화 누락

세션 리스트만 갱신하면 오늘의 세션 리스트와 데이터가 엇갈릴 수 있습니다. 여기서도 upcomingSessionInfo를 함께 계산/세팅하거나, 단일 함수로 병합하세요.

아래 통합 함수 예시를 참고해 주세요.

feature/schedule/src/main/java/com/yapp/feature/schedule/ScheduleScreen.kt (1)

359-399: Preview 데이터 최신 스키마와의 타입 일치 검증

프리뷰에서 upcomingSessionInfo = listOf(...)로 전달됩니다. 본문과 동일하게 네이밍/타입이 실제 ScheduleState와 일치하는지 다시 한 번 확인 바랍니다(특히 endDate/endDayOfWeek의 nullability가 데이터 계층과 다를 수 있음).

  • 프리뷰도 실제 런타임 스키마와 동일하게 유지하면 리팩토링 시 컴파일 타임에 문제를 조기 포착할 수 있습니다.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between dc98926 and c39eae0.

📒 Files selected for processing (18)
  • app/src/main/java/com/yapp/app/YappApp.kt (4 hunks)
  • core/data/src/main/java/com/yapp/core/data/data/repository/ScheduleRepositoryImpl.kt (2 hunks)
  • core/data/src/main/java/com/yapp/core/data/remote/api/ScheduleApi.kt (1 hunks)
  • core/data/src/main/java/com/yapp/core/data/remote/model/response/DateGroupedScheduleResponse.kt (3 hunks)
  • core/data/src/main/java/com/yapp/core/data/remote/model/response/SessionResponse.kt (1 hunks)
  • core/model/src/main/java/com/yapp/model/ScheduleInfo.kt (2 hunks)
  • core/ui/src/main/java/com/yapp/core/ui/component/BottomNavigationBar.kt (1 hunks)
  • core/ui/src/main/java/com/yapp/core/ui/component/Indicator.kt (2 hunks)
  • core/ui/src/main/java/com/yapp/core/ui/util/DateTime.kt (1 hunks)
  • feature/home/src/main/java/com/yapp/feature/home/HomeViewModel.kt (2 hunks)
  • feature/home/src/main/java/com/yapp/feature/home/component/HomeHeader.kt (1 hunks)
  • feature/schedule/src/main/java/com/yapp/feature/schedule/ScheduleContract.kt (2 hunks)
  • feature/schedule/src/main/java/com/yapp/feature/schedule/ScheduleScreen.kt (7 hunks)
  • feature/schedule/src/main/java/com/yapp/feature/schedule/ScheduleViewModel.kt (3 hunks)
  • feature/schedule/src/main/java/com/yapp/feature/schedule/component/DateGroupedScheduleItem.kt (6 hunks)
  • feature/schedule/src/main/java/com/yapp/feature/schedule/component/SessionItem.kt (1 hunks)
  • feature/schedule/src/main/java/com/yapp/feature/schedule/component/UpcomingSessionSection.kt (3 hunks)
  • feature/schedule/src/main/res/values/strings.xml (1 hunks)
🔇 Additional comments (10)
feature/home/src/main/java/com/yapp/feature/home/HomeViewModel.kt (1)

19-19: 404 에러 → NotFoundException 매핑 적용 여부 수동 검증 요청

core/data 모듈의 Retrofit 설정에서 ApiResultCallAdapterFactory(및 OptionalApiResultCallAdapterFactory)가 실제로 Retrofit.Builder에 등록되어 있는지 확인이 필요합니다.
이 어댑터들이 등록되어야만, handleCommonError(404)가 호출되어 NotFoundException으로 변환되며 홈 화면의 when (e) is NotFoundException 분기로 내려옵니다.

검증할 사항:

  • Retrofit 인스턴스 생성 시 ResultCallAdapterFactory.create() 혹은 OptionalApiResultCallAdapter가 Builder에 추가되었는지 (addCallAdapterFactory 호출 여부).
  • suspend 함수 호출(코루틴)에서도 동일한 에러 매핑 로직이 적용되는지.

위 설정이 누락되면 404가 HttpException으로 그대로 올라와 일반 예외 처리(HomeSideEffect.HandleException)가 될 수 있습니다.
확인 후 피드백 부탁드립니다.

core/ui/src/main/java/com/yapp/core/ui/component/Indicator.kt (1)

34-46: 공개 API 패키지 경로 변경 검증 완료

구 패키지(com.yapp.feature.home.component.Indicators)를 참조하는 import 구문은 전역 검색 결과 발견되지 않았으며, Indicators 호출부 역시 최신 패키지(com.yapp.core.ui.component)를 사용하고 있습니다.
별도 수정이 필요하지 않으므로 이 부분은 해결되었습니다.

feature/home/src/main/java/com/yapp/feature/home/component/HomeHeader.kt (2)

41-41: Indicator import 경로 변경 — 정상 적용 확인

Indicator가 core/ui로 이동하며 공개 API가 되었고, 본 파일에서도 새 경로로 교체되어 일관됩니다. 추가 조치 필요 없습니다.


125-136: onClickSessionItem이 빈 람다로 전달됩니다 — 의도 확인 필요

카드 클릭 시 동작이 비활성화되어 있습니다. 상세 화면 이동 등 의도된 네비게이션 콜백 연동을 확인해 주세요.

core/model/src/main/java/com/yapp/model/ScheduleInfo.kt (1)

41-41: ScheduleProgressPhase 변경에 따른 검토 결과

아래 검색 결과로 ScheduleProgressPhaseScheduleInfo 모델이 참조되는 위치가 확인되었습니다. 현재 ScheduleInfo.kt 에서만 DONE("종료") 로 변경된 상태이며, 이 모델을 직접 참조하는 UI/테스트 코드는 발견되지 않았습니다.

• core/model/src/main/java/com/yapp/model/ScheduleInfo.kt (enum 정의)
└ DONE("종료"), TODAY("당일"), ONGOING("진행중"), PENDING("예정")

ScheduleProgressPhase 사용처: 없음
ScheduleInfo 사용처: 없음

추가로 UI에서 모델의 title 값을 노출하는 컴포넌트나, 스냅샷/문구 비교 테스트가 있는지 확인이 필요합니다.

  • 만약 ScheduleProgressPhase.DONE.title 을 직접 사용해 "완료"를 기대하는 테스트가 있다면, 종료로 변경되며 실패할 수 있습니다.
  • 릴리스 노트 및 카피 가이드에도 “종료” 반영 여부를 검토해주세요.

필요 시:

  • 관련 UI 컴포넌트 검토 및 테스트 수정
  • 별도 PR로 테스트·문서 업데이트 분리
core/data/src/main/java/com/yapp/core/data/remote/api/ScheduleApi.kt (1)

20-21: 신규 엔드포인트 추가 LGTM

active-generation 세션 전용 API 분리는 의도와 구현이 명확합니다. 메서드 시그니처도 간결합니다.

app/src/main/java/com/yapp/app/YappApp.kt (2)

57-91: CompositionLocal로 BottomBar 높이 주입 접근은 합리적 — Insets 처리 상호작용만 재점검

contentWindowInsets = WindowInsets(0.dp)로 덮어쓰고, 화면 하단은 조건부로 navigationBars를 consume하고 있습니다. 상단(status bars) 등 여타 inset 처리는 상위 레이아웃에서 보장되는지 확인 부탁드립니다. 또한 IME(키보드) 등장 시 bottomBar 높이를 사용할 컴포넌트가 원하는 동작(레이아웃 이동/고정)을 하는지도 점검이 필요합니다.


142-146: 네비게이션 바 차감 로직 검증 필요

아래 두 가지 사항을 확인해 주세요:

  • onGloballyPositioned로 얻은 coordinates.size.height에 이미 WindowInsets가 반영된 상태인지 확인
    • 이미 반영되어 있다면, 별도 네비게이션 바 높이 차감 로직을 제거하는 방안을 고려해야 합니다.
  • heightDp - navigationBarHeight 계산 결과가 음수가 될 수 있으므로, 최소 0dp로 하한을 두는 coerceAtLeast(0.dp) 적용을 권장합니다.

예시 제안(diff):

     BottomNavigationBar(
         modifier = modifier.onGloballyPositioned { coordinates ->
             val heightDp = with(density) { coordinates.size.height.toDp() }
-            val bottomBarHeightWithoutNav = heightDp - navigationBarHeight
+            val bottomBarHeightWithoutNav = (heightDp - navigationBarHeight).coerceAtLeast(0.dp)

             onHeightMeasured(bottomBarHeightWithoutNav)
         }

또한, BottomNavigationBar 내부에서 이미 WindowInsets 처리를 수행 중이라면 이중 차감 여부를 반드시 검토해 주세요.

feature/schedule/src/main/java/com/yapp/feature/schedule/component/DateGroupedScheduleItem.kt (1)

86-94: formatScheduleTimeRange로 교체 LGTM — null/형식 케이스만 점검

date, endDate 형식(yyyy-MM-dd vs yyyy.MM.dd 혼재)과 time/endTime null 케이스에서 의도한 문자열이 생성되는지 확인해 주세요. 유틸이 null 반환 시, SessionItem의 duration 파라미터 처리(빈 문자열 대체 등)도 점검이 필요합니다.

core/data/src/main/java/com/yapp/core/data/remote/model/response/DateGroupedScheduleResponse.kt (1)

44-47: endDate/요일 필드 null 안정성 확인 필요

  • core/data/src/main/java/com/yapp/core/data/remote/model/response/DateGroupedScheduleResponse.kt (Lines 44–47)
    endDate: String → 서버에서 값이 빠지면 kotlinx.serialization 디시리얼라이즈 예외 발생 가능
    startDayOfTheWeek/endDayOfTheWeek가 non-null로 추가되어, UI(UpcomingSessionItem)에서는 String?로 가정하는 현재 타입과 불일치

제안하는 안전한 기본값 처리 방식:

 data class ScheduleResponse(
   …
-  val startDayOfTheWeek: String,
-  val endDate: String,
-  val endDayOfTheWeek: String,
+  val startDayOfTheWeek: String,
+  val endDate: String? = null,
+  val endDayOfTheWeek: String? = null,
   …
 ) {
   fun toScheduleModel() = ScheduleInfo(
     …
-    endDate = endDate,
-    startDayOfWeek = startDayOfTheWeek,
-    endDayOfWeek = endDayOfTheWeek,
+    endDate = endDate ?: date,                           // 단일 일정 기본값
+    startDayOfWeek = startDayOfTheWeek,
+    endDayOfWeek = endDayOfTheWeek ?: startDayOfTheWeek, // 미제공 시 시작 요일로 대체
     …
   )
 }
  • 위 처리 후에도 서버 스펙(특히 단일일정에서 endDate/endDayOfTheWeek 보장 여부)을 반드시 재확인해주세요.
  • 서버 계약이 100% non-null을 보장한다면, UI 측 UpcomingSessionItem 또한 non-null 타입으로 통일하는 방향을 고려하십시오.

@coderabbitai
Copy link

coderabbitai bot commented Aug 21, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

import com.yapp.core.designsystem.theme.YappTheme

val LocalBottomBarHeight = compositionLocalOf { 56.dp }
val LocalBottomBarHeight = compositionLocalOf { 0.dp }
Copy link
Member

Choose a reason for hiding this comment

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

C: 음 .. 혹시 이거 scaffold의 innerpadding.calculateBottom() 써서 받아오는건 안되려나요?

Copy link
Member Author

Choose a reason for hiding this comment

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

bd0ad36 수정했습니다!

return "${formatToKoreanTime(context, startTime)} - ${formatToKoreanTime(context, endTime)}"
}

fun formatScheduleTimeRange(
Copy link
Member

Choose a reason for hiding this comment

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

C: 요 함수는 컴포저블 내부에서도 호출되겠죠? 리컴포지션 때마다 호출되면 성능 이슈가 좀 있을 것 같은데 호출되는 곳에서 remember로 잘 처리되고 있는지 한번 확인 부탁드려용

Copy link
Member Author

Choose a reason for hiding this comment

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

aacce45 수정했습니다!

@DongChyeon DongChyeon requested a review from jinukeu August 22, 2025 07:51
Copy link
Member

@jinukeu jinukeu left a comment

Choose a reason for hiding this comment

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

고생하셨어요!!

@DongChyeon DongChyeon merged commit 7eb82f0 into develop Aug 22, 2025
3 of 4 checks passed
@DongChyeon DongChyeon deleted the feature/#205-session-chip-label-update branch August 22, 2025 12:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CodeRabbit CodeRabbit 코드 리뷰 요청 Feature 기능 추가, 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] 세션 목록에서 칩에 대한 표현 명확화

2 participants